From 457b05a7ce7d733dd96528bd1c7b7b5643e312cb Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 24 Nov 2018 16:27:48 +0100 Subject: [PATCH] Added support for Chords --- README.md | 1 + docs/Future.md | 2 +- main/config/default.json | 4 +- main/music/MIDIPlayer.cpp | 26 +++++---- main/music/Measure.cpp | 39 ++++++++----- main/music/Measure.h | 33 ++++++++--- main/music/Note.cpp | 89 ++++++++++++++++------------- main/music/Note.h | 111 +++++++++++++++++++++++++++++++++++-- main/music/Part.h | 28 +++++----- main/util/FileHandler.cpp | 114 +++++++++++++++++++++----------------- main/util/Generator.cpp | 57 ++++++++++--------- main/util/Generator.h | 8 +-- test/music/PartTest.cpp | 2 +- 13 files changed, 339 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 28befdc..30d0d58 100644 --- a/README.md +++ b/README.md @@ -121,3 +121,4 @@ compile `cmake` of this project with the flag `-DTRNG_LOC` set to your home dire | 18-11-2018 | Added **1/f Noise** and **Centralized** as a pitch algorithm. | 22-11-2018 | Fixed MidiPlayer playback with ties, added `bpm` and `time` signature to styles. | 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). diff --git a/docs/Future.md b/docs/Future.md index 3775bc3..33a091c 100644 --- a/docs/Future.md +++ b/docs/Future.md @@ -16,6 +16,7 @@ This file describes all planned features to be added in the future - _(Dynamic algorithms)_ - _(Custom algorithm parsing)_ - MidiPlayer + - Optimize playback (redo algorithm). - _(Force playing at all times)_ - Config - Make it so the execution does not break on invalid input (preprocessing) @@ -24,4 +25,3 @@ This file describes all planned features to be added in the future - General Todo - Simple Installation - Automatic beaming for notes - - Add support for chords diff --git a/main/config/default.json b/main/config/default.json index dea3791..daecd56 100644 --- a/main/config/default.json +++ b/main/config/default.json @@ -10,13 +10,13 @@ "options": { "rhythm": { "smallest": "16th", - "largest": "quarter" + "largest": "half" } }, "rest-ratio": 0.05 }, "export": { - "filename": "good-rhythm.xml", + "filename": "music.xml", "title": "My Score", "composer": "autoplay v@VERSION@", "rights": "Copyright \u00A9 2018 autoplay v@VERSION@, created by Randy Paredis" diff --git a/main/music/MIDIPlayer.cpp b/main/music/MIDIPlayer.cpp index 5d32c74..ed7a278 100644 --- a/main/music/MIDIPlayer.cpp +++ b/main/music/MIDIPlayer.cpp @@ -123,6 +123,8 @@ namespace autoplay { midiout->sendMessage(&msg); // Collect & Play Measures + // TODO: split up in collect and playback + // TODO: add support for playing chords, instead of the bottom note. logger->debug("Collecting and Playing ") << duration << " Measure(s)"; zz::log::ProgBar pb{duration, "Playing"}; for(unsigned measure_number = 0; measure_number < duration; ++measure_number) { @@ -146,44 +148,44 @@ namespace autoplay { bpm = curr_measure->getBPM(); } - for(const auto& note : curr_measure->getNotes()) { + for(const auto& chord : curr_measure->getNotes()) { uint8_t msgch = channel; if(score.getParts().at(channel)->getInstruments().size() > 1 || score.getParts().at(channel)->getInstruments().at(0)->isPercussion()) { msgch = 9; } - if(note.isPause()) { - for(uint8_t len = 0; len < note.getDuration(); ++len) { + if(chord.isPause()) { + for(uint8_t len = 0; len < chord.getDuration(); ++len) { msg = {}; nl.emplace_back(msg); } continue; } std::vector _msg = {}; - if(!note.getTieEnd()) { - _msg = note.getOnMessage(msgch); + if(!chord.getTieEnd()) { + _msg = chord.bottom()->getOnMessage(msgch); if(score.getParts().at(channel)->getInstruments().size() > 1 || score.getParts().at(channel)->getInstruments().at(0)->isPercussion()) { - if(note.getInstrument() != nullptr) { - _msg.at(1) = note.getInstrument()->getUnpitched(); + if(chord.bottom()->getInstrument() != nullptr) { + _msg.at(1) = chord.bottom()->getInstrument()->getUnpitched(); } } } nl.emplace_back(_msg); - for(uint8_t len = 0; len < note.getDuration() - 2; ++len) { // duration - strike - release + for(uint8_t len = 0; len < chord.getDuration() - 2; ++len) { // duration - strike - release msg = {}; nl.emplace_back(msg); } - if(note.getTieStart()) { + if(chord.getTieStart()) { msg = {}; nl.emplace_back(msg); } else { - _msg = note.getOffMessage(msgch); + _msg = chord.bottom()->getOffMessage(msgch); if(score.getParts().at(channel)->getInstruments().size() > 1 || score.getParts().at(channel)->getInstruments().at(0)->isPercussion()) { - if(note.getInstrument() != nullptr) { - _msg.at(1) = note.getInstrument()->getUnpitched(); + if(chord.bottom()->getInstrument() != nullptr) { + _msg.at(1) = chord.bottom()->getInstrument()->getUnpitched(); } } nl.emplace_back(_msg); diff --git a/main/music/Measure.cpp b/main/music/Measure.cpp index 5e68d53..9b98741 100644 --- a/main/music/Measure.cpp +++ b/main/music/Measure.cpp @@ -26,8 +26,10 @@ namespace autoplay { unsigned int Measure::length() const { unsigned int length = 0; - for(const Note& n : m_notes) { - length += n.getDuration(); + for(const Chord& c : m_notes) { + if(!c.empty()) { + length += c.getDuration(); + } } return length; } @@ -50,32 +52,33 @@ namespace autoplay { me->setBPM(m_bpm); MeasureList res = {me}; if(isOverflowing()) { - for(const Note& n : m_notes) { + for(const Chord& c : m_notes) { std::shared_ptr m = res.back(); unsigned int m_len = m->length(); - if(m_len + n.getDuration() <= m->max_length()) { - m->m_notes.emplace_back(n); - } else if(m_len == m->max_length()) { + if(m_len + c.getDuration() <= m->max_length()) { + m->m_notes.emplace_back(c); + } else if(m_len == m->max_length() && c.getDuration() < m->max_length()) { res.emplace_back(std::make_shared(m_clef, m_time, m_divisions, m_fifths)); res.back()->setBPM(m_bpm); - res.back()->m_notes.emplace_back(n); + res.back()->m_notes.emplace_back(c); } else { // Create new notes - std::vector v = {}; + std::vector v = {}; unsigned int da = m->max_length() - m_len; - Note a{n}; + + Chord a{c}; a.setDuration(da); v.emplace_back(a); - unsigned int db = n.getDuration() - da; + unsigned int db = c.getDuration() - da; while(db > m->max_length()) { - Note b{n}; + Chord b{c}; b.setDuration(m->max_length()); v.emplace_back(b); db -= m->max_length(); } - Note b{n}; + Chord b{c}; b.setDuration(db); v.emplace_back(b); @@ -103,17 +106,25 @@ namespace autoplay { } MeasureList Measure::operator+(const Measure& rhs) { - for(const Note& n : rhs.m_notes) { - *this += n; + for(const Chord& c : rhs.m_notes) { + *this += c; } return this->measurize(); } Measure* Measure::operator+(const Note& rhs) { + Chord chord{rhs}; + this->m_notes.emplace_back(chord); + return this; + } + + Measure* Measure::operator+(const Chord& rhs) { this->m_notes.emplace_back(rhs); return this; } void Measure::operator+=(const Note& rhs) { *this + rhs; } + + void Measure::operator+=(const Chord& rhs) { *this + rhs; } } } \ No newline at end of file diff --git a/main/music/Measure.h b/main/music/Measure.h index beaaf3f..f0a129f 100644 --- a/main/music/Measure.h +++ b/main/music/Measure.h @@ -166,6 +166,19 @@ namespace autoplay { */ void operator+=(const Note& rhs); + /** + * Add a Chord to this Measure + * @param rhs The Chord to add + * @return This Measure, after the note was added + */ + Measure* operator+(const Chord& rhs); + + /** + * Add a Chord respectively to this Measure + * @param rhs The Chord to add + */ + void operator+=(const Chord& rhs); + /** * Append a Note to the Measure. Alias of operator+ * @param n The Note to append. @@ -173,22 +186,28 @@ namespace autoplay { inline void append(const Note& n) { *this += n; } /** - * Get the last Note of the Measure by reference + * Append a Chord to the Measure. Alias of operator+ + * @param n The Chord to append. + */ + inline void append(const Chord& n) { *this += n; } + + /** + * Get the last Chord of the Measure by reference * @return The back of the m_notes vector */ - inline Note& back() { return m_notes.back(); } + inline Chord& back() { return m_notes.back(); } /** - * Fetches all Notes from this Measure + * Fetches all Chords from this Measure * @return A vector, containing all Notes. */ - inline std::vector getNotes() const { return m_notes; } + inline std::vector getNotes() const { return m_notes; } /** - * Fetches all Notes from this Measure by reference + * Fetches all Chords from this Measure by reference * @return A vector, containing all Notes. */ - inline std::vector& getNotes() { return m_notes; } + inline std::vector& getNotes() { return m_notes; } /** * Sets the BPM. It uses the time as reference; @@ -208,7 +227,7 @@ namespace autoplay { Clef m_clef; ///< The clef of the current measure std::pair m_time; ///< The time of the measure, in the form of - std::vector m_notes; ///< The Notes of this Measure + std::vector m_notes; ///< The Notes of this Measure int m_divisions; ///< The divisions value (amount of duration per quarter Note) for this Measure int m_bpm; ///< The amount of beats per minute (expressed wrt the time signature) diff --git a/main/music/Note.cpp b/main/music/Note.cpp index ee89019..efb91a4 100644 --- a/main/music/Note.cpp +++ b/main/music/Note.cpp @@ -293,15 +293,46 @@ namespace autoplay { return (fill && !boost::algorithm::ends_with(m_head, "-empty")); } - std::vector Note::splitByDivisions(const int& divisions, bool generatedots) const { - std::vector ref; + std::string Note::getType(const int& divisions) const { + float dur = (std::pow(2.0f, std::floor(std::log2f(m_duration))) / (float)divisions) / 4.0f; + auto nearby = [](float val, float ref) -> bool { return val < ref + 0.0000001 && val > ref - 0.0000001; }; + if(nearby(dur, 1.0f / 256.0f)) { + return "256th"; + } else if(nearby(dur, 1.0f / 128.0f)) { + return "128th"; + } else if(nearby(dur, 1.0f / 64.0f)) { + return "64th"; + } else if(nearby(dur, 1.0f / 32.0f)) { + return "32nd"; + } else if(nearby(dur, 1.0f / 16.0f)) { + return "16th"; + } else if(nearby(dur, 1.0f / 8.0f)) { + return "eighth"; + } else if(nearby(dur, 1.0f / 4.0f)) { + return "quarter"; + } else if(nearby(dur, 1.0f / 2.0f)) { + return "half"; + } else if(nearby(dur, 1.0f)) { + return "whole"; + } else if(nearby(dur, 2.0f)) { + return "breve"; + } else if(nearby(dur, 4.0f)) { + return "long"; + } + throw std::runtime_error("Division value (" + std::to_string((int)divisions) + + ") gave an unknown duration of " + std::to_string(dur) + ", which came from " + + std::to_string(m_duration) + "."); + } + + std::vector Chord::splitByDivisions(const int& divisions, bool generatedots) const { + std::vector ref; - if(m_duration == 0) { - throw std::runtime_error("Duration of Note is 0!"); + if(getDuration() == 0) { + throw std::runtime_error("Duration of Chord is 0!"); } float rem = 0.0f; - float T = (float)m_duration / (4.0f * divisions); + float T = (float)getDuration() / (4.0f * divisions); int n = 1; auto nearby = [](float val, float ref) -> bool { return val < ref + 0.00000001 && val > ref - 0.00000001; }; @@ -312,11 +343,11 @@ namespace autoplay { float _n = std::log2(T) + 8; n = (int)std::floor(_n); rem = (float)std::pow(2.0f, n - 8); - Note note{*this}; - note.setDuration((unsigned int)(rem * divisions * 4)); - note.setTieStart(); - note.setTieEnd(); - ref.emplace_back(note); + Chord chord{*this}; + chord.setDuration((unsigned int)(rem * divisions * 4)); + chord.setTieStart(); + chord.setTieEnd(); + ref.emplace_back(chord); if(nearby(_n - n, 0.0f)) { break; @@ -350,35 +381,17 @@ namespace autoplay { return ref; } - std::string Note::getType(const int& divisions) const { - float dur = (std::pow(2.0f, std::floor(std::log2f(m_duration))) / (float)divisions) / 4.0f; - auto nearby = [](float val, float ref) -> bool { return val < ref + 0.0000001 && val > ref - 0.0000001; }; - if(nearby(dur, 1.0f / 256.0f)) { - return "256th"; - } else if(nearby(dur, 1.0f / 128.0f)) { - return "128th"; - } else if(nearby(dur, 1.0f / 64.0f)) { - return "64th"; - } else if(nearby(dur, 1.0f / 32.0f)) { - return "32nd"; - } else if(nearby(dur, 1.0f / 16.0f)) { - return "16th"; - } else if(nearby(dur, 1.0f / 8.0f)) { - return "eighth"; - } else if(nearby(dur, 1.0f / 4.0f)) { - return "quarter"; - } else if(nearby(dur, 1.0f / 2.0f)) { - return "half"; - } else if(nearby(dur, 1.0f)) { - return "whole"; - } else if(nearby(dur, 2.0f)) { - return "breve"; - } else if(nearby(dur, 4.0f)) { - return "long"; + std::shared_ptr Chord::bottom() const { + if(empty()) { + return nullptr; } - throw std::runtime_error("Division value (" + std::to_string((int)divisions) + - ") gave an unknown duration of " + std::to_string(dur) + ", which came from " + - std::to_string(m_duration) + "."); + unsigned int idx = 0; + for(unsigned int i = 0; i < m_notes.size(); ++i) { + if(m_notes.at(i)->getPitch() < m_notes.at(idx)->getPitch()) { + idx = i; + } + } + return m_notes.at(idx); } } } \ No newline at end of file diff --git a/main/music/Note.h b/main/music/Note.h index f852b07..8e0fb2c 100644 --- a/main/music/Note.h +++ b/main/music/Note.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -404,15 +405,117 @@ namespace autoplay { bool operator>(const Note& rhs) const; public: + std::string getType(const int& divisions) const; + }; + + /** + * The Chord class is a class used to define a group of Notes that are played similtaneously. + */ + class Chord + { + private: + std::vector> m_notes; + + public: + explicit Chord(const Note& note) : m_notes() { m_notes.emplace_back(std::make_shared(note)); } + + Chord(const Chord& rhs) { + for(const std::shared_ptr& note : rhs.m_notes) { + Note n = *note.get(); + m_notes.emplace_back(std::make_shared(n)); + } + } + + inline std::vector> getNotes() const { return m_notes; } + /** - * Split the Note in a series of linked notes, w.r.t. the divisions + * Sets the Chord to have a specific duration. + * @param d The new duration of all Notes in the Chord + */ + inline void setDuration(const unsigned int& d) { + for(const std::shared_ptr& note : m_notes) { + note->setDuration(d); + } + } + + /** + * Ties a Note to a following Note + * @param enable If this must be enabled. + */ + inline void setTieStart(bool enable = true) { + for(const std::shared_ptr& note : m_notes) { + note->setTieStart(enable); + } + } + + /** + * Ties a Note to a preceding Note + * @param enable If this must be enabled. + */ + inline void setTieEnd(bool enable = true) { + for(const std::shared_ptr& note : m_notes) { + note->setTieEnd(enable); + } + } + + /** + * Get if the current Chord has a Note that is tied to a following Note + * @return true if so + */ + inline bool getTieStart() const { return m_notes.begin()->get()->getTieStart(); } + + /** + * Get if the current Chord has a Note that is tied to a preceding Note + * @return true if so + */ + inline bool getTieEnd() const { return m_notes.begin()->get()->getTieEnd(); } + + /** + * Returns the duration of the Chord. + * @return The duration of the Chord. + */ + inline unsigned int getDuration() const { return m_notes.begin()->get()->getDuration(); } + + /** + * Sets the amount of dots to use + * @param dots New amount of dots. + */ + inline void setDots(const uint8_t& dots) { + for(const std::shared_ptr& note : m_notes) { + note->setDots(dots); + } + } + + /** + * Checks if the Chord is empty + * @return true if it is empty + */ + inline bool empty() const { return m_notes.empty(); } + + /** + * Changes the Chord to a Pause + */ + inline void toPause() { m_notes.begin()->get()->toPause(); } + + /** + * Checks if the current Chord is a Pause + * @return true if so + */ + inline bool isPause() const { return m_notes.begin()->get()->isPause(); } + + /** + * Finds the lowest Note of the Chord + * @return a shared pointer, pointing to the bottom note (root Note) of the Chord + */ + std::shared_ptr bottom() const; + + /** + * Split the Chord in a series of linked notes, w.r.t. the divisions * @param divisions The amount of divisions to take into a count. * @param generatedots When true, dots must be used whenever possible. * @return A vector of linked Notes */ - std::vector splitByDivisions(const int& divisions, bool generatedots) const; - - std::string getType(const int& divisions) const; + std::vector splitByDivisions(const int& divisions, bool generatedots) const; }; } } diff --git a/main/music/Part.h b/main/music/Part.h index e94fb79..901da58 100644 --- a/main/music/Part.h +++ b/main/music/Part.h @@ -115,12 +115,12 @@ namespace autoplay { inline uint8_t getLines() const { return m_lines; } /** - * Compute the note that is playing at a certain time + * Compute the Chord that is playing at a certain time * @param timestamp The time to look - * @return A pointer to the Note that is playing, or nullptr if no Note has + * @return A pointer to the Chord that is playing, or nullptr if no Chord has * been found. */ - Note* at(unsigned int timestamp) const { + Chord* at(unsigned int timestamp) const { unsigned int c = 0; unsigned int mi = 0; auto m = m_measures.at(mi); @@ -143,12 +143,12 @@ namespace autoplay { return nullptr; } - // Find the right Note - for(Note& note : m_measures.at(mi)->getNotes()) { - if(c + note.getDuration() < timestamp) { - c += note.getDuration(); + // Find the right Chord + for(Chord& chord : m_measures.at(mi)->getNotes()) { + if(c + chord.getDuration() < timestamp) { + c += chord.getDuration(); } else { - return ¬e; + return &chord; } } @@ -156,11 +156,11 @@ namespace autoplay { } /** - * Get the nth Note of the Part - * @param n The index of the Note - * @return A pointer to the Note at index n. + * Get the nth Chord of the Part + * @param n The index of the Chord + * @return A pointer to the Chord at index n. */ - Note* noteAt(unsigned int n) const { + Chord* noteAt(unsigned int n) const { if(m_measures.empty()) { return nullptr; } @@ -181,8 +181,8 @@ namespace autoplay { } /** - * Get the nth Note of the Part and change it to a pause/rest - * @param n The index of the Note + * Get the nth Chord of the Part and change it to a pause/rest + * @param n The index of the Chord * @return true if succesful */ bool toPause(unsigned int n) const { diff --git a/main/util/FileHandler.cpp b/main/util/FileHandler.cpp index 9559591..ab9d976 100644 --- a/main/util/FileHandler.cpp +++ b/main/util/FileHandler.cpp @@ -184,72 +184,84 @@ namespace autoplay { measure_tree.put(".number", measure_idx); // Add Notes - for(const auto& note_ : measure->getNotes()) { - auto vec = note_.splitByDivisions(prev->getDivisions(), true); - for(const auto& note : vec) { - pt::ptree note_tree; - pt::ptree notation_tree; - - music::Note::Semitone s; - - auto repr = music::Note::splitPitch(music::Note::pitchRepr(note.getPitch()), s); - - if(note.isPause()) { + for(const auto& chord_ : measure->getNotes()) { + auto vec = chord_.splitByDivisions(prev->getDivisions(), true); + for(const auto& chord : vec) { + bool c_ = false; + if(chord.isPause()) { + pt::ptree note_tree; note_tree.put("rest", ""); - note_tree.put("duration", note.getDuration()); + note_tree.put("duration", chord.getDuration()); measure_tree.add_child("note", note_tree); continue; } - if(measure->getClef().isPercussion()) { - note_tree.put("unpitched.display-step", repr.first); - note_tree.put("unpitched.display-octave", repr.second); - } else { - note_tree.put("pitch.step", repr.first); - if(s == music::Note::Semitone::SHARP) { - note_tree.put("pitch.alter", 1); - } else if(s == music::Note::Semitone::FLAT) { - note_tree.put("pitch.alter", -1); + for(const auto& note : chord.getNotes()) { + pt::ptree note_tree; + pt::ptree notation_tree; + + music::Note::Semitone s; + + auto repr = music::Note::splitPitch(music::Note::pitchRepr(note->getPitch()), s); + + if(!c_) { + c_ = true; + } else { + note_tree.put("chord", ""); } - note_tree.put("pitch.octave", repr.second); - } - note_tree.put("duration", note.getDuration()); - if(note.getInstrument() != nullptr) { - note_tree.put("instrument..id", instr_ids.at(note.getInstrument()->getName())); - } + if(measure->getClef().isPercussion()) { + note_tree.put("unpitched.display-step", repr.first); + note_tree.put("unpitched.display-octave", repr.second); + } else { + note_tree.put("pitch.step", repr.first); + if(s == music::Note::Semitone::SHARP) { + note_tree.put("pitch.alter", 1); + } else if(s == music::Note::Semitone::FLAT) { + note_tree.put("pitch.alter", -1); + } + note_tree.put("pitch.octave", repr.second); + } + note_tree.put("duration", note->getDuration()); - if(note.getTieEnd()) { - pt::ptree tmp; - tmp.put(".type", "stop"); - note_tree.add_child("tie", tmp); - notation_tree.add_child("tied", tmp); - } + if(note->getInstrument() != nullptr) { + note_tree.put("instrument..id", + instr_ids.at(note->getInstrument()->getName())); + } - if(note.getTieStart()) { - pt::ptree tmp; - tmp.put(".type", "start"); - note_tree.add_child("tie", tmp); - notation_tree.add_child("tied", tmp); - } + if(note->getTieEnd()) { + pt::ptree tmp; + tmp.put(".type", "stop"); + note_tree.add_child("tie", tmp); + notation_tree.add_child("tied", tmp); + } - note_tree.put("voice", 1); - note_tree.put("type", note.getType(prev->getDivisions())); + if(note->getTieStart()) { + pt::ptree tmp; + tmp.put(".type", "start"); + note_tree.add_child("tie", tmp); + notation_tree.add_child("tied", tmp); + } - for(uint8_t i = 0; i < note.getDots(); ++i) { - note_tree.add("dot", ""); - } + note_tree.put("voice", 1); + note_tree.put("type", note->getType(prev->getDivisions())); - if(!note.getHeadName().empty()) { - note_tree.put("notehead", note.getHeadName()); - if(note.canBeFilled()) { - note_tree.put("notehead..filled", note.getHeadFilled() ? "yes" : "no"); + for(uint8_t i = 0; i < note->getDots(); ++i) { + note_tree.add("dot", ""); + } + + if(!note->getHeadName().empty()) { + note_tree.put("notehead", note->getHeadName()); + if(note->canBeFilled()) { + note_tree.put("notehead..filled", + note->getHeadFilled() ? "yes" : "no"); + } } - } - note_tree.put_child("notations", notation_tree); + note_tree.put_child("notations", notation_tree); - measure_tree.add_child("note", note_tree); + measure_tree.add_child("note", note_tree); + } } } diff --git a/main/util/Generator.cpp b/main/util/Generator.cpp index 376a92f..1baed2f 100644 --- a/main/util/Generator.cpp +++ b/main/util/Generator.cpp @@ -106,11 +106,11 @@ namespace autoplay { part->setInstrumentName(pt_part.get("name", "")); - std::shared_ptr prev; + std::shared_ptr prev; for(unsigned int j = 0; j < length * measure.max_length();) { - std::vector conc = {}; + std::vector conc = {}; for(unsigned int p = 0; p < i; ++p) { - music::Note* n = score.getParts().at(p)->at(j); + music::Chord* n = score.getParts().at(p)->at(j); if(n) { conc.emplace_back(n); } @@ -131,7 +131,7 @@ namespace autoplay { note.setInstrument(repr_to_inst.at(prepr)); } - prev = std::make_shared(note); + prev = std::make_shared(note); measure.append(note); j += duration; } @@ -143,7 +143,7 @@ namespace autoplay { if(!percussion && picked <= m_config.conf("style.chance")) { music::Note::Semitone s; - auto c = part->back()->back().getPitch(); + auto c = part->back()->back().bottom()->getPitch(); auto r = music::Note::pitchRepr(c); auto p = music::Note::splitPitch(r, s); @@ -158,13 +158,13 @@ namespace autoplay { auto ap2 = std::abs(c - p2); auto ap3 = std::abs(c - p3); if(ap1 < ap2 && ap1 < ap3) { - part->back()->back().setPitch(p1); + part->back()->back().bottom()->setPitch(p1); } else if(ap2 < ap1 && ap2 < ap3) { - part->back()->back().setPitch(p2); + part->back()->back().bottom()->setPitch(p2); } else if(ap3 < ap2 && ap3 < ap1) { - part->back()->back().setPitch(p3); + part->back()->back().bottom()->setPitch(p3); } else { - part->back()->back().setPitch(p1); + part->back()->back().bottom()->setPitch(p1); } } @@ -192,7 +192,7 @@ namespace autoplay { return score; } - std::function& conc, pt::ptree& pt)> + std::function& conc, pt::ptree& pt)> Generator::getPitchAlgorithm(std::string algo) const { // Get algorithm variables if(algo.empty()) { @@ -202,13 +202,13 @@ namespace autoplay { m_config.getLogger()->debug("Using Pitch Algorithm '{}'", algo); if(algo == "random-piano") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { auto stave = pt.get("stave"); return (uint8_t)Randomizer::pick_uniform(gen, getPitches(21, 108, stave)); }; } else if(algo == "contain-stave") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { auto stave = pt.get("stave"); auto range = staveRange(stave); @@ -216,17 +216,17 @@ namespace autoplay { return (uint8_t)Randomizer::pick_uniform(gen, getPitches(range.first, range.second, stave)); }; } else if(algo == "brownian-motion") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { return pitchBrownianMotion(gen, prev, conc, pt); }; } else if(algo == "1/f-noise") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { auto res = pitch1FNoise(gen, pt); pt.put("_p1fn._reinit", false); return res; }; } else if(algo == "centralized") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { auto stave = pt.get("stave"); auto range = staveRange(stave); @@ -239,7 +239,7 @@ namespace autoplay { return (uint8_t)Randomizer::gaussian(gen, p2); }; } /*else if(algo == "gaussian-voicing") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { auto stave = pt.get("stave"); auto range = staveRange(stave); @@ -252,7 +252,7 @@ namespace autoplay { return (uint8_t)Randomizer::gaussian(gen, p2, -3.0f, 3.0f, true); }; }*/ else { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> uint8_t { auto stave = pt.get("stave"); return (uint8_t)Randomizer::pick_uniform(gen, getPitches(0, 128, stave)); @@ -260,7 +260,7 @@ namespace autoplay { } } - std::function& conc, pt::ptree& pt)> + std::function& conc, pt::ptree& pt)> Generator::getRhythmAlgorithm(std::string algo) const { // Get algorithm variables if(algo.empty()) { @@ -270,7 +270,7 @@ namespace autoplay { m_config.getLogger()->debug("Using Rhythm Algorithm '{}'", algo); if(algo == "random") { - return [](RNEngine& gen, music::Note* prev, std::vector& conc, pt::ptree& pt) -> float { + return [](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> float { auto smallest = (int)std::log2(music::Note::DURATION.at(pt.get("rhythm.smallest", "256th"))); auto largest = @@ -281,10 +281,10 @@ namespace autoplay { return rh; }; } else if(algo == "brownian-motion") { - return [this](RNEngine& gen, music::Note* prev, std::vector& conc, + return [this](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> float { return rhythmBrownianMotion(gen, prev, conc, pt); }; } else { - return [](RNEngine& gen, music::Note* prev, std::vector& conc, pt::ptree& pt) -> float { + return [](RNEngine& gen, music::Chord* prev, std::vector& conc, pt::ptree& pt) -> float { auto rh = pt.get("rhythm.duration", "quarter"); try { return music::Note::DURATION.at(rh); @@ -366,17 +366,20 @@ namespace autoplay { return clef.range(); } - uint8_t Generator::pitchBrownianMotion(autoplay::util::RNEngine& gen, autoplay::music::Note* prev, - std::vector& conc, const pt::ptree& pt) const { + uint8_t Generator::pitchBrownianMotion(autoplay::util::RNEngine& gen, autoplay::music::Chord* prev, + std::vector& conc, const pt::ptree& pt) const { auto stave = pt.get("stave", 0); auto range = staveRange(stave); auto pitches = getPitches(range.first, range.second, stave); if(prev) { - auto it = std::find(pitches.begin(), pitches.end(), prev->getPitch()); + uint8_t pitch = prev->getNotes() + .at((unsigned long)Randomizer::pick_uniform(gen, 0, (int)prev->getNotes().size())) + ->getPitch(); + auto it = std::find(pitches.begin(), pitches.end(), pitch); if(it == pitches.end()) { - throw std::invalid_argument("Note '" + std::to_string(prev->getPitch()) + "' not in scale!"); + throw std::invalid_argument("Note '" + std::to_string(pitch) + "' not in scale!"); } auto idx = std::distance(pitches.begin(), it); auto min = pt.get("pitch.min", -3); @@ -445,8 +448,8 @@ namespace autoplay { return pitches.at(sum); } - float Generator::rhythmBrownianMotion(autoplay::util::RNEngine& gen, autoplay::music::Note* prev, - std::vector& conc, const pt::ptree& pt) const { + float Generator::rhythmBrownianMotion(autoplay::util::RNEngine& gen, autoplay::music::Chord* prev, + std::vector& conc, const pt::ptree& pt) const { auto divisions = pt.get("rhythm._divs"); auto smallest = (int)std::log2(music::Note::DURATION.at(pt.get("rhythm.smallest", "256th"))); auto largest = (int)std::log2(music::Note::DURATION.at(pt.get("rhythm.largest", "long"))); diff --git a/main/util/Generator.h b/main/util/Generator.h index 6f5420c..9f57199 100644 --- a/main/util/Generator.h +++ b/main/util/Generator.h @@ -35,7 +35,7 @@ namespace autoplay { * @param algo If not empty, it will use this algorithm to check, instead of the generation.pitch value * @return A lambda function that implements the algorithm */ - std::function& conc, pt::ptree& pt)> + std::function& conc, pt::ptree& pt)> getPitchAlgorithm(std::string algo = "") const; /** @@ -43,7 +43,7 @@ namespace autoplay { * @param algo If not empty, it will use this algorithm to check, instead of the generation.rhythm value * @return A lambda function that implements the algorithm */ - std::function& conc, pt::ptree& pt)> + std::function& conc, pt::ptree& pt)> getRhythmAlgorithm(std::string algo = "") const; private: @@ -75,7 +75,7 @@ namespace autoplay { * index. * @return A new pitch. */ - uint8_t pitchBrownianMotion(RNEngine& gen, music::Note* prev, std::vector& conc, + uint8_t pitchBrownianMotion(RNEngine& gen, music::Chord* prev, std::vector& conc, const pt::ptree& pt) const; /** @@ -99,7 +99,7 @@ namespace autoplay { * index. * @return A new rhythm. */ - float rhythmBrownianMotion(RNEngine& gen, music::Note* prev, std::vector& conc, + float rhythmBrownianMotion(RNEngine& gen, music::Chord* prev, std::vector& conc, const pt::ptree& pt) const; private: diff --git a/test/music/PartTest.cpp b/test/music/PartTest.cpp index 46dbdc6..92a0d9a 100644 --- a/test/music/PartTest.cpp +++ b/test/music/PartTest.cpp @@ -51,5 +51,5 @@ TEST(PartStandard, PartSetters) { EXPECT_EQ(p.getMeasures().size(), 2); - EXPECT_EQ(p.at(36)->getPitch(), 13); + EXPECT_EQ(p.at(36)->bottom()->getPitch(), 13); } \ No newline at end of file