From 958dc2691907b9768753fe1ea22c9b7e20915a84 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 10 Jul 2023 10:46:03 -0700 Subject: [PATCH 001/383] Change variable to camelCase. --- src/iohumdrum.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 60248a0897a..86ce92f6b0f 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -7723,10 +7723,10 @@ void HumdrumInput::fillStaffInfo(hum::HTp staffstart, int staffnumber, int staff // dynamics position to centered if there is a slash in the *staff1/2 string. // In the future also check *part# to see if there are two staves for a part // with no **dynam for the lower staff (infer to be a grand staff). - hum::HTp dynamspine = getAssociatedDynamSpine(stafftok); - if (dynamspine != NULL) { - if (dynamspine->compare(0, 6, "*staff") == 0) { - if (dynamspine->find('/') != std::string::npos) { + hum::HTp dynamSpine = getAssociatedDynamSpine(stafftok); + if (dynamSpine != NULL) { + if (dynamSpine->compare(0, 6, "*staff") == 0) { + if (dynamSpine->find('/') != std::string::npos) { // the dynamics should be placed between // staves: the current one and the one below it. ss.at(staffindex).m_dynampos = 0; @@ -7737,14 +7737,14 @@ void HumdrumInput::fillStaffInfo(hum::HTp staffstart, int staffnumber, int staff } } if (parttok) { - hum::HTp dynamspine = getAssociatedDynamSpine(parttok); + hum::HTp dynamSpine = getAssociatedDynamSpine(parttok); int partnum = 0; int dpartnum = 0; int lpartnum = 0; hum::HumRegex hre; - if (dynamspine) { - if (hre.search(dynamspine, "^\\*part(\\d+)")) { + if (dynamSpine) { + if (hre.search(dynamSpine, "^\\*part(\\d+)")) { dpartnum = hre.getMatchInt(1); } } From 95bff2b0fe4a7f840e2bfe3f0ea65f1f9e481f85 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 2 Jan 2024 22:47:46 -0800 Subject: [PATCH 002/383] Update humlib. --- include/hum/humlib.h | 9 +- src/hum/humlib.cpp | 210 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 181 insertions(+), 38 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 5223b1a9250..ea27437f1ab 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Dec 12 11:01:04 PST 2023 +// Last Modified: Tue Jan 2 22:35:54 PST 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -2499,7 +2499,9 @@ class HumdrumFileContent : public HumdrumFileStructure { bool analyzePhrasings (void); bool analyzeTextRepetition (void); bool analyzeKernTies (void); - bool analyzeKernAccidentals (void); + bool analyzeAccidentals (void); + bool analyzeKernAccidentals (const std::string& dataType = "**kern"); + bool analyzeMensAccidentals (void); bool analyzeRScale (void); // in HumdrumFileContent-rest.cpp @@ -10628,9 +10630,10 @@ class Tool_tspos : public HumTool { bool hasFullTriadAttack(HumdrumLine& line); void avoidRdfCollisions(HumdrumFile& infile); void printUsedMarkers(void); - std::string makeOpacityColor(std::string& color, double value, double total); + std::string makeOpacityColor(std::string& color, double value, double total, bool enhance = false); int getToolCounter(HumdrumFile& infile); std::string makePercentString(double value, double total, int digits); + int logisticColorMap(double input, double max); private: std::string m_root_marker = "@"; diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 74d086f6ac6..e3799acf9b9 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Dec 12 11:01:04 PST 2023 +// Last Modified: Tue Jan 2 22:35:54 PST 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -22598,6 +22598,31 @@ void HumdrumFileBase::clearTokenLinkInfo(void) { +////////////////////////////// +// +// HumdrumFileContent::analyzeAccidentals -- Analyze kern and mens accidentals. +// + +bool HumdrumFileContent::analyzeAccidentals(void) { + bool status = true; + status &= analyzeKernAccidentals(); + status &= analyzeMensAccidentals(); + return status; +} + + + +////////////////////////////// +// +// HumdrumFileContent::analyzeMensAccidentals -- Analyze kern and mens accidentals. +// + +bool HumdrumFileContent::analyzeMensAccidentals(void) { + return analyzeKernAccidentals("**mens"); +} + + + ////////////////////////////// // // HumdrumFileContent::analyzeKernAccidentals -- Identify accidentals that @@ -22608,7 +22633,7 @@ void HumdrumFileBase::clearTokenLinkInfo(void) { // about grace-note accidental display still needs to be done. // -bool HumdrumFileContent::analyzeKernAccidentals(void) { +bool HumdrumFileContent::analyzeKernAccidentals(const string& dataType) { // ottava marks must be analyzed first: this->analyzeOttavas(); @@ -22620,7 +22645,17 @@ bool HumdrumFileContent::analyzeKernAccidentals(void) { // ktracks == List of **kern spines in data. // rtracks == Reverse mapping from track to ktrack index (part/staff index). - vector ktracks = getKernSpineStartList(); + vector ktracks; + if ((dataType == "**kern") || dataType.empty()) { + ktracks = getKernSpineStartList(); + } else if (dataType == "**mens") { + getSpineStartList(ktracks, "**mens"); + } else { + getSpineStartList(ktracks, dataType); + } + if (ktracks.empty()) { + return true; + } vector rtracks(getMaxTrack()+1, -1); for (i=0; i<(int)ktracks.size(); i++) { track = ktracks[i]->getTrack(); @@ -23009,10 +23044,10 @@ bool HumdrumFileContent::analyzeKernAccidentals(void) { if ((loc != string::npos) && (loc > 0)) { if (subtok[loc-1] == '#') { token->setValue("auto", to_string(k), "cautionaryAccidental", "true"); - token->setValue("auto", to_string(k), "visualAccidental", "true"); + token->setValue("auto", to_string(k), "visualAccidental", "true"); } else if (subtok[loc-1] == '-') { token->setValue("auto", to_string(k), "cautionaryAccidental", "true"); - token->setValue("auto", to_string(k), "visualAccidental", "true"); + token->setValue("auto", to_string(k), "visualAccidental", "true"); } else if (subtok[loc-1] == 'n') { token->setValue("auto", to_string(k), "cautionaryAccidental", "true"); token->setValue("auto", to_string(k), "visualAccidental", "true"); @@ -23025,7 +23060,8 @@ bool HumdrumFileContent::analyzeKernAccidentals(void) { } // Indicate that the accidental analysis has been done: - infile.setValue("auto", "accidentalAnalysis", "true"); + string dataTypeDone = "accidentalAnalysis" + dataType; + infile.setValue("auto", dataTypeDone, "true"); return true; } @@ -23039,23 +23075,26 @@ bool HumdrumFileContent::analyzeKernAccidentals(void) { // only by HumdrumFileContent::analyzeKernAccidentals(). // -void HumdrumFileContent::fillKeySignature(vector& states, - const string& keysig) { +void HumdrumFileContent::fillKeySignature(vector& states, const string& keysig) { + if (states.size() < 7) { + cerr << "In HumdrumFileContent::fillKeySignature, states is too small: " << states.size() << endl; + return; + } std::fill(states.begin(), states.end(), 0); - if (keysig.find("f#") != string::npos) { states[3] = +1; } - if (keysig.find("c#") != string::npos) { states[0] = +1; } - if (keysig.find("g#") != string::npos) { states[4] = +1; } - if (keysig.find("d#") != string::npos) { states[1] = +1; } - if (keysig.find("a#") != string::npos) { states[5] = +1; } - if (keysig.find("e#") != string::npos) { states[2] = +1; } - if (keysig.find("b#") != string::npos) { states[6] = +1; } - if (keysig.find("b-") != string::npos) { states[6] = -1; } - if (keysig.find("e-") != string::npos) { states[2] = -1; } - if (keysig.find("a-") != string::npos) { states[5] = -1; } - if (keysig.find("d-") != string::npos) { states[1] = -1; } - if (keysig.find("g-") != string::npos) { states[4] = -1; } - if (keysig.find("c-") != string::npos) { states[0] = -1; } - if (keysig.find("f-") != string::npos) { states[3] = -1; } + if (keysig.find("f#") != string::npos) { states.at(3) = +1; } + if (keysig.find("c#") != string::npos) { states.at(0) = +1; } + if (keysig.find("g#") != string::npos) { states.at(4) = +1; } + if (keysig.find("d#") != string::npos) { states.at(1) = +1; } + if (keysig.find("a#") != string::npos) { states.at(5) = +1; } + if (keysig.find("e#") != string::npos) { states.at(2) = +1; } + if (keysig.find("b#") != string::npos) { states.at(6) = +1; } + if (keysig.find("b-") != string::npos) { states.at(6) = -1; } + if (keysig.find("e-") != string::npos) { states.at(2) = -1; } + if (keysig.find("a-") != string::npos) { states.at(5) = -1; } + if (keysig.find("d-") != string::npos) { states.at(1) = -1; } + if (keysig.find("g-") != string::npos) { states.at(4) = -1; } + if (keysig.find("c-") != string::npos) { states.at(0) = -1; } + if (keysig.find("f-") != string::npos) { states.at(3) = -1; } } @@ -34048,10 +34087,19 @@ int HumdrumToken::hasVisibleAccidental(int subtokenIndex) const { if (humfile == NULL) { return -1; } - if (!humfile->getValueBool("auto", "accidentalAnalysis")) { - int status = humfile->analyzeKernAccidentals(); - if (!status) { - return -1; + if (isKern()) { + if (!humfile->getValueBool("auto", "accidentalAnalysis**kern")) { + int status = humfile->analyzeKernAccidentals(); + if (!status) { + return -1; + } + } + } else if (isMens()) { + if (!humfile->getValueBool("auto", "accidentalAnalysis**mens")) { + int status = humfile->analyzeMensAccidentals(); + if (!status) { + return -1; + } } } return getValueBool("auto", to_string(subtokenIndex), "visualAccidental"); @@ -34078,10 +34126,19 @@ int HumdrumToken::hasCautionaryAccidental(int subtokenIndex) const { if (humfile == NULL) { return -1; } - if (!humfile->getValueBool("auto", "accidentalAnalysis")) { - int status = humfile->analyzeKernAccidentals(); - if (!status) { - return -1; + if (isKern()) { + if (!humfile->getValueBool("auto", "accidentalAnalysis**kern")) { + int status = humfile->analyzeKernAccidentals(); + if (!status) { + return -1; + } + } + } else if (isMens()) { + if (!humfile->getValueBool("auto", "accidentalAnalysis**mens")) { + int status = humfile->analyzeMensAccidentals(); + if (!status) { + return -1; + } } } return getValueBool("auto", to_string(subtokenIndex), "cautionaryAccidental"); @@ -105903,12 +105960,62 @@ void Tool_myank::printStarting(HumdrumFile& infile) { if (!m_hideStarting) { m_humdrum_text << infile[i] << "\n"; } else { - if (infile[i].rfind("!!!RDF", 0) == 0) { + if (infile[i].rfind("!!!RDF", 0) == 0 || infile[i].rfind("!!!system-decoration", 0) == 0) { m_humdrum_text << infile[i] << "\n"; } } } + // keep *part interpretations + bool hasPart = false; + for (i=exi+1; icompare(0, 5, "*part") == 0) { + hasPart = true; + break; + } + } + if (hasPart) { + for (j=0; jcompare(0, 5, "*part") == 0) { + m_humdrum_text << infile.token(i, j); + } else { + m_humdrum_text << "*"; + } + if (j < infile[i].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << "\n"; + } + } + + // keep *staff interpretations + bool hasStaff = false; + for (i=exi+1; icompare(0, 6, "*staff") == 0) { + hasStaff = true; + break; + } + } + if (hasStaff) { + for (j=0; jcompare(0, 6, "*staff") == 0) { + m_humdrum_text << infile.token(i, j); + } else { + m_humdrum_text << "*"; + } + if (j < infile[i].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << "\n"; + } + } + int hasI = 0; if (m_instrumentQ) { @@ -105999,7 +106106,7 @@ void Tool_myank::printEnding(HumdrumFile& infile, int lastline, int adjlin) { if (startline >= 0) { for (i=startline; i ending)) { - if (infile[i].rfind("!!!RDF", 0) == 0) { + if (infile[i].rfind("!!!RDF", 0) == 0 || infile[i].rfind("!!!system-decoration", 0) == 0) { m_humdrum_text << infile[i] << "\n"; } } else { @@ -122010,10 +122117,20 @@ string Tool_tspos::generateTable(HumdrumFile& infile, vector& names) { // Tool_tspos::makeOpacityColor -- // -string Tool_tspos::makeOpacityColor(string& color, double value, double total) { +string Tool_tspos::makeOpacityColor(string& color, double value, double total, bool enhance) { stringstream output; - int percent = int(value / total * 255.49 + 0.5); - output << color << std::hex << std::setw(2) << std::setfill('0') << percent << std::dec; + int opacity; + if (enhance) { + opacity = logisticColorMap(value, total); + } else { + opacity = int(value / total * 255.49 + 0.5); + } + if (opacity < 0) { + opacity = 0; + } else if (opacity > 255) { + opacity = 255; + } + output << color << std::hex << std::setw(2) << std::setfill('0') << opacity << std::dec; return output.str(); } @@ -122608,4 +122725,27 @@ int Tool_tspos::getToolCounter(HumdrumFile& infile) { +////////////////////////////// +// +// Tool_tspos::logisticColorMap -- Increase sensitivity of color mapping +// around 40%. +// + +int Tool_tspos::logisticColorMap(double input, double max) { + double center = max * 0.40; + double k = 0.04; + int output = max / (1.0 + pow(M_E, -k * (input + center) - (max + center)/2)); + output -= 11.4209; + output = output * 255.0 / 243.377; + if (output < 0) { + output = 0; + } + if (output > 255) { + output = 255; + } + return output; +} + + + } // end namespace hum From ee2f51d67f38712ad298ee62e1f266c0198c7c2e Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 2 Jan 2024 22:47:52 -0800 Subject: [PATCH 003/383] Implementation for issue https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/868 --- src/iohumdrum.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 6eed5feb39c..6417cc94a9e 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -774,7 +774,7 @@ bool HumdrumInput::convertHumdrum() infile.analyzeKernTies(); // infile.analyzeKernStemLengths(); infile.analyzeRestPositions(); - infile.analyzeKernAccidentals(); + infile.analyzeAccidentals(); infile.analyzeTextRepetition(); parseSignifiers(infile); if (!m_signifiers.kernTerminalLong.empty()) { @@ -25831,7 +25831,6 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta } bool mensit = false; - bool gesturalQ = false; bool hasAccidental = false; int accidlevel = 0; if (m_mens && token->isMensLike()) { @@ -25854,14 +25853,7 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta else if (tstring.find("y") != std::string::npos) { accidlevel = 4; } - if (accidlevel <= ss[staffindex].acclev) { - gesturalQ = false; - } - else { - gesturalQ = true; - } } - Accid *accid = NULL; int accidCount = hum::Convert::base40ToAccidental(base40); @@ -25908,7 +25900,7 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta subelementQ = true; } - if (gesturalQ) { + if (showInAccidGes) { switch (accidCount) { case +2: accid->SetAccidGes(ACCIDENTAL_GESTURAL_ss); break; case +1: accid->SetAccidGes(ACCIDENTAL_GESTURAL_s); break; @@ -26014,7 +26006,6 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta showInAccid = false; } } - if (!editorialQ) { if (showInAccid) { switch (accidCount) { From 7bb9a10b528812237d630ad333d780622549d089 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Fri, 12 Jan 2024 10:58:00 -0800 Subject: [PATCH 004/383] Update humlib. --- src/iohumdrum.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 6417cc94a9e..b22b2b746ce 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -17598,7 +17598,6 @@ void HumdrumInput::processLinkedDirection(int index, hum::HTp token, int staffin } } else { - dir = new Dir(); if (placement == "between") { setStaffBetween(dir, m_currentstaff); @@ -20091,7 +20090,7 @@ hum::HumNum HumdrumInput::getMeasureEndTstamp(int staffindex) ///////////////////////////// // -// HumdrumInput::addSmuflSymbol -- Add a SMuFL symbol to some +// HumdrumInput::addMusicSymbol -- Add a SMuFL symbol to some // text-based element (such as ). Humdrum music symbol names assumed // as input, such as "sc" for segnum contruentiae.. @@ -20174,7 +20173,7 @@ void HumdrumInput::addTextElement( // Parse [ASCII] music codes to route to VerovioText font rends: hum::HumRegex hre; - if (hre.search(data, "^(.*?)(\\[.*?\\])(.*)$")) { + if (hre.search(data, "^(.*?\\[*?)(\\[[^[[].*?\\])(.*)$")) { std::string pretext = hre.getMatch(1); std::string rawmusictext = hre.getMatch(2); std::vector musictext = convertMusicSymbolNameToSmuflName(rawmusictext); @@ -20184,6 +20183,11 @@ void HumdrumInput::addTextElement( element->AddChild(lb); pretext = ""; } + else if (hre.search(pretext, "\\\\n(.*)")) { + Lb *lb = new Lb(); + element->AddChild(lb); + pretext = hre.getMatch(1); + } if (musictext.empty()) { hum::HumRegex hre2; std::string newtext = rawmusictext; From 5d6cf49d4537677ce6eaf820e93a167b60fbef84 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Fri, 12 Jan 2024 10:58:58 -0800 Subject: [PATCH 005/383] merge of some sort. --- include/hum/humlib.h | 2 +- src/hum/humlib.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index ea27437f1ab..1f12490f571 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Jan 2 22:35:54 PST 2024 +// Last Modified: Mon Jan 8 08:56:08 PST 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index e3799acf9b9..da5429be608 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Jan 2 22:35:54 PST 2024 +// Last Modified: Mon Jan 8 08:56:08 PST 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 From 80ec72db26db16978097b86b61526af75a8b560e Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Fri, 19 Jan 2024 22:08:12 -0800 Subject: [PATCH 006/383] Implementation for issue https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/872 --- include/vrv/iohumdrum.h | 1 + src/iohumdrum.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index f5ac61083e2..f9c31a1d954 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -893,6 +893,7 @@ class HumdrumInput : public vrv::Input { int getKeySignatureNumber(const std::string &humkeysig); int getStaffNumForSpine(hum::HTp token); bool checkIfReversedSpineOrder(std::vector &staffstarts); + bool hasOmdText(int startline, int endline); // header related functions: /////////////////////////////////////////// void createHeader(); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index b22b2b746ce..96ed8b17f2f 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -9684,6 +9684,15 @@ void HumdrumInput::checkForOmd(int startline, int endline) } } + bool omdTextQ = hasOmdText(startline, endline); + + if (omdTextQ) { + // Do not print the !!!OMD: reference record since there is an + // alternate !!LO:TX:omd: entry that will be printed (expected + // to be attached to a time signature for now). + return; + } + if (!value.empty()) { Tempo *tempo = new Tempo(); hum::HTp token = infile.token(index, 0); @@ -9714,6 +9723,28 @@ void HumdrumInput::checkForOmd(int startline, int endline) } } +////////////////////////////// +// +// HumdrumInput::hasOmdText -- Check for global layout text that should +// be used instead of any OMD reference record. +// + +bool HumdrumInput::hasOmdText(int startline, int endline) +{ + hum::HumdrumFile &infile = m_infiles[0]; + hum::HumRegex hre; + for (int i = startline; i <= endline; i++) { + if (infile[i].hasSpines()) { + continue; + } + hum::HTp token = infile.token(i, 0); + if (hre.search(token, "^!!LO:TX.*:omd(:|$)")) { + return true; + } + } + return false; +} + ////////////////////////////// // // HumdrumInput::addChildBackMeasureOrSection -- Add to the current measure, or add to section From 55ec0f8222ec4e95a30a59276c992de2f5bf935d Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 22 Jan 2024 22:49:38 -0800 Subject: [PATCH 007/383] Partial implementation for issue https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/869 --- src/iohumdrum.cpp | 50 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 96ed8b17f2f..244f208158a 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -17987,10 +17987,23 @@ bool HumdrumInput::setLabelContent(Label *label, const std::string &name) bool HumdrumInput::setTempoContent(Tempo *tempo, const std::string &text) { + + Rend *rend = NULL; hum::HumRegex hre; + if (hre.search(text, "\\\\n")) { + // Insert text into if an will be added + rend = new Rend(); + tempo->AddChild(rend); + } + if (!hre.search(text, "(.*)\\[([^=\\]]*)\\]\\s*=\\s*(\\d+.*)")) { // no musical characters to unescape - addTextElement(tempo, text); + if (rend) { + addTextElement(rend, text); + } + else { + addTextElement(tempo, text); + } return true; } std::string first = hre.getMatch(1); @@ -18004,7 +18017,12 @@ bool HumdrumInput::setTempoContent(Tempo *tempo, const std::string &text) // to separate parenthesis and notehead: first += " "; } - addTextElement(tempo, first); + if (rend) { + addTextElement(rend, first); + } + else { + addTextElement(tempo, first); + } } // Add the musical symbols, adding a space between them @@ -18018,10 +18036,20 @@ bool HumdrumInput::setTempoContent(Tempo *tempo, const std::string &text) if (counter) { // Add a space element between music symbols. if (name == "metAugmentationDot") { - addTextElement(tempo, m_textAugmentationDotSpacer); + if (rend) { + addTextElement(rend, m_textAugmentationDotSpacer); + } + else { + addTextElement(tempo, m_textAugmentationDotSpacer); + } } else { - addTextElement(tempo, m_textSmuflSpacer); + if (rend) { + addTextElement(rend, m_textSmuflSpacer); + } + else { + addTextElement(tempo, m_textSmuflSpacer); + } } } ++counter; @@ -18029,12 +18057,22 @@ bool HumdrumInput::setTempoContent(Tempo *tempo, const std::string &text) Symbol *symbol = new Symbol(); setSmuflContent(symbol, name); setFontsize(symbol, name, ""); - tempo->AddChild(symbol); + if (rend) { + rend->AddChild(symbol); + } + else { + tempo->AddChild(symbol); + } } // Force spaces around equals sign: third = m_textSmuflSpacer + "=" + m_textSmuflSpacer + third; - addTextElement(tempo, third); + if (rend) { + addTextElement(rend, third); + } + else { + addTextElement(tempo, third); + } return true; } From f8e270ee7a38e45d258ca3e027994c39c428fbdc Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 23 Jan 2024 00:23:27 -0800 Subject: [PATCH 008/383] Fix for issue https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/875 --- src/iohumdrum.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 244f208158a..03e8915f120 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -20242,7 +20242,7 @@ void HumdrumInput::addTextElement( // Parse [ASCII] music codes to route to VerovioText font rends: hum::HumRegex hre; - if (hre.search(data, "^(.*?\\[*?)(\\[[^[[].*?\\])(.*)$")) { + if (hre.search(data, "^(.*?\\[*?)(\\[[^[[][^.]*?\\])(.*)$")) { std::string pretext = hre.getMatch(1); std::string rawmusictext = hre.getMatch(2); std::vector musictext = convertMusicSymbolNameToSmuflName(rawmusictext); From 0b135a7e1f25185ad0cfb67cac5ec13691f1a798 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Thu, 1 Feb 2024 21:44:35 -0800 Subject: [PATCH 009/383] Humlib update --- include/hum/humlib.h | 2 +- src/hum/humlib.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 1f12490f571..aa992215ff4 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Mon Jan 8 08:56:08 PST 2024 +// Last Modified: Tue Jan 23 21:56:32 PST 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index da5429be608..a56fc4a468a 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Mon Jan 8 08:56:08 PST 2024 +// Last Modified: Tue Jan 23 21:56:32 PST 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -85428,7 +85428,7 @@ void Tool_humtr::initialize(void) { } if (getBoolean("popc")) { - addFromToCombined("ſ:s ʃ:s ſ:s ν:u ί:í α:a ť:k ᴣ:z ʓ:z̨ ʒ̇:ż ʒ́:ź Ʒ̇:Ż Ʒ́:Ź æ:ae"); + addFromToCombined("ſ:s ʃ:s ſ:s ν:u ί:í α:a ť:k ᴣ:z ʓ:z̨ ʒ̇:ż ʒ́:ź Ʒ̇:Ż Ʒ́:Ź ӡ:z Ʒ:Z æ:ae"); } } From f77779db4bb2947dab88ca1f4c642dc3237b9d1b Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 29 Dec 2023 21:05:28 +0100 Subject: [PATCH 010/383] Add a neume-line measure types for representing line sections * Special handling of `
` for the neon editor --- include/vrv/doc.h | 14 ++++++++++++++ include/vrv/measure.h | 14 ++++++++++---- include/vrv/vrvdef.h | 8 ++++++++ src/convertfunctor.cpp | 18 ++++++++++++------ src/doc.cpp | 1 + src/iodarms.cpp | 2 +- src/iohumdrum.cpp | 2 +- src/iomei.cpp | 37 ++++++++++++++++++++++++++++++------- src/iopae.cpp | 4 ++-- src/measure.cpp | 6 +++--- src/savefunctor.cpp | 4 ++-- 11 files changed, 84 insertions(+), 26 deletions(-) diff --git a/include/vrv/doc.h b/include/vrv/doc.h index 687f6f068e2..48876eec45e 100644 --- a/include/vrv/doc.h +++ b/include/vrv/doc.h @@ -451,6 +451,14 @@ class Doc : public Object { bool IsMensuralMusicOnly() const { return m_isMensuralMusicOnly; } ///@} + /** + * @name Setter for and getter for neume-line flag + */ + ///@{ + void SetNeumeLines(bool isNeumeLines) { m_isNeumeLines = isNeumeLines; } + bool IsNeumeLines() const { return m_isNeumeLines; } + ///@} + /** * @name Setter and getter for facsimile */ @@ -660,6 +668,12 @@ class Doc : public Object { */ bool m_isMensuralMusicOnly; + /** + * A flag to indicate that the document contains neume lines. + * This is a special document type where neume lines are encoded with
+ */ + bool m_isNeumeLines; + /** Page width (MEI scoredef@page.width) - currently not saved */ int m_pageWidth; /** Page height (MEI scoredef@page.height) - currently not saved */ diff --git a/include/vrv/measure.h b/include/vrv/measure.h index 49ab432a0d7..dcf96d15243 100644 --- a/include/vrv/measure.h +++ b/include/vrv/measure.h @@ -53,7 +53,7 @@ class Measure : public Object, * Reset method resets all attribute classes */ ///@{ - Measure(bool measuredMusic = true, int logMeasureNb = -1); + Measure(MeasureType measuredMusic = MEASURED, int logMeasureNb = -1); virtual ~Measure(); Object *Clone() const override { return new Measure(*this); }; void Reset() override; @@ -79,7 +79,12 @@ class Measure : public Object, /** * Return true if measured music (otherwise we have fake measures) */ - bool IsMeasuredMusic() const { return m_measuredMusic; } + bool IsMeasuredMusic() const { return (m_measureType == MEASURED); } + + /** + * Return true if the measure represents a neume (section) line + */ + bool IsNeumeLine() const { return (m_measureType == NEUMELINE); } /** * Get and set the measure index @@ -404,9 +409,10 @@ class Measure : public Object, private: /** - * Indicates measured music (otherwise we have fake measures) + * Indicate measured music (CMN), unmeasured (fake measures for mensural or neumes) or neume lines + * Neume line measure are created from
*/ - bool m_measuredMusic; + MeasureType m_measureType; /** * The unique measure index diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index eb3644af011..9fe1acdbe03 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -661,6 +661,14 @@ enum SmuflTextFont { SMUFL_NONE = 0, SMUFL_FONT_SELECTED, SMUFL_FONT_FALLBACK }; enum GraphicID { PRIMARY = 0, SPANNING, SYMBOLREF }; +//---------------------------------------------------------------------------- +// Measure type +//---------------------------------------------------------------------------- + +enum MeasureType { MEASURED = 0, UNMEASURED, NEUMELINE }; + +#define NEUME_LINE_TYPE "neon-neume-line" + //---------------------------------------------------------------------------- // Legacy Wolfgang defines //---------------------------------------------------------------------------- diff --git a/src/convertfunctor.cpp b/src/convertfunctor.cpp index 61838252781..d80c5db720f 100644 --- a/src/convertfunctor.cpp +++ b/src/convertfunctor.cpp @@ -187,9 +187,12 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) bool nextIsBarline = (next && next->Is(BARLINE)); // See if we create proper measures and what to do with the barLine - bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); + MeasureType convertToMeasured = UNMEASURED; + if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { + convertToMeasured = MEASURED; + } - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { // barLine object will be deleted m_targetMeasure->SetRight(barLine->GetForm()); } @@ -212,7 +215,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { m_targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { m_targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1 + m_segmentIdx)); } m_targetSubSystem->AddChild(m_targetMeasure); @@ -277,7 +280,10 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) return FUNCTOR_CONTINUE; } - bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); + MeasureType convertToMeasured = UNMEASURED; + if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { + convertToMeasured = MEASURED; + } assert(m_targetSystem); assert(m_layerTree); @@ -289,7 +295,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) // Create the first measure segment - problem: we are dropping the section element - we should create a score-based // MEI file instead Measure *targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1)); } m_targetSubSystem->AddChild(targetMeasure); @@ -375,7 +381,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitSyllable(Syllable *syllable) // Make a segment break // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { - m_targetMeasure = new Measure(false); + m_targetMeasure = new Measure(UNMEASURED); m_targetSubSystem->AddChild(m_targetMeasure); // Add a staff with same attributes as in the previous segment m_targetStaff = new Staff(*m_targetStaff); diff --git a/src/doc.cpp b/src/doc.cpp index 5b13dd476d7..d47b5a0c188 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -132,6 +132,7 @@ void Doc::Reset() m_timemapTempo = 0.0; m_markup = MARKUP_DEFAULT; m_isMensuralMusicOnly = false; + m_isNeumeLines = false; m_isCastOff = false; m_visibleScores.clear(); diff --git a/src/iodarms.cpp b/src/iodarms.cpp index debf3f3181a..0f31bb84223 100644 --- a/src/iodarms.cpp +++ b/src/iodarms.cpp @@ -473,7 +473,7 @@ bool DarmsInput::Import(const std::string &data_str) score->AddChild(section); m_staff = new Staff(1); - m_measure = new Measure(true, 1); + m_measure = new Measure(MEASURED, 1); m_layer = new Layer(); m_layer->SetN(1); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 4353e83816a..5e7423a1e7b 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -29294,7 +29294,7 @@ void HumdrumInput::setupSystemMeasure(int startline, int endline) } if (hasMensuralStaff(&infile[startline])) { - m_measure = new Measure(false); + m_measure = new Measure(UNMEASURED); } else { m_measure = new Measure(); diff --git a/src/iomei.cpp b/src/iomei.cpp index aa139f99077..ebe8703ec85 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -445,7 +445,14 @@ bool MEIOutput::WriteObjectInternal(Object *object, bool useCustomScoreDef) this->WriteSymbolTable(m_currentNode, vrv_cast(object)); } else if (object->Is(MEASURE)) { - m_currentNode = m_currentNode.append_child("measure"); + Measure *measure = vrv_cast(object); + assert(measure); + std::string name = "measure"; + if (measure->IsNeumeLine()) { + name = "section"; + measure->SetType(NEUME_LINE_TYPE); + } + m_currentNode = m_currentNode.append_child(name.c_str()); this->WriteMeasure(m_currentNode, vrv_cast(object)); } else if (object->Is(STAFF)) { @@ -4394,7 +4401,13 @@ bool MEIInput::ReadScore(Object *parent, pugi::xml_node score) bool MEIInput::ReadSection(Object *parent, pugi::xml_node section) { Section *vrvSection = new Section(); - this->SetMeiID(section, vrvSection); + this->ReadSystemElement(section, vrvSection); + + if (vrvSection->GetType() == NEUME_LINE_TYPE) { + delete vrvSection; + m_doc->SetNeumeLines(true); + return ReadSectionChildren(parent, section); + } vrvSection->ReadNNumberLike(section); vrvSection->ReadSectionVis(section); @@ -4454,8 +4467,13 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) else if (std::string(current.name()) == "staff") { if (!unmeasured) { if (parent->Is(SECTION)) { - unmeasured = new Measure(false); - m_doc->SetMensuralMusicOnly(true); + if (m_doc->IsNeumeLines()) { + unmeasured = new Measure(NEUMELINE); + } + else { + unmeasured = new Measure(UNMEASURED); + m_doc->SetMensuralMusicOnly(true); + } parent->AddChild(unmeasured); } else { @@ -4484,8 +4502,13 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) // New for blank files in neume notation if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume)) { - unmeasured = new Measure(false); - m_doc->SetMensuralMusicOnly(true); + if (m_doc->IsNeumeLines()) { + unmeasured = new Measure(NEUMELINE); + } + else { + unmeasured = new Measure(UNMEASURED); + m_doc->SetMensuralMusicOnly(true); + } parent->AddChild(unmeasured); } return success; @@ -4628,7 +4651,7 @@ bool MEIInput::ReadSystemChildren(Object *parent, pugi::xml_node parentNode) if (parent->Is(SYSTEM)) { System *system = vrv_cast(parent); assert(system); - unmeasured = new Measure(false); + unmeasured = new Measure(UNMEASURED); m_doc->SetMensuralMusicOnly(true); if (m_doc->IsTranscription() && (m_meiversion == meiVersion_MEIVERSION_2013)) { UpgradeMeasureTo_3_0_0(unmeasured, system); diff --git a/src/iopae.cpp b/src/iopae.cpp index 938efd8d54f..725f9bf22fa 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -2875,7 +2875,7 @@ bool PAEInput::Import(const std::string &input) } // Add a measure at the beginning of the data because there is always at least one measure - Measure *measure = new Measure(true, 1); + Measure *measure = new Measure(MEASURED, 1); // By default there is no end barline on an incipit measure->SetRight(BARRENDITION_invis); m_pae.push_back(pae::Token(0, pae::UNKOWN_POS, measure)); @@ -3394,7 +3394,7 @@ bool PAEInput::ConvertMeasure() // We can now create a new measure but not if we have reached the end of the data if (!token.IsEnd()) { measureCount++; - currentMeasure = new Measure(true, measureCount); + currentMeasure = new Measure(MEASURED, measureCount); currentMeasure->SetRight(BARRENDITION_invis); measureToken->m_object = currentMeasure; } diff --git a/src/measure.cpp b/src/measure.cpp index 7b566400378..a3c45c0bd12 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -54,7 +54,7 @@ namespace vrv { static const ClassRegistrar s_factory("measure", MEASURE); -Measure::Measure(bool measureMusic, int logMeasureNb) +Measure::Measure(MeasureType measureMusic, int logMeasureNb) : Object(MEASURE, "measure-") , FacsimileInterface() , AttBarring() @@ -76,7 +76,7 @@ Measure::Measure(bool measureMusic, int logMeasureNb) this->RegisterAttClass(ATT_TYPED); this->RegisterInterface(FacsimileInterface::GetAttClasses(), FacsimileInterface::IsInterface()); - m_measuredMusic = measureMusic; + m_measureType = measureMusic; // We set parent to it because we want to access the parent doc from the aligners m_measureAligner.SetParent(this); @@ -149,7 +149,7 @@ void Measure::Reset() m_rightBarLine.SetForm(this->GetRight()); m_leftBarLine.SetForm(this->GetLeft()); - if (!m_measuredMusic) { + if (!m_measureType) { m_drawingFacsX1 = VRV_UNSET; m_drawingFacsX2 = VRV_UNSET; } diff --git a/src/savefunctor.cpp b/src/savefunctor.cpp index 74172c5112e..082b9d9259a 100644 --- a/src/savefunctor.cpp +++ b/src/savefunctor.cpp @@ -96,12 +96,12 @@ FunctorCode SaveFunctor::VisitMdivEnd(Mdiv *mdiv) FunctorCode SaveFunctor::VisitMeasure(Measure *measure) { - return (measure->IsMeasuredMusic()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMeasureEnd(Measure *measure) { - return (measure->IsMeasuredMusic()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMNum(MNum *mNum) From 8b3613eb5ba0977bdc7d2dd86f685d49d2bbe2b9 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 21:22:59 +0100 Subject: [PATCH 011/383] Adjust the facsimile functor to take into account the PPU --- include/vrv/devicecontext.h | 7 +++++ include/vrv/facsimilefunctor.h | 6 +++- src/devicecontext.cpp | 5 ++++ src/doc.cpp | 8 ++++-- src/facsimilefunctor.cpp | 51 ++++++++++++++++++++++++++++++---- src/measure.cpp | 4 +++ src/svgdevicecontext.cpp | 4 +-- src/toolkit.cpp | 3 +- 8 files changed, 77 insertions(+), 11 deletions(-) diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index e493d38b611..fde5c18875d 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -74,6 +74,7 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; + m_viewBoxFactor = (double)DEFINITION_FACTOR; } DeviceContext(ClassId classId) { @@ -89,6 +90,7 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; + m_viewBoxFactor = (double)DEFINITION_FACTOR; } virtual ~DeviceContext(){}; ClassId GetClassId() const { return m_classId; } @@ -124,12 +126,14 @@ class DeviceContext { m_baseWidth = width; m_baseHeight = height; } + void SetViewBoxFactor(double ppuFactor); int GetWidth() const { return m_width; } int GetHeight() const { return m_height; } int GetContentHeight() const { return m_contentHeight; } double GetUserScaleX() { return m_userScaleX; } double GetUserScaleY() { return m_userScaleY; } std::pair GetBaseSize() const { return std::make_pair(m_baseWidth, m_baseHeight); } + double GetViewBoxFactor() const { return m_viewBoxFactor; } ///@} /** @@ -365,6 +369,9 @@ class DeviceContext { /** stores the scale as requested by the used */ double m_userScaleX; double m_userScaleY; + + /** stores the viewbox factor taking into account the DEFINTION_FACTOR and the PPU */ + double m_viewBoxFactor; }; } // namespace vrv diff --git a/include/vrv/facsimilefunctor.h b/include/vrv/facsimilefunctor.h index 1273343626c..7ccf8f3e772 100644 --- a/include/vrv/facsimilefunctor.h +++ b/include/vrv/facsimilefunctor.h @@ -42,7 +42,7 @@ class SyncFromFacsimileFunctor : public Functor { /* * Abstract base implementation */ - bool ImplementsEndInterface() const override { return false; } + bool ImplementsEndInterface() const override { return true; } /* * Functor interface @@ -51,6 +51,7 @@ class SyncFromFacsimileFunctor : public Functor { FunctorCode VisitLayerElement(LayerElement *layerElement) override; FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitPage(Page *page) override; + FunctorCode VisitPageEnd(Page *page) override; FunctorCode VisitPb(Pb *pb) override; FunctorCode VisitSb(Sb *sb) override; FunctorCode VisitStaff(Staff *staff) override; @@ -71,6 +72,9 @@ class SyncFromFacsimileFunctor : public Functor { // Page *m_currentPage; System *m_currentSystem; + Measure *m_currentNeumeLine; + /** map to store the zone corresponding to a staff */ + std::map m_staffZones; }; //---------------------------------------------------------------------------- diff --git a/src/devicecontext.cpp b/src/devicecontext.cpp index 09a868e56ab..e734a35919d 100644 --- a/src/devicecontext.cpp +++ b/src/devicecontext.cpp @@ -129,6 +129,11 @@ const Resources *DeviceContext::GetResources(bool showWarning) const return m_resources; } +void DeviceContext::SetViewBoxFactor(double ppuFactor) +{ + m_viewBoxFactor = double(DEFINITION_FACTOR) / ppuFactor; +} + void DeviceContext::SetPen(int color, int width, int style, int dashLength, int gapLength, int lineCap, int lineJoin) { float opacityValue; diff --git a/src/doc.cpp b/src/doc.cpp index d47b5a0c188..4e6e4b0b3d7 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -2129,8 +2129,10 @@ int Doc::GetAdjustedDrawingPageHeight() const { assert(m_drawingPage); + // Take into account the PPU when getting the page height in facsimile if (this->IsTranscription() || this->IsFacs()) { - return m_drawingPage->m_pageHeight / DEFINITION_FACTOR; + const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); + return m_drawingPage->m_pageHeight / factor; } int contentHeight = m_drawingPage->GetContentHeight(); @@ -2141,8 +2143,10 @@ int Doc::GetAdjustedDrawingPageWidth() const { assert(m_drawingPage); + // Take into account the PPU when getting the page width in facsimile if (this->IsTranscription() || this->IsFacs()) { - return m_drawingPage->m_pageWidth / DEFINITION_FACTOR; + const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); + return m_drawingPage->m_pageWidth / factor; } int contentWidth = m_drawingPage->GetContentWidth(); diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 73ec1ef23ca..6c01cbc5217 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -12,6 +12,7 @@ #include "doc.h" #include "layerelement.h" #include "measure.h" +#include "miscfunctor.h" #include "page.h" #include "pb.h" #include "sb.h" @@ -39,7 +40,7 @@ SyncFromFacsimileFunctor::SyncFromFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ CLEF, CUSTOS, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; Zone *zone = layerElement->GetZone(); assert(zone); @@ -50,22 +51,54 @@ FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerEleme FunctorCode SyncFromFacsimileFunctor::VisitMeasure(Measure *measure) { - Zone *zone = measure->GetZone(); - assert(zone); - measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); - measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + // neon specific code - measure have no zone, we will use the staff one in VisitStaff + if (measure->IsNeumeLine()) { + m_currentNeumeLine = measure; + } + else { + Zone *zone = measure->GetZone(); + assert(zone); + measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + } return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitPage(Page *page) { + m_staffZones.clear(); m_currentPage = page; m_doc->SetDrawingPage(m_currentPage->GetIdx()); return FUNCTOR_CONTINUE; } +FunctorCode SyncFromFacsimileFunctor::VisitPageEnd(Page *page) +{ + // Used for adjusting staff size in neon - filled in VisitStaff + if (m_staffZones.empty()) return FUNCTOR_CONTINUE; + + // The staff size is calculated based on the zone height and takes into acocunt the rotation + for (auto &[staff, zone] : m_staffZones) { + double rotate = (zone->HasRotate()) ? zone->GetRotate() : 0.0; + int yDiff + = zone->GetLry() - zone->GetUly() - (zone->GetLrx() - zone->GetUlx()) * tan(abs(rotate) * M_PI / 180.0); + staff->m_drawingStaffSize + = 100 * yDiff / (m_doc->GetOptions()->m_unit.GetValue() * 2 * (staff->m_drawingLines - 1)); + } + + // Since we multiply all values by the DEFINITION_FACTOR, set it as PPU + m_currentPage->SetPPUFactor(DEFINITION_FACTOR); + if (m_currentPage->GetPPUFactor() != 1.0) { + ApplyPPUFactorFunctor applyPPUFactor; + m_currentPage->Process(applyPPUFactor); + m_doc->UpdatePageDrawingSizes(); + } + + return FUNCTOR_CONTINUE; +} + FunctorCode SyncFromFacsimileFunctor::VisitPb(Pb *pb) { // This would happen if we run the functor on data not converted to page-based @@ -109,12 +142,20 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) assert(zone); staff->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); + // neon specific code - set the position of the pseudo measure (neume line) + if (m_currentNeumeLine) { + m_currentNeumeLine->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + m_staffZones[staff] = zone; + } + return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitSystem(System *system) { m_currentSystem = system; + m_currentNeumeLine = NULL; return FUNCTOR_CONTINUE; } diff --git a/src/measure.cpp b/src/measure.cpp index a3c45c0bd12..9b13faf70db 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -213,6 +213,7 @@ void Measure::AddChildBack(Object *child) int Measure::GetDrawingX() const { + /* if (!this->IsMeasuredMusic()) { const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); assert(system); @@ -220,6 +221,7 @@ int Measure::GetDrawingX() const return (system->m_systemLeftMar); } } + */ if (m_drawingFacsX1 != VRV_UNSET) return m_drawingFacsX1; @@ -353,6 +355,7 @@ int Measure::GetRightBarLineRight() const int Measure::GetWidth() const { + /* if (!this->IsMeasuredMusic()) { const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); assert(system); @@ -363,6 +366,7 @@ int Measure::GetWidth() const return page->m_pageWidth - system->m_systemLeftMar - system->m_systemRightMar; } } + */ if (m_drawingFacsX2 != VRV_UNSET) return (m_drawingFacsX2 - m_drawingFacsX1); diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 1769f1f138d..1cec345599f 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -496,8 +496,8 @@ void SvgDeviceContext::StartPage() = StringFormat("0 0 %d %d", this->GetWidth(), this->GetHeight()).c_str(); } else { - m_currentNode.append_attribute("viewBox") = StringFormat( - "0 0 %d %d", this->GetWidth() * DEFINITION_FACTOR, this->GetContentHeight() * DEFINITION_FACTOR) + m_currentNode.append_attribute("viewBox") = StringFormat("0 0 %d %d", + int(this->GetWidth() * this->GetViewBoxFactor()), int(this->GetContentHeight() * this->GetViewBoxFactor())) .c_str(); } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 3552ff2279f..a2d44009785 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1521,7 +1521,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) std::swap(height, width); } - double userScale = m_view.GetPPUFactor() * m_options->m_scale.GetValue() / 100; + double userScale = m_options->m_scale.GetValue() / 100.0; assert(userScale != 0.0); if (m_options->m_scaleToPageSize.GetValue()) { @@ -1533,6 +1533,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) deviceContext->SetUserScale(userScale, userScale); deviceContext->SetWidth(width); deviceContext->SetHeight(height); + deviceContext->SetViewBoxFactor(m_view.GetPPUFactor()); if (m_doc.IsFacs()) { deviceContext->SetWidth(m_doc.GetFacsimile()->GetMaxX()); From f21e847a5be37ca9efba718b23115d1dfe45947c Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 22:06:09 +0100 Subject: [PATCH 012/383] Store staff rotation in Staff and adjust facsimile functor --- include/vrv/staff.h | 16 ++++++++++++++++ src/facsimilefunctor.cpp | 8 ++++++++ src/staff.cpp | 2 ++ src/view_page.cpp | 7 ++++++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/include/vrv/staff.h b/include/vrv/staff.h index cd68fe3eaa4..f11daee3f8e 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -112,6 +112,16 @@ class Staff : public Object, } ///@} + /** + * @name Getters and setters for the rotation. + * Used only with facsimile rendering. + */ + ///@{ + void SetDrawingRotation(double drawingRotation) { m_drawingRotation = drawingRotation; } + double GetDrawingRotation() const { return m_drawingRotation; } + bool HasDrawingRotation() const { return (m_drawingRotation != 0.0); } + ///@} + /** * Delete all the legder line arrays. */ @@ -290,6 +300,12 @@ class Staff : public Object, ArrayOfLedgerLines m_ledgerLinesAboveCue; ArrayOfLedgerLines m_ledgerLinesBelowCue; ///@} + + /** + * The drawing rotation. + * Used only with facsimile rendering + */ + double m_drawingRotation; }; } // namespace vrv diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 6c01cbc5217..0287275a7fc 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -86,6 +86,7 @@ FunctorCode SyncFromFacsimileFunctor::VisitPageEnd(Page *page) = zone->GetLry() - zone->GetUly() - (zone->GetLrx() - zone->GetUlx()) * tan(abs(rotate) * M_PI / 180.0); staff->m_drawingStaffSize = 100 * yDiff / (m_doc->GetOptions()->m_unit.GetValue() * 2 * (staff->m_drawingLines - 1)); + staff->SetDrawingRotation(rotate); } // Since we multiply all values by the DEFINITION_FACTOR, set it as PPU @@ -147,6 +148,13 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) m_currentNeumeLine->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); m_staffZones[staff] = zone; + + // The staff slope is going up. The y left postion needs to be adjusted accordingly + if (zone->GetRotate() < 0) { + staff->m_drawingFacsY = staff->m_drawingFacsY + + (m_currentNeumeLine->m_drawingFacsX2 - m_currentNeumeLine->m_drawingFacsX1) + * tan(zone->GetRotate() * M_PI / 180.0); + } } return FUNCTOR_CONTINUE; diff --git a/src/staff.cpp b/src/staff.cpp index 28b69219213..1a37ab7ead0 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -75,6 +75,7 @@ void Staff::Reset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; + m_drawingRotation = 0.0; ClearLedgerLines(); } @@ -90,6 +91,7 @@ void Staff::CloneReset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; + m_drawingRotation = 0.0; } void Staff::ClearLedgerLines() diff --git a/src/view_page.cpp b/src/view_page.cpp index 51d8b78be5c..a8f41dfb829 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1295,7 +1295,12 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys x1 = measure->GetDrawingX(); x2 = x1 + measure->GetWidth(); y1 = staff->GetDrawingY(); - y2 = y1; + if (!staff->HasDrawingRotation()) { + y2 = y1; + } + else { + y2 = y1 - staff->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); + } } const int lineWidth = m_doc->GetDrawingStaffLineWidth(staff->m_drawingStaffSize); From 136331fc2b563aea564ddb84a6fb29a522c5ada4 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 22:28:25 +0100 Subject: [PATCH 013/383] Handle rotation offset when rendering layer elements --- include/vrv/staff.h | 1 + src/facsimilefunctor.cpp | 2 +- src/staff.cpp | 6 ++++++ src/view_element.cpp | 15 ++++++++++++--- src/view_neume.cpp | 20 ++++++++++---------- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/include/vrv/staff.h b/include/vrv/staff.h index f11daee3f8e..92a699a43bf 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -120,6 +120,7 @@ class Staff : public Object, void SetDrawingRotation(double drawingRotation) { m_drawingRotation = drawingRotation; } double GetDrawingRotation() const { return m_drawingRotation; } bool HasDrawingRotation() const { return (m_drawingRotation != 0.0); } + int GetDrawingRotationOffsetFor(int x); ///@} /** diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 0287275a7fc..436939cf0cf 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -149,7 +149,7 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); m_staffZones[staff] = zone; - // The staff slope is going up. The y left postion needs to be adjusted accordingly + // The staff slope is going up. The y left position needs to be adjusted accordingly if (zone->GetRotate() < 0) { staff->m_drawingFacsY = staff->m_drawingFacsY + (m_currentNeumeLine->m_drawingFacsX2 - m_currentNeumeLine->m_drawingFacsX1) diff --git a/src/staff.cpp b/src/staff.cpp index 1a37ab7ead0..1e3c1f00a52 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -94,6 +94,12 @@ void Staff::CloneReset() m_drawingRotation = 0.0; } +int Staff::GetDrawingRotationOffsetFor(int x) +{ + int xDiff = x - this->GetDrawingX(); + return int(xDiff * tan(this->GetDrawingRotation() * M_PI / 180.0)); +} + void Staff::ClearLedgerLines() { m_ledgerLinesAbove.clear(); diff --git a/src/view_element.cpp b/src/view_element.cpp index 3c698c7add7..236df764961 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -300,16 +300,19 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta } if (notationType == NOTATIONTYPE_neume) { - int rotateOffset = 0; + int rotationOffset = 0; if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { double deg = staff->GetDrawingRotate(); int xDiff = x - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); + } + else if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(x); } if (accid->HasFacs() && m_doc->IsFacs()) { y = ToLogicalY(y); } - y -= rotateOffset; + y -= rotationOffset; } this->DrawSmuflString( @@ -676,6 +679,9 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf int xDiff = x - staff->GetDrawingX(); y -= int(xDiff * tan(deg * M_PI / 180.0)); } + else if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); + } } else if (clef->GetShape() == CLEFSHAPE_perc) { y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); @@ -794,6 +800,9 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St int xDiff = x - staff->GetDrawingX(); y -= int(xDiff * tan(deg * M_PI / 180.0)); } + else if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); + } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 971a8bf3304..1b972bab007 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -228,14 +228,14 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); } int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotateOffset; + int rotationOffset = 0; if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { double deg = staff->GetDrawingRotate(); int xDiff = noteX - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); } - else { - rotateOffset = 0; + else if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(noteX); } if (nc->HasLoc()) { @@ -248,7 +248,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (clef->GetShape() == CLEFSHAPE_F) { pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); } - yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; + yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; } for (auto it = params.begin(); it != params.end(); it++) { @@ -392,17 +392,17 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S y -= (m_doc->GetDrawingUnit(staff->m_drawingStaffSize)) * 3; - int rotateOffset; + int rotationOffset = 0; if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { double deg = staff->GetDrawingRotate(); int xDiff = x - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); } - else { - rotateOffset = 0; + else if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(x); } - y -= rotateOffset; + y -= rotationOffset; DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); From ac5f7db32db77d6320e4ba8a62bdc9a80d046266 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 9 Jan 2024 07:54:34 +0100 Subject: [PATCH 014/383] Fix custos positioning without facs --- src/view_element.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 236df764961..4bbd8ec48a8 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -766,17 +766,18 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St const int sym = custos->GetCustosGlyph(staff->m_drawingNotationType); int x, y; - if (custos->HasFacs() && m_doc->IsFacs()) { + // For neume notation we ignore the value set in CalcAlignmentPitchPosFunctor + if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { x = custos->GetDrawingX(); // Recalculate y from pitch to prevent visual/meaning mismatch Clef *clef = layer->GetClef(element); - y = ToLogicalY(staff->GetDrawingY()); + y = staff->GetDrawingY(); PitchInterface pi; // Neume notation uses C3 for C clef rather than C4. // Take this into account when determining location. // However this doesn't affect the value for F clef. pi.SetPname(PITCHNAME_c); - if ((staff->m_drawingNotationType == NOTATIONTYPE_neume) && (clef->GetShape() == CLEFSHAPE_C)) { + if (clef->GetShape() == CLEFSHAPE_C) { pi.SetOct(3); } else { From a093efbe99fb62e83bc594f60fffd8a05a527fc8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 9 Jan 2024 11:23:05 +0100 Subject: [PATCH 015/383] Resolve `@facs` pointing to `` --- include/vrv/facsimileinterface.h | 7 +++++++ src/facsimilefunctor.cpp | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/vrv/facsimileinterface.h b/include/vrv/facsimileinterface.h index 7afafef144b..84f46ddb9b8 100644 --- a/include/vrv/facsimileinterface.h +++ b/include/vrv/facsimileinterface.h @@ -58,6 +58,13 @@ class FacsimileInterface : public Interface, public AttFacsimile { Zone *GetZone() { return m_zone; } const Zone *GetZone() const { return m_zone; } ///@} + /// + + /** Get the surface */ + ///@{ + Surface *GetSurface() { return m_surface; } + const Surface *GetSurface() const { return m_surface; } + ///@} //-----------------// // Pseudo functors // diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 436939cf0cf..83f53d6b275 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -106,9 +106,11 @@ FunctorCode SyncFromFacsimileFunctor::VisitPb(Pb *pb) assert(m_currentPage); Zone *zone = pb->GetZone(); - assert(zone && zone->GetParent()); - Surface *surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; - // Use the parent surface attributes if given + Surface *surface = pb->GetSurface(); + if (!surface && zone && zone->GetParent()) + surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; + assert(zone || surface); + // Use the (parent) surface attributes if given if (surface && surface->HasLrx() && surface->HasLry()) { m_currentPage->m_pageHeight = surface->GetLry() * DEFINITION_FACTOR; m_currentPage->m_pageWidth = surface->GetLrx() * DEFINITION_FACTOR; From 9a9945c1b9737dfc36953f710c58c08fa0c71f60 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 9 Jan 2024 14:29:46 +0100 Subject: [PATCH 016/383] Fix milestone end for added mdiv and score --- src/doc.cpp | 2 ++ src/iomei.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/doc.cpp b/src/doc.cpp index 4e6e4b0b3d7..4300e41b8d8 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -1408,6 +1408,8 @@ void Doc::SyncToFacsimileDoc() if (!m_facsimile->FindDescendantByType(SURFACE)) { m_facsimile->AddChild(new Surface()); } + this->ScoreDefSetCurrentDoc(); + m_facsimile->SetType("transcription"); m_facsimile->ClearChildren(); diff --git a/src/iomei.cpp b/src/iomei.cpp index ebe8703ec85..badac886885 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -8206,12 +8206,14 @@ void MEIInput::UpgradePageTo_5_0(Page *page) PageMilestoneEnd *scoreEnd = new PageMilestoneEnd(score); page->AddChild(scoreEnd); + score->SetEnd(scoreEnd); Mdiv *mdiv = new Mdiv(); page->InsertChild(mdiv, 0); PageMilestoneEnd *mdivEnd = new PageMilestoneEnd(mdiv); page->AddChild(mdivEnd); + mdiv->SetEnd(mdivEnd); } void MEIInput::UpgradePgHeadFootTo_5_0(pugi::xml_node element) From 2253dc1354bf2a36db2add41c41bbd989664f79a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 10 Jan 2024 08:01:15 +0100 Subject: [PATCH 017/383] Remove unused code in Measure --- src/measure.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/measure.cpp b/src/measure.cpp index 9b13faf70db..d5e48b679f7 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -213,16 +213,6 @@ void Measure::AddChildBack(Object *child) int Measure::GetDrawingX() const { - /* - if (!this->IsMeasuredMusic()) { - const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); - assert(system); - if (system->m_drawingFacsY != VRV_UNSET) { - return (system->m_systemLeftMar); - } - } - */ - if (m_drawingFacsX1 != VRV_UNSET) return m_drawingFacsX1; if (m_cachedDrawingX != VRV_UNSET) return m_cachedDrawingX; @@ -355,19 +345,6 @@ int Measure::GetRightBarLineRight() const int Measure::GetWidth() const { - /* - if (!this->IsMeasuredMusic()) { - const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); - assert(system); - if (system->m_drawingFacsY != VRV_UNSET) { - const Page *page = vrv_cast(system->GetFirstAncestor(PAGE)); - assert(page); - // xAbs2 = page->m_pageWidth - system->m_systemRightMar; - return page->m_pageWidth - system->m_systemLeftMar - system->m_systemRightMar; - } - } - */ - if (m_drawingFacsX2 != VRV_UNSET) return (m_drawingFacsX2 - m_drawingFacsX1); assert(m_measureAligner.GetRightAlignment()); From cd19e4978951e1b8e5f781e92c86012e862bea73 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 10 Jan 2024 09:26:59 +0100 Subject: [PATCH 018/383] Use measure width for calculating staff slope --- src/view_page.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view_page.cpp b/src/view_page.cpp index a8f41dfb829..3a13c9d1cb9 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1299,7 +1299,7 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys y2 = y1; } else { - y2 = y1 - staff->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); + y2 = y1 - measure->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); } } From 820b5a8d07d25a37b289a93d6370c26810e98044 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 14:39:46 -0500 Subject: [PATCH 019/383] Adjust offset when insert new neume component --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2b1d30f6dc8..6a8cf8e89a4 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -881,11 +881,12 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int noteWidthOffset = (int)(noteWidth / 2); // Set up facsimile - zone->SetUlx(ulx); + zone->SetUlx(ulx - noteWidthOffset); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidthOffset); zone->SetLry(uly + noteHeight); // add syl bounding box if Facs From ff9b4fb3e88bc81516cb43e5922a7090fc614187 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 14:43:15 -0500 Subject: [PATCH 020/383] Adjust offset when insert new neume grouping --- src/editortoolkit_neume.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 6a8cf8e89a4..2564eb70c2f 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -996,9 +996,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in // Apply offset due to rotate newUly += (newUlx - ulx) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); - newZone->SetUlx(newUlx); + newZone->SetUlx(newUlx - noteWidthOffset); newZone->SetUly(newUly); - newZone->SetLrx(newUlx + noteWidth); + newZone->SetLrx(newUlx + noteWidthOffset); newZone->SetLry(newUly + noteHeight); newNc->AttachZone(newZone); From 5281a345080aa9ca2aaa0fc705b6f4beb042fb88 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:00:05 -0500 Subject: [PATCH 021/383] Adjust offset when insert new clef --- src/editortoolkit_neume.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2564eb70c2f..92989b866e6 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1028,14 +1028,21 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Clef *clef = new Clef(); data_CLEFSHAPE clefShape = CLEFSHAPE_NONE; + const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int noteWidthOffsetR, noteWidthOffsetL; + for (auto it = attributes.begin(); it != attributes.end(); ++it) { if (it->first == "shape") { if (it->second == "C") { clefShape = CLEFSHAPE_C; + noteWidthOffsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + noteWidthOffsetL = noteWidthOffsetR; break; } else if (it->second == "F") { clefShape = CLEFSHAPE_F; + noteWidthOffsetR = 0; + noteWidthOffsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); break; } } @@ -1049,7 +1056,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } clef->SetShape(clefShape); - const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); int yDiff = -staff->GetDrawingY() + uly; yDiff += ((ulx - staff->GetZone()->GetUlx())) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); // Subtract distance due to rotate. @@ -1057,9 +1063,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in clef->SetLine(clefLine); Zone *zone = new Zone(); - zone->SetUlx(ulx); + zone->SetUlx(ulx - noteWidthOffsetR); zone->SetUly(uly); - zone->SetLrx(ulx + staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + zone->SetLrx(ulx + noteWidthOffsetL); zone->SetLry(uly + staffSize / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); clef->AttachZone(zone); Surface *surface = dynamic_cast(facsimile->FindDescendantByType(SURFACE)); From e9e9270ed80c7d0559f4bacaab6015ee3820c4de Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:15:21 -0500 Subject: [PATCH 022/383] Adjust offset when insert new custos --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 92989b866e6..2ce57934ac8 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1111,13 +1111,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 4); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); if (!AdjustPitchFromPosition(custos)) { From b637ee96aff10c816a41b98a17445a8c353481e0 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:19:08 -0500 Subject: [PATCH 023/383] Rename offset for position adjustment --- src/editortoolkit_neume.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2ce57934ac8..bf2925fb352 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -881,12 +881,12 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - const int noteWidthOffset = (int)(noteWidth / 2); + const int offsetX = (int)(noteWidth / 2); // Set up facsimile - zone->SetUlx(ulx - noteWidthOffset); + zone->SetUlx(ulx - offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidthOffset); + zone->SetLrx(ulx + offsetX); zone->SetLry(uly + noteHeight); // add syl bounding box if Facs @@ -996,9 +996,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in // Apply offset due to rotate newUly += (newUlx - ulx) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); - newZone->SetUlx(newUlx - noteWidthOffset); + newZone->SetUlx(newUlx - offsetX); newZone->SetUly(newUly); - newZone->SetLrx(newUlx + noteWidthOffset); + newZone->SetLrx(newUlx + offsetX); newZone->SetLry(newUly + noteHeight); newNc->AttachZone(newZone); @@ -1029,20 +1029,20 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in data_CLEFSHAPE clefShape = CLEFSHAPE_NONE; const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int noteWidthOffsetR, noteWidthOffsetL; + int offsetR, offsetL; for (auto it = attributes.begin(); it != attributes.end(); ++it) { if (it->first == "shape") { if (it->second == "C") { clefShape = CLEFSHAPE_C; - noteWidthOffsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); - noteWidthOffsetL = noteWidthOffsetR; + offsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + offsetL = offsetR; break; } else if (it->second == "F") { clefShape = CLEFSHAPE_F; - noteWidthOffsetR = 0; - noteWidthOffsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + offsetR = 0; + offsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); break; } } @@ -1063,9 +1063,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in clef->SetLine(clefLine); Zone *zone = new Zone(); - zone->SetUlx(ulx - noteWidthOffsetR); + zone->SetUlx(ulx - offsetR); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidthOffsetL); + zone->SetLrx(ulx + offsetL); zone->SetLry(uly + staffSize / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); clef->AttachZone(zone); Surface *surface = dynamic_cast(facsimile->FindDescendantByType(SURFACE)); From a1ae991187af6aede571866146bf94416f248441 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:52:52 -0500 Subject: [PATCH 024/383] Adjust offset when insert new accid --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index bf2925fb352..d6c97ef532d 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1165,13 +1165,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); From da97fc5cd526187c268486681a8610a3ae0fdb26 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:57:01 -0500 Subject: [PATCH 025/383] Adjust offset when insert new divLine --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index d6c97ef532d..ac01f9f0fde 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1230,13 +1230,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); From 4e063962e3c5ebf9ab83e1b57a6063ec37aed1da Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 6 Feb 2024 17:04:50 -0500 Subject: [PATCH 026/383] Remove redundant zone of syl when merging syllables --- src/editortoolkit_neume.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index ac01f9f0fde..0e9ec5e8fae 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2728,6 +2728,10 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e + obj->GetChildCount(CLEF))) { Object *leftover; while ((leftover = obj->FindDescendantByType(SYL)) != NULL) { + Zone *zone = dynamic_cast(leftover->GetFacsimileInterface()->GetZone()); + if (zone != NULL) { + m_doc->GetFacsimile()->FindDescendantByType(SURFACE)->DeleteChild(zone); + } obj->DeleteChild(leftover); } while ((leftover = obj->FindDescendantByType(DIVLINE)) != NULL) { From 14ed2b1379c53ebf55b9f55e9cde03b7d23fc632 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 4 Jan 2024 15:17:38 -0500 Subject: [PATCH 027/383] Add this-> to DrawDivLine --- src/view_element.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 4bbd8ec48a8..51ed9b647ad 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -113,7 +113,7 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay this->DrawCustos(dc, element, layer, staff, measure); } else if (element->Is(DIVLINE)) { - DrawDivLine(dc, element, layer, staff, measure); + this->DrawDivLine(dc, element, layer, staff, measure); } else if (element->Is(DOT)) { this->DrawDot(dc, element, layer, staff, measure); From 4ce51cbc8d8eaf1a4f3619c8583467b1e55fde59 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 4 Jan 2024 16:40:31 -0500 Subject: [PATCH 028/383] Write liquescent via nc@curve --- src/editortoolkit_neume.cpp | 2 -- src/iomei.cpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 0e9ec5e8fae..5739ee1b417 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -948,7 +948,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in contour = it->second; } else if (it->first == "curve") { - Liquescent *liquescent = new Liquescent(); curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; if (it->second == "a") { curve = curvatureDirection_CURVE_a; @@ -958,7 +957,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in curve = curvatureDirection_CURVE_c; nc->SetCurve(curve); } - nc->AddChild(liquescent); } } diff --git a/src/iomei.cpp b/src/iomei.cpp index badac886885..410fb82e4bb 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2710,6 +2710,7 @@ void MEIOutput::WriteNc(pugi::xml_node currentNode, Nc *nc) this->WritePitchInterface(currentNode, nc); this->WritePositionInterface(currentNode, nc); nc->WriteColor(currentNode); + nc->WriteCurvatureDirection(currentNode); nc->WriteIntervalMelodic(currentNode); nc->WriteNcForm(currentNode); } From e50606ddd7dc675c8f416e59aae9eec5357ce922 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Sun, 7 Jan 2024 15:05:56 -0500 Subject: [PATCH 029/383] Add reading @curve for nc --- src/iomei.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iomei.cpp b/src/iomei.cpp index 410fb82e4bb..20d79472273 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -6833,6 +6833,7 @@ bool MEIInput::ReadNc(Object *parent, pugi::xml_node nc) this->ReadPitchInterface(nc, vrvNc); this->ReadPositionInterface(nc, vrvNc); vrvNc->ReadColor(nc); + vrvNc->ReadCurvatureDirection(nc); vrvNc->ReadIntervalMelodic(nc); vrvNc->ReadNcForm(nc); From 4c869d896d7ea46902317e57bb17fb416ee4b01d Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Sun, 7 Jan 2024 16:00:33 -0500 Subject: [PATCH 030/383] Add liquescent element to insertion --- src/editortoolkit_neume.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 5739ee1b417..ec7230e6081 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -957,6 +957,8 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in curve = curvatureDirection_CURVE_c; nc->SetCurve(curve); } + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); } } From 354fd814c7af517ad754d3c519078b4f86ffb9d9 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 8 Jan 2024 11:51:09 -0500 Subject: [PATCH 031/383] Add SetLiquescent() --- include/vrv/editortoolkit_neume.h | 2 + src/editortoolkit_neume.cpp | 66 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index 11828f083cb..5fcd380f836 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -50,6 +50,7 @@ class EditorToolkitNeume : public EditorToolkit { bool Set(std::string elementId, std::string attrType, std::string attrValue); bool SetText(std::string elementId, const std::string &text); bool SetClef(std::string elementId, std::string shape); + bool SetLiquescent(std::string elementId, std::string shape); bool Split(std::string elementId, int x); bool SplitNeume(std::string elementId, std::string ncId); bool Remove(std::string elementId); @@ -80,6 +81,7 @@ class EditorToolkitNeume : public EditorToolkit { bool ParseSetAction(jsonxx::Object param, std::string *elementId, std::string *attrType, std::string *attrValue); bool ParseSetTextAction(jsonxx::Object param, std::string *elementId, std::string *text); bool ParseSetClefAction(jsonxx::Object param, std::string *elementId, std::string *shape); + bool ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *shape); bool ParseSplitAction(jsonxx::Object param, std::string *elementId, int *x); bool ParseSplitNeumeAction(jsonxx::Object param, std::string *elementId, std::string *ncId); bool ParseRemoveAction(jsonxx::Object param, std::string *elementId); diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index ec7230e6081..1fe87d1b6bd 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -136,6 +136,13 @@ bool EditorToolkitNeume::ParseEditorAction(const std::string &json_editorAction) } LogWarning("Could not parse the set clef action"); } + else if (action == "setLiquescent") { + std::string elementId, curve; + if (this->ParseSetLiquescentAction(json.get("param"), &elementId, &curve)) { + return this->SetLiquescent(elementId, curve); + } + LogWarning("Could not parse the set liquescent action"); + } else if (action == "remove") { std::string elementId; if (this->ParseRemoveAction(json.get("param"), &elementId)) { @@ -1996,6 +2003,50 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) return true; } +bool EditorToolkitNeume::SetLiquescent(std::string elementId, std::string curve) +{ + if (!m_doc->GetDrawingPage()) { + LogError("Could not get the drawing page."); + m_editInfo.import("status", "FAILURE"); + m_editInfo.import("message", "Could not get the drawing page."); + return false; + } + + Nc *nc = vrv_cast(m_doc->GetDrawingPage()->FindDescendantByID(elementId)); + assert(nc); + bool hasLiquscent = nc->GetChildCount(); + + if (curve == "a") { + curvatureDirection_CURVE curve = curvatureDirection_CURVE_a; + nc->SetCurve(curve); + if (!hasLiquscent) { + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); + } + } + else if (curve == "c") { + curvatureDirection_CURVE curve = curvatureDirection_CURVE_c; + nc->SetCurve(curve); + if (!hasLiquscent) { + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); + } + } + else { + // For unset curve + curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; + nc->SetCurve(curve); + if (hasLiquscent) { + Liquescent *liquescent = vrv_cast(nc->FindDescendantByType(LIQUESCENT)); + nc->DeleteChild(liquescent); + } + } + + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); + return true; +} + bool EditorToolkitNeume::Split(std::string elementId, int x) { if (!m_doc->GetDrawingPage()) { @@ -3859,6 +3910,21 @@ bool EditorToolkitNeume::ParseSetClefAction(jsonxx::Object param, std::string *e return true; } +bool EditorToolkitNeume::ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *curve) +{ + if (!param.has("elementId")) { + LogWarning("Could not parse 'elementId'"); + return false; + } + *elementId = param.get("elementId"); + if (!param.has("curve")) { + LogWarning("Could not parse 'curve'"); + return false; + } + *curve = param.get("curve"); + return true; +} + bool EditorToolkitNeume::ParseRemoveAction(jsonxx::Object param, std::string *elementId) { if (!param.has("elementId")) return false; From 8b97bf9dee994bfd23d18488e163b04dc2fece99 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Jan 2024 14:08:48 -0500 Subject: [PATCH 032/383] Handle liquescent in DrawNc() --- src/view_neume.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 1b972bab007..e62afa9787c 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -177,7 +177,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; } - else if (nc->GetCurve() == curvatureDirection_CURVE_c) { + else if (nc->GetCurve() == curvatureDirection_CURVE_c && nc->FindDescendantByType(LIQUESCENT)) { params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; @@ -187,7 +187,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).yOffsetLiq[0] = -1.5; params.at(0).yOffsetLiq[4] = -1.75; } - else if (nc->GetCurve() == curvatureDirection_CURVE_a) { + else if (nc->GetCurve() == curvatureDirection_CURVE_a && nc->FindDescendantByType(LIQUESCENT)) { params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; @@ -274,7 +274,9 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - this->DrawLayerChildren(dc, nc, layer, staff, measure); + if (!nc->FindDescendantByType(LIQUESCENT)) { + this->DrawLayerChildren(dc, nc, layer, staff, measure); + } dc->EndGraphic(element, this); } From 26dafc2af6809b59a15d8ad3aedbb8d03cb7218c Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Feb 2024 17:09:58 -0500 Subject: [PATCH 033/383] Add DrawLiquescent() --- include/vrv/view.h | 1 + src/view_element.cpp | 3 + src/view_neume.cpp | 145 ++++++++++++++++++++++++++++++++----------- 3 files changed, 113 insertions(+), 36 deletions(-) diff --git a/include/vrv/view.h b/include/vrv/view.h index c9137403e20..fffae2d7348 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -416,6 +416,7 @@ class View { ///@{ void DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); + void DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNeume(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); ///@} diff --git a/src/view_element.cpp b/src/view_element.cpp index 51ed9b647ad..abad3b08e88 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -139,6 +139,9 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay else if (element->Is(LIGATURE)) { this->DrawLigature(dc, element, layer, staff, measure); } + else if (element->Is(LIQUESCENT)) { + this->DrawLiquescent(dc, element, layer, staff, measure); + } else if (element->Is(MENSUR)) { this->DrawMensur(dc, element, layer, staff, measure); } diff --git a/src/view_neume.cpp b/src/view_neume.cpp index e62afa9787c..c593605b805 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -56,6 +56,110 @@ void View::DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, dc->EndGraphic(element, this); } +void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) +{ + assert(dc); + assert(layer); + assert(staff); + assert(measure); + + Liquescent *liquescent = dynamic_cast(element); + assert(liquescent); + + struct drawingParams { + wchar_t fontNo = SMUFL_E990_chantPunctum; + wchar_t fontNoLiq[5] = {}; + float xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + float yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + }; + std::vector params; + params.push_back(drawingParams()); + + dc->StartGraphic(element, "", element->GetID()); + + Clef *clef = layer->GetClef(element); + int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int staffLineNumber = staff->m_drawingLines; + int clefLine = clef->GetLine(); + + Nc *nc = dynamic_cast(element->GetParent()); + assert(liquescent); + + if (nc->GetCurve() == curvatureDirection_CURVE_c) { + params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; + params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; + params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; + params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).xOffsetLiq[4] = 0.8; + params.at(0).yOffsetLiq[0] = -1.5; + params.at(0).yOffsetLiq[4] = -1.75; + } + else if (nc->GetCurve() == curvatureDirection_CURVE_a) { + params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; + params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; + params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; + params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).xOffsetLiq[4] = 0.8; + params.at(0).yOffsetLiq[0] = 0.5; + params.at(0).yOffsetLiq[4] = 0.75; + } + + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + int noteY, noteX; + int yValue; + if (nc->HasFacs() && m_doc->IsFacs()) { + noteY = ToLogicalY(staff->GetDrawingY()); + noteX = nc->GetDrawingX(); + } + else { + noteX = element->GetDrawingX(); + noteY = element->GetDrawingY(); + } + + // Calculating proper y offset based on pname, clef, staff, and staff rotate + int clefYPosition = noteY - (staffSize * (staffLineNumber - clefLine)); + int pitchOffset = 0; + + // The default octave = 3, but the actual octave is calculated by + // taking into account the displacement of the clef + int clefOctave = 3; + if (clef->GetDis() && clef->GetDisPlace()) { + clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); + } + int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); + int rotateOffset; + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = noteX - staff->GetDrawingX(); + rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + } + else { + rotateOffset = 0; + } + + if (clef->GetShape() == CLEFSHAPE_C) { + pitchOffset = (nc->GetPname() - 1) * (staffSize / 2); + } + else if (clef->GetShape() == CLEFSHAPE_F) { + pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); + } + yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; + + for (auto it = params.begin(); it != params.end(); it++) { + for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { + DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, + it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); + } + } + + dc->EndGraphic(element, this); +} + void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) { assert(dc); @@ -74,10 +178,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff struct drawingParams { wchar_t fontNo = SMUFL_E990_chantPunctum; wchar_t fontNoLiq[5] = {}; - double xOffset = 0; - double yOffset = 0; - double xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - double yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + float xOffset = 0; + float yOffset = 0; }; std::vector params; params.push_back(drawingParams()); @@ -177,27 +279,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; } - else if (nc->GetCurve() == curvatureDirection_CURVE_c && nc->FindDescendantByType(LIQUESCENT)) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; - params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; - params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = -1.5; - params.at(0).yOffsetLiq[4] = -1.75; - } - else if (nc->GetCurve() == curvatureDirection_CURVE_a && nc->FindDescendantByType(LIQUESCENT)) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; - params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; - params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = 0.5; - params.at(0).yOffsetLiq[4] = 0.75; - } - const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth @@ -251,14 +332,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; } - for (auto it = params.begin(); it != params.end(); it++) { - if (nc->GetCurve() == curvatureDirection_CURVE_a || nc->GetCurve() == curvatureDirection_CURVE_c) { - for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { - DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, - it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); - } - } - else { + if (!nc->HasCurve()) { + for (auto it = params.begin(); it != params.end(); it++) { DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, yValue + it->yOffset * noteHeight, it->fontNo, staff->m_drawingStaffSize, false, true); } @@ -274,9 +349,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - if (!nc->FindDescendantByType(LIQUESCENT)) { - this->DrawLayerChildren(dc, nc, layer, staff, measure); - } + this->DrawLayerChildren(dc, nc, layer, staff, measure); dc->EndGraphic(element, this); } From 1e0177cb063fb005f9a0b7cf8492b3acec57a064 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Feb 2024 17:20:14 -0500 Subject: [PATCH 034/383] Skip liquescents for pitch adjustment --- src/editortoolkit_neume.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 1fe87d1b6bd..0118c09f510 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -4154,6 +4154,8 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); for (auto it = pitchedChildren.begin(); it != pitchedChildren.end(); ++it) { + if ((*it)->Is(LIQUESCENT)) continue; + FacsimileInterface *fi = (*it)->GetFacsimileInterface(); if (fi == NULL || !fi->HasFacs()) { LogError("Could not adjust pitch: child %s does not have facsimile data", (*it)->GetID().c_str()); From fbf2ec97d2aa1aa2e7948aab61f38f2cb7633327 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Jan 2024 14:08:48 -0500 Subject: [PATCH 035/383] Handle liquescent in DrawNc() --- src/view_neume.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index c593605b805..63840290216 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -349,7 +349,9 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - this->DrawLayerChildren(dc, nc, layer, staff, measure); + if (!nc->FindDescendantByType(LIQUESCENT)) { + this->DrawLayerChildren(dc, nc, layer, staff, measure); + } dc->EndGraphic(element, this); } From 8a32b8a8bbd17675811d8aee347f6157d63a1673 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Feb 2024 17:09:58 -0500 Subject: [PATCH 036/383] Add DrawLiquescent() --- src/view_neume.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 63840290216..c593605b805 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -349,9 +349,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - if (!nc->FindDescendantByType(LIQUESCENT)) { - this->DrawLayerChildren(dc, nc, layer, staff, measure); - } + this->DrawLayerChildren(dc, nc, layer, staff, measure); dc->EndGraphic(element, this); } From 136335316b88554122bc59469c9fc308d9fe7bbf Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 14 Feb 2024 15:51:56 -0500 Subject: [PATCH 037/383] Use while loop for consecutive layer elements inside syllable --- src/editortoolkit_neume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 0118c09f510..a2b5ff7460e 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2910,7 +2910,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } } - if (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { + while (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { fparent = el->GetFirstAncestor(SYLLABLE); sparent = el->GetFirstAncestor(LAYER); if (fparent && sparent) { From 5922773c246e4c6cc2b51b910640174971a19b26 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 16 Feb 2024 16:36:14 -0500 Subject: [PATCH 038/383] Break loop when reach end of syllable --- src/editortoolkit_neume.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index a2b5ff7460e..8f8885c8a33 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2836,6 +2836,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector ListOfObjects syllables; // List of syllables used. groupType=neume only. jsonxx::Array uuidArray; + bool breakOnEnd = false; // Check if you can get drawing page if (!m_doc->GetDrawingPage()) { @@ -2920,10 +2921,15 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector fparent->ReorderByXPos(); uuidArray << (*it); it = elementIds.erase(it); - if (it == elementIds.end()) break; + if (it == elementIds.end()) { + breakOnEnd = true; + break; + } el = m_doc->GetDrawingPage()->FindDescendantByID(*it); } } + if (breakOnEnd) break; + if (elementIds.begin() == it || firstIsSyl) { // if the element is a syl we want it to stay attached to the first element // we'll still need to initialize all the parents, thus the bool From 8dcc9e986b52044c948f6ba63c1e50a56d6d5a13 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 16 Feb 2024 17:21:53 -0500 Subject: [PATCH 039/383] Remove redundant text for new syl in SetText() --- src/editortoolkit_neume.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 8f8885c8a33..0aa9f3fe447 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1895,11 +1895,7 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) std::u32string str = U""; text->SetText(str); syl->AddChild(text); - syllable->AddChild(syl); - Text *textChild = new Text(); - textChild->SetText(wtext); - syl->AddChild(textChild); if (m_doc->GetType() == Facs) { // Create a default bounding box Zone *zone = new Zone(); From 4f5eb510d4eb0823a7888865a212ca2157240af0 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 15 Mar 2024 18:09:48 -0400 Subject: [PATCH 040/383] Add layer element to SyncFromFacsimileFunctor and SyncToFacsimileFunctor --- src/facsimilefunctor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 83f53d6b275..7bac6c4e1ec 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -40,7 +40,7 @@ SyncFromFacsimileFunctor::SyncFromFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ CLEF, CUSTOS, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ ACCID, CLEF, CUSTOS, DIVLINE, LIQUESCENT, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; Zone *zone = layerElement->GetZone(); assert(zone); @@ -187,7 +187,7 @@ SyncToFacsimileFunctor::SyncToFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncToFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ ACCID, CLEF, CUSTOS, DIVLINE, LIQUESCENT, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; Zone *zone = this->GetZone(layerElement, layerElement->GetClassName()); zone->SetUlx(m_view.ToDeviceContextX(layerElement->GetDrawingX()) / DEFINITION_FACTOR + m_pageMarginLeft); From c97cbf0d582c8332d2c4f02bef9e303b32b963dd Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 15 Mar 2024 18:10:07 -0400 Subject: [PATCH 041/383] Remove unused code --- src/layerelement.cpp | 16 ---------------- src/staff.cpp | 7 ------- 2 files changed, 23 deletions(-) diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 459d4c505df..17d0aa31cd4 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -396,14 +396,6 @@ void LayerElement::SetGraceAlignment(Alignment *graceAlignment) int LayerElement::GetDrawingX() const { - // If this element has a facsimile and we are in facsimile mode, use Facsimile::GetDrawingX - if (this->HasFacs()) { - const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); - assert(doc); - if (doc->IsFacs()) { - return FacsimileInterface::GetDrawingX(); - } - } // Since m_drawingFacsX is the left position, we adjust the XRel accordingly in AdjustXRelForTranscription if (m_drawingFacsX != VRV_UNSET) return m_drawingFacsX + this->GetDrawingXRel(); @@ -444,14 +436,6 @@ int LayerElement::GetDrawingX() const int LayerElement::GetDrawingY() const { - // If this element has a facsimile and we are in facsimile mode, use Facsimile::GetDrawingY - if (this->HasFacs()) { - const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); - assert(doc); - if (doc->IsFacs()) { - return FacsimileInterface::GetDrawingY(); - } - } if (m_cachedDrawingY != VRV_UNSET) return m_cachedDrawingY; diff --git a/src/staff.cpp b/src/staff.cpp index 1e3c1f00a52..5ba6a4a3dad 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -142,13 +142,6 @@ int Staff::GetDrawingX() const int Staff::GetDrawingY() const { - if (this->HasFacs()) { - const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); - assert(DOC); - if (doc->IsFacs()) { - return FacsimileInterface::GetDrawingY(); - } - } if (m_drawingFacsY != VRV_UNSET) return m_drawingFacsY; From 0946c1fcad6663d3a259bb4755068b18f586704e Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 15 Mar 2024 18:42:36 -0400 Subject: [PATCH 042/383] Use m_doc->HasFacsimile() instead of file type --- src/editortoolkit_neume.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 0aa9f3fe447..e123883be09 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -752,7 +752,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -897,7 +897,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in zone->SetLry(uly + noteHeight); // add syl bounding box if Facs - if (m_doc->GetType() == Facs) { + if (!m_doc->HasFacsimile()) { FacsimileInterface *fi = vrv_cast(syl->GetFacsimileInterface()); assert(fi); sylZone = new Zone(); @@ -1271,7 +1271,7 @@ bool EditorToolkitNeume::InsertToSyllable(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1421,7 +1421,7 @@ bool EditorToolkitNeume::MoveOutsideSyllable(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1621,7 +1621,7 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1826,7 +1826,7 @@ bool EditorToolkitNeume::Set(std::string elementId, std::string attrType, std::s success = true; else if (AttModule::SetVisual(element, attrType, attrValue)) success = true; - if (success && m_doc->GetType() != Facs) { + if (success && !m_doc->HasFacsimile()) { m_doc->PrepareData(); m_doc->GetDrawingPage()->LayOut(true); } @@ -1896,7 +1896,7 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) text->SetText(str); syl->AddChild(text); syllable->AddChild(syl); - if (m_doc->GetType() == Facs) { + if (!m_doc->HasFacsimile()) { // Create a default bounding box Zone *zone = new Zone(); int ulx, uly, lrx, lry; @@ -1990,7 +1990,7 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) pi->AdjustPitchByOffset(shift); } } - if (success && m_doc->GetType() != Facs) { + if (success && !m_doc->HasFacsimile()) { m_doc->PrepareData(); m_doc->GetDrawingPage()->LayOut(true); } @@ -2165,7 +2165,7 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) linkedSyllable->AddChild(syl); // Create default bounding box if facs - if (m_doc->GetType() == Facs) { + if (!m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx( @@ -2335,7 +2335,7 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx m_editInfo.import("message", "Could not get the drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogWarning("Resizing is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Resizing is only available in facsimile mode."); @@ -2644,7 +2644,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e parent->AddChild(syl); // add a default bounding box if you need to - if (m_doc->GetType() == Facs) { + if (!m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx(parent->GetFirst(NEUME)->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -2702,7 +2702,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e std::u32string fullString = U""; for (auto it = fullParents.begin(); it != fullParents.end(); ++it) { Syl *syl = dynamic_cast((*it)->FindDescendantByType(SYL)); - if (syl != NULL && m_doc->GetType() == Facs) { + if (syl != NULL && !m_doc->HasFacsimile()) { Zone *zone = dynamic_cast(syl->GetFacsimileInterface()->GetZone()); if (fullSyl == NULL) { @@ -2736,7 +2736,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e fullText->SetText(fullString); parent->AddChild(fullSyl); - if (m_doc->GetType() == Facs) { + if (!m_doc->HasFacsimile()) { Zone *zone = dynamic_cast(fullSyl->GetFacsimileInterface()->GetZone()); zone->SetUlx(ulx); zone->SetUly(uly); @@ -3027,7 +3027,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector newParent->AddChild(syl); // Create default bounding box if facs - if (m_doc->GetType() == Facs) { + if (!m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx(el->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -3364,7 +3364,7 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) // m_editInfo.import("message", "isLigature value '" + isLigature + "' is invalid."); // return false; // } - if (success1 && success2 && m_doc->GetType() != Facs) { + if (success1 && success2 && !m_doc->HasFacsimile()) { m_doc->PrepareData(); m_doc->GetDrawingPage()->LayOut(true); } @@ -3389,7 +3389,7 @@ bool EditorToolkitNeume::ChangeStaff(std::string elementId) return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogWarning("Staff re-association is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Staff re-association is only available in facsimile mode."); @@ -3594,7 +3594,7 @@ bool EditorToolkitNeume::ChangeStaffTo(std::string elementId, std::string staffI return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogWarning("Staff re-association is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Staff re-association is only available in facsimile mode."); From e92578736952d50709b257277bbbcfba3c8b1b7f Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 15 Mar 2024 20:12:47 -0400 Subject: [PATCH 043/383] Skip writing coordX1 for transcription with facs --- src/iomei.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iomei.cpp b/src/iomei.cpp index 20d79472273..e02dc730a74 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2313,7 +2313,7 @@ void MEIOutput::WriteLayerElement(pugi::xml_node currentNode, LayerElement *elem this->WriteLinkingInterface(currentNode, element); element->WriteLabelled(currentNode); element->WriteTyped(currentNode); - if (element->m_drawingFacsX != VRV_UNSET) { + if (element->m_drawingFacsX != VRV_UNSET && !(m_doc->IsTranscription() && m_doc->HasFacsimile())) { element->SetCoordX1(element->m_drawingFacsX / DEFINITION_FACTOR); element->WriteCoordX1(currentNode); } From cf605aba147a6cc86ff498a831f5d9ca67a41e78 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 18 Mar 2024 15:24:45 -0400 Subject: [PATCH 044/383] Clean up View::DrawClef --- src/view_element.cpp | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index abad3b08e88..bd7b0ee9879 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -659,14 +659,8 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf } int x, y; - if (m_doc->IsFacs() && clef->HasFacs()) { - y = ToLogicalY(staff->GetDrawingY()); - x = clef->GetDrawingX(); - } - else { - y = staff->GetDrawingY(); - x = element->GetDrawingX(); - } + y = staff->GetDrawingY(); + x = element->GetDrawingX(); char32_t sym = clef->GetClefGlyph(staff->m_drawingNotationType); @@ -677,12 +671,7 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (clef->HasLine()) { y -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - clef->GetLine()); - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - y -= int(xDiff * tan(deg * M_PI / 180.0)); - } - else if (staff->HasDrawingRotation()) { + if (staff->HasDrawingRotation()) { y -= staff->GetDrawingRotationOffsetFor(x); } } @@ -705,7 +694,7 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); - if (m_doc->IsFacs() && element->HasFacs()) { + if (m_doc->IsTranscription() && element->HasFacs()) { const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth From 471e36a95aa86797afde30b11d54bca65aa275db Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 18 Mar 2024 15:55:59 -0400 Subject: [PATCH 045/383] Clean up View::DrawDivLine --- src/view_neume.cpp | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index c593605b805..f98e05fdda2 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -437,9 +437,6 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S DivLine *divLine = dynamic_cast(element); assert(divLine); - // int x = divLine->GetDrawingX(); - // int y = divLine->GetDrawingY(); - dc->StartGraphic(element, "", element->GetID()); int sym = 0; @@ -455,29 +452,14 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S } int x, y; - if (m_doc->IsFacs() && (divLine->HasFacs())) { - x = divLine->GetDrawingX(); - y = ToLogicalY(staff->GetDrawingY()); - } - else { - x = element->GetDrawingX(); - y = element->GetDrawingY(); - y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - } + x = divLine->GetDrawingX(); + y = staff->GetDrawingY(); y -= (m_doc->GetDrawingUnit(staff->m_drawingStaffSize)) * 3; - int rotationOffset = 0; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); + if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); } - else if (staff->HasDrawingRotation()) { - rotationOffset = staff->GetDrawingRotationOffsetFor(x); - } - - y -= rotationOffset; DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); From 66bbaba5e593f40b618f24fbad831e7f4834412f Mon Sep 17 00:00:00 2001 From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:54:14 -0700 Subject: [PATCH 046/383] Fix typo that prevented mxhm data from being properly read into Verovio. Get basic above/below working for mxhm as well (and stop defaulting to below, instead default to unspecified). --- src/iohumdrum.cpp | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 05f6cc3f45c..cdfdb7d4951 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -856,6 +856,7 @@ bool HumdrumInput::convertHumdrum() m_harm = true; } else if (it->isDataType("**mxhm")) { + analyzeHarmInterpretations(it); m_harm = true; } else if (it->isDataType("**fing")) { @@ -1188,15 +1189,23 @@ bool HumdrumInput::hasNoStaves(hum::HumdrumFile &infile) void HumdrumInput::analyzeHarmInterpretations(hum::HTp starttok) { bool aboveQ = false; + bool belowQ = false; + bool ignoreLabels = false; hum::HumRegex hre; if (hre.search(starttok->getDataType(), "^\\*\\*adata")) { aboveQ = true; } + else if (hre.search(starttok->getDataType(), "^\\*\\*bdata")) { + belowQ = true; + } + else if (hre.search(starttok->getDataType(), "^\\*\\*mxhm")) { + ignoreLabels = true; + } hum::HTp keydesig = NULL; hum::HTp verselabel = NULL; hum::HTp current = starttok; std::string initialLabel = ""; - if (hre.search(current->getDataType(), "^\\*\\*[ab]data-(.*)")) { + if (!ignoreLabels && hre.search(current->getDataType(), "^\\*\\*[ab]data-(.*)")) { initialLabel = hre.getMatch(1); } @@ -1209,6 +1218,14 @@ void HumdrumInput::analyzeHarmInterpretations(hum::HTp starttok) if (aboveQ) { current->setValue("auto", "above", 1); } + else if (belowQ) { + current->setValue("auto", "below", 1); + } + + if (ignoreLabels) { + continue; + } + if (keydesig && !keydesig->empty()) { std::string label = keydesig->substr(1); if (!label.empty()) { @@ -1239,13 +1256,21 @@ void HumdrumInput::analyzeHarmInterpretations(hum::HTp starttok) if (!current->isInterpretation()) { continue; } + if (*current == "*above") { + belowQ = false; aboveQ = true; } else if (*current == "*below") { aboveQ = false; + belowQ = true; } - else if (current->isKeyDesignation()) { + + if (ignoreLabels) { + continue; + } + + if (current->isKeyDesignation()) { keydesig = current; } else if (hre.search(current, "^\\*v[bi]*:")) { @@ -10821,12 +10846,20 @@ void HumdrumInput::addHarmFloatsForMeasure(int startline, int endline) harm->SetTstamp(tstamp.getFloat()); // Place harm data above/below staff: - std::string place = "below"; + std::string place; int aboveQ = token->getValueInt("auto", "above"); if (aboveQ) { place = "above"; } - setPlaceRelStaff(harm, place, false); + else { + int belowQ = token->getValueInt("auto", "below"); + if (belowQ) { + place = "below"; + } + } + if (place.size() > 0) { + setPlaceRelStaff(harm, place, false); + } // Add key label (for harm/rhrm/deg/degree data) if (isCData || isHarm || isDegree) { @@ -10858,7 +10891,7 @@ void HumdrumInput::addHarmFloatsForMeasure(int startline, int endline) else if (datatype == "**rhrm") { setHarmContent(harmRend, token); } - else if (datatype == "*mxhm") { + else if (datatype == "**mxhm") { setMxHarmContent(harmRend, *token); } else if (isDegree) { From d9a473925546ef27c6275565e63bd87f7dc2a173 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 9 Apr 2024 11:01:25 -0700 Subject: [PATCH 047/383] Humlib updates --- include/hum/humlib.h | 62 ++- src/hum/humlib.cpp | 946 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1002 insertions(+), 6 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index aa992215ff4..a636e8698a5 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Jan 23 21:56:32 PST 2024 +// Last Modified: Thu Apr 4 23:30:44 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -7663,6 +7663,33 @@ class Tool_hproof : public HumTool { +class Tool_humbreak : public HumTool { + public: + Tool_humbreak (void); + ~Tool_humbreak () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void addBreaks (HumdrumFile& infile); + void removeBreaks (HumdrumFile& infile); + void convertPageToLine (HumdrumFile& infile); + + private: + std::map m_lineMeasures; // list of measures to add line breaks to + std::map m_pageMeasures; // list of measures to add page breaks to + std::string m_group = "original"; + bool m_removeQ = false; + bool m_page2lineQ = false; + +}; + + // A TimePoint records the event times in a file. These are positions of note attacks // in the file. The "index" variable keeps track of the line in the original file // (for the first position in index), and other positions in index keep track of the @@ -9766,6 +9793,39 @@ class Tool_ruthfix : public HumTool { }; +class Tool_sab2gs : public HumTool { + public: + Tool_sab2gs (void); + ~Tool_sab2gs () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + + void adjustMiddleVoice (HTp spineStart); + void printGrandStaff (HumdrumFile& infile, std::vector& starts); + std::string hasBelowMarker(HumdrumFile& infile); + + void printReducedLine (HumdrumFile& infile, int index, std::vector& ktracks); + void printSpineMerge (HumdrumFile& infile, int index, std::vector& ktracks); + void printSpineSplit (HumdrumFile& infile, int index, std::vector& ktracks); + void printSwappedLine (HumdrumFile& infile, int index, std::vector& ktracks); + + private: + bool m_hasCrossStaff = false; // Middle voice has notes/rests on bottom staff + bool m_hasBelowMarker = false; // Input data has RDF**kern down marker + string m_belowMarker = "<"; // RDF**kern marker for staff down + bool m_downQ = false; // Used only *down/*Xdown for staff changes + + +}; + + class Tool_satb2gs : public HumTool { public: Tool_satb2gs (void); diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index a56fc4a468a..337cd5a0b0c 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Jan 23 21:56:32 PST 2024 +// Last Modified: Thu Apr 4 23:30:44 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -24655,10 +24655,15 @@ void HumdrumFileContent::linkPhraseEndpoints(HTp phrasestart, HTp phraseend) { void HumdrumFileContent::analyzeRestPositions(void) { vector kernstarts = getKernSpineStartList(); - for (int i=0; i<(int)kernstarts.size(); i++) { - assignImplicitVerticalRestPositions(kernstarts[i]); - } + // Now using verovio automatic rest positions, so not calcualting + // by default anymore. This code can be uncommented out if explicit + // rest positions are needed for other purposes. + //for (int i=0; i<(int)kernstarts.size(); i++) { + // assignImplicitVerticalRestPositions(kernstarts[i]); + //} + + // Check for explicit positioning: checkForExplicitVerticalRestPositions(); } @@ -27433,6 +27438,10 @@ bool HumdrumFileStructure::analyzeStrophes(void) { if (*current == "*Xstrophe") { break; } + if (*current == "*S-") { + // Alternate for *Xstrophe + break; + } current->setStrophe(strophestarts[i]); current = current->getNextToken(); } @@ -79886,6 +79895,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(homorhythm2, infile, commands[i].second, status); } else if (commands[i].first == "hproof") { RUNTOOL(hproof, infile, commands[i].second, status); + } else if (commands[i].first == "humbreak") { + RUNTOOL(humbreak, infile, commands[i].second, status); } else if (commands[i].first == "humsheet") { RUNTOOL(humsheet, infile, commands[i].second, status); } else if (commands[i].first == "humtr") { @@ -79922,6 +79933,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(recip, infile, commands[i].second, status); } else if (commands[i].first == "restfill") { RUNTOOL(restfill, infile, commands[i].second, status); + } else if (commands[i].first == "sab2gs") { + RUNTOOL(sab2gs, infile, commands[i].second, status); } else if (commands[i].first == "scordatura") { RUNTOOL(scordatura, infile, commands[i].second, status); } else if (commands[i].first == "semitones") { @@ -83657,6 +83670,233 @@ void Tool_hproof::markHarmonicTones(HTp tok, vector& cts) { +///////////////////////////////// +// +// Tool_humbreak::Tool_humbreak -- Set the recognized options for the tool. +// + +Tool_humbreak::Tool_humbreak(void) { + define("m|measures=s", "Measures numbers to place linebreaks before"); + define("p|page-breaks=s", "Measure numbers to place page breaks before"); + define("g|group=s:original", "Line/page break group"); + define("r|remove|remove-breaks=b", "Remove line/page breaks"); + define("l|page-to-line-breaks=b", "Convert page breaks to line breaks"); +} + + + +///////////////////////////////// +// +// Tool_humbreak::run -- Do the main work of the tool. +// + +bool Tool_humbreak::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i lbs; + vector pbs; + HumRegex hre; + hre.split(lbs, systemMeasures, "[^\\d]+"); + hre.split(pbs, pageMeasures, "[^\\d]+"); + + for (int i=0; i<(int)lbs.size(); i++) { + int number = std::stoi(lbs[i]); + m_lineMeasures[number] = 1; + } + + for (int i=0; i<(int)pbs.size(); i++) { + int number = std::stoi(lbs[i]); + m_pageMeasures[number] = 1; + } +} + + +////////////////////////////// +// +// Tool_humbreak::addBreaks -- +// + +void Tool_humbreak::addBreaks(HumdrumFile& infile) { + + HumRegex hre; + for (int i=0; icompare(0, 8, "!!LO:PB:") == 0)) { + // Add group to existing LO:PB: + HTp token = infile.token(i, 0); + HTp barToken = infile.token(i+1, 0); + if (barToken->isBarline()) { + int measure = infile[i+1].getBarNumber(); + int pbStatus = m_pageMeasures[measure]; + if (pbStatus) { + string query = "\\b" + m_group + "\\b"; + if (!hre.match(token, query)) { + m_humdrum_text << token << ", " << m_group << endl; + } else { + m_humdrum_text << token << endl; + } + } else { + m_humdrum_text << token << endl; + } + m_humdrum_text << infile[i+1] << endl; + i++; + continue; + } + } + + if ((icompare(0, 8, "!!LO:LB:") == 0)) { + // Add group to existing LO:LB: + HTp token = infile.token(i, 0); + HTp barToken = infile.token(i+1, 0); + if (barToken->isBarline()) { + int measure = infile[i+1].getBarNumber(); + int lbStatus = m_lineMeasures[measure]; + if (lbStatus) { + string query = "\\b" + m_group + "\\b"; + if (!hre.match(token, query)) { + m_humdrum_text << token << ", " << m_group << endl; + } else { + m_humdrum_text << token << endl; + } + } else { + m_humdrum_text << token << endl; + } + m_humdrum_text << infile[i+1] << endl; + i++; + continue; + } + } + + if (!infile[i].isBarline()) { + m_humdrum_text << infile[i] << endl; + continue; + } + + int measure = infile[i].getBarNumber(); + int pbStatus = m_pageMeasures[measure]; + int lbStatus = m_lineMeasures[measure]; + + + if (pbStatus) { + m_humdrum_text << "!!LO:PB:g=" << m_group << endl; + } else if (lbStatus) { + m_humdrum_text << "!!LO:LB:g=" << m_group << endl; + } + + m_humdrum_text << infile[i] << endl; + } +} + + + + +////////////////////////////// +// +// Tool_humbreak::processFile -- +// + +void Tool_humbreak::processFile(HumdrumFile& infile) { + initialize(); + if (m_removeQ) { + removeBreaks(infile); + } else if (m_page2lineQ) { + convertPageToLine(infile); + } else { + addBreaks(infile); + } +} + + + +////////////////////////////// +// +// Tool_humbreak::removeBreaks -- +// + +void Tool_humbreak::removeBreaks(HumdrumFile& infile) { + for (int i=0; icompare(0, 7, "!!LO:LB") == 0) { + continue; + } + if (infile[i].token(0)->compare(0, 7, "!!LO:PB") == 0) { + continue; + } + m_humdrum_text << infile[i] << endl; + } +} + + + +////////////////////////////// +// +// Tool_humbreak::convertPageToLine -- +// + +void Tool_humbreak::convertPageToLine(HumdrumFile& infile) { + HumRegex hre; + for (int i=0; icompare(0, 7, "!!LO:PB") == 0) { + string text = *infile[i].token(0); + hre.replaceDestructive(text, "!!LO:LB", "!!LO:PB"); + m_humdrum_text << text << endl; + continue; + } + m_humdrum_text << infile[i] << endl; + } +} + + + + ///////////////////////////////// // // Tool_humdiff::Tool_humdiff -- Set the recognized options for the tool. @@ -85428,7 +85668,7 @@ void Tool_humtr::initialize(void) { } if (getBoolean("popc")) { - addFromToCombined("ſ:s ʃ:s ſ:s ν:u ί:í α:a ť:k ᴣ:z ʓ:z̨ ʒ̇:ż ʒ́:ź Ʒ̇:Ż Ʒ́:Ź ӡ:z Ʒ:Z æ:ae"); + addFromToCombined("ſ:s ʃ:s ſ:s ν:u ί:í α:a ť:k ᴣ:z ʓ:z̨ ʒ̇:ż ʒ́:ź Ʒ̇:Ż Ʒ́:Ź ӡ:z Ʒ:Z Ӡ:Z æ:ae"); } } @@ -111136,6 +111376,702 @@ void Tool_ruthfix::createTiedNote(HTp left, HTp right) { +///////////////////////////////// +// +// Tool_sab2gs::Tool_sab2gs -- Set the recognized options for the tool. +// + +Tool_sab2gs::Tool_sab2gs(void) { + define("b|below=s:<", "Marker for displaying on next staff below"); + define("d|down=b", "Use only *down/*Xdown interpretations"); +} + + + +///////////////////////////////// +// +// Tool_sab2gs::run -- Do the main work of the tool. +// + +bool Tool_sab2gs::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i spines; + infile.getSpineStartList(spines); + vector kernSpines; + for (int i=0; i<(int)spines.size(); i++) { + if (spines[i]->isKern()) { + kernSpines.push_back(spines[i]); + } + } + if (kernSpines.size() != 3) { + // Not valid for processing kern spines, so return original: + m_humdrum_text << infile; + return; + } + + string belowMarker = hasBelowMarker(infile); + if (!belowMarker.empty()) { + m_hasBelowMarker = true; + m_belowMarker = belowMarker; + } + + adjustMiddleVoice(kernSpines[1]); + printGrandStaff(infile, kernSpines); +} + + + +///////////////////////////// +// +// Tool_sab2gs::hasBelowMarker -- Returns below marker if found; otherwise, +// returns empty string. +// + +string Tool_sab2gs::hasBelowMarker(HumdrumFile& infile) { + string output; + HumRegex hre; + if (m_hasCrossStaff) { + // Search backwards since if there is a below marker, it will be more + // likely found at the bottom of the score. + for (int i=infile.getLineCount()-1; i<=0; i--) { + if (infile[i].hasSpines()) { + continue; + } + if (hre.search(infile.token(i, 0), "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=]+)\\s*=\\s*below\\s*$")) { + output = hre.getMatch(1); + break; + } + } + } + return output; +} + + + +/////////////////////////////// +// +// Tool_sab2gs::printGrandStaff -- +// + +void Tool_sab2gs::printGrandStaff(HumdrumFile& infile, vector& starts) { + bool foundData = false; + + vector ktracks(starts.size()); + for (int i=0; i<(int)starts.size(); i++) { + ktracks.at(i) = starts.at(i)->getTrack(); + } + + for (int i=0; i& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; + } + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << "*"; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Ignore the second kern spine as it does not exist yet in the + // output data. + nextIndex++; + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += "*"; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << "*^"; + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << "*"; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; +} + + + +////////////////////////////// +// +// Tool_sab2gs::printSpineMerge -- Merge second and third spines, moving non-kern spines +// after the second one to the end of the line (null interpretations); +// + +void Tool_sab2gs::printSpineMerge(HumdrumFile& infile, int index, vector& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; + } + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << "*"; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Save the second kern spine as it does not exist yet in the + // output data. + // HTp savedKernToken = infile.token(index, nextIndex); + nextIndex++; + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += "*"; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*v"; + nextIndex++; + // Now printed the saved second **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << "*v"; + fcount++; + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << "*"; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; +} + + + +////////////////////////////// +// +// Tool_sab2gs::printSwappedLine -- move the second **kern spine immediately after +// the third one, and move any non-kern spines after then end of the line. +// + +void Tool_sab2gs::printSwappedLine(HumdrumFile& infile, int index, vector& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << token; + nextIndex++; + } + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << infile.token(index, nextIndex); + nextIndex++; + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << token; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Save the second kern spine as it does not exist yet in the + // output data. + HTp savedKernToken = infile.token(index, nextIndex++); + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += *token; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << infile.token(index, nextIndex++); + // Now printed the saved second **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << savedKernToken; + fcount++; + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << token; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; +} + + + +////////////////////////////// +// +// Tool_sab2gs::printReducedLine -- remove the contents of the second **kern +// spine, and move any non-kernspines after it to become after the third **kern spine +// + +void Tool_sab2gs::printReducedLine(HumdrumFile& infile, int index, vector& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << token; + nextIndex++; + } + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << infile.token(index, nextIndex++); + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << token; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Ignore the second kern spine as it does not exist yet in the + // output data. + nextIndex++; + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += *token; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << infile.token(index, nextIndex++); + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << token; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; +} + + +////////////////////////////// +// +// Tool_sab2gs::adjustMiddleVoice -- +// + +void Tool_sab2gs::adjustMiddleVoice(HTp spineStart) { + HTp current = spineStart; + // staff: +1 = top staff, -1 = bottom staff + // when on top staff, force stem down, or on bottom staff, force stem up + // when on bottom staff add "<" marker after pitch (or rest) to move to + // bottom staff. Staff choice is selected by clef: clefG2 is for top staff + // and staffF4 is for bottom staff. Chords are not expected. + int staff = 0; + string replacement = "$1" + m_belowMarker; + HumRegex hre; + while (current) { + if (*current == "*-") { + break; + } + if (!m_downQ && current->isClef()) { + if (current->substr(0, 7) == "*clefG2") { + staff = 1; + // suppress clef: + string text = "*x" + current->substr(1); + current->setText(text); + } else if (current->substr(0, 7) == "*clefF4") { + staff = -1; + // suppress clef: + string text = "*x" + current->substr(1); + current->setText(text); + } + } else if (current->isInterpretation()) { + if (*current == "*down") { + staff = -1; + } else if (*current == "*Xdown") { + staff = 1; + } + } else if ((staff != 0) && current->isData()) { + if (current->isNull()) { + // nothing to do with token + current = current->getNextToken(); + continue; + } + if (staff > 0) { + // force stems down or add stem down to non-rest notes + if (hre.search(current, "[/\\\\]")) { + string value = hre.replaceCopy(current, "\\", "/", "g"); + if (value != *current) { + current->setText(value); + } + current = current->getNextToken(); + continue; + } if (current->isRest()) { + current = current->getNextToken(); + continue; + } else { + string value = *current; + value += "\\"; + current->setText(value); + current = current->getNextToken(); + continue; + } + + } else if (staff < 0) { + // force stems up or add stem up to non-rest notes + if (hre.search(current, "[/\\\\]")) { + string value = hre.replaceCopy(current, "\\", "/", "g"); + if (value != *current) { + current->setText(value); + } + current = current->getNextToken(); + continue; + } if (current->isRest()) { + // Do not at stem direction to rests + } else { + // Force stem up (assuming not a chord, although it should not matter): + string value = hre.replaceCopy(current, "/", "$"); + if (value != *current) { + current->setText(value); + } + } + // Add < after pitch (and accidental and qualifiers) to display + // on staff below. + m_hasCrossStaff = true; + string output = hre.replaceCopy(current, replacement, "([A-Ga-gr]+[-#nXYxy]*)", "g"); + if (output != *current) { + current->setText(output); + } + } + } + current = current->getNextToken(); + } +} + + + + ///////////////////////////////// // // Tool_satb2gs::Tool_satb2gs -- Set the recognized options for the tool. From 89fd4579c6308d10323052735d383ca1e168a593 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 9 Apr 2024 11:20:17 -0700 Subject: [PATCH 048/383] Implementation for issue https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/890 --- src/iohumdrum.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 05f6cc3f45c..498cda39c6f 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -26603,6 +26603,7 @@ std::string HumdrumInput::checkNoteForScordatura(const std::string &token) void HumdrumInput::addCautionaryAccidental(Accid *accid, hum::HTp token, int acount) { accid->SetFunc(accidLog_FUNC_caution); + accid->SetType("caution"); switch (acount) { case +3: accid->SetAccid(ACCIDENTAL_WRITTEN_ts); break; case +2: accid->SetAccid(ACCIDENTAL_WRITTEN_x); break; From 0c8fb320892349a453a8441e93c6c32d9487b13c Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Apr 2024 16:13:44 -0400 Subject: [PATCH 049/383] Fix custos drawing and dragging methods --- src/editortoolkit_neume.cpp | 16 +++++++--------- src/view_element.cpp | 18 +----------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index e123883be09..48c1ccb03ba 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -738,6 +738,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) } Layer *layer = vrv_cast(element->GetFirstAncestor(LAYER)); layer->ReorderByXPos(); // Reflect position order of elements internally (and in the resulting output file) + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", status); m_editInfo.import("message", message); return true; @@ -4106,15 +4107,12 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - // Use the same pitchDifference equation for both syllables and custos - const int pitchDifference - = round((double)(staff->GetDrawingY() + (2 * staffSize * (staff->m_drawingLines - clef->GetLine())) - - fi->GetZone()->GetUly() - - ((fi->GetZone()->GetUlx() - staff->GetZone()->GetUlx()) - * tan(-staff->GetDrawingRotate() * M_PI / 180.0))) - / (double)(staffSize)); - - pi->AdjustPitchByOffset(pitchDifference); + const int pitchDifference = round( + (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) + - m_view->ToLogicalY(fi->GetZone()->GetUly())) + / staffSize + - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); + pi->AdjustPitchByOffset(-pitchDifference); return true; } diff --git a/src/view_element.cpp b/src/view_element.cpp index bd7b0ee9879..6820fb8dbd7 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -788,28 +788,12 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); } - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - y -= int(xDiff * tan(deg * M_PI / 180.0)); - } - else if (staff->HasDrawingRotation()) { + if (staff->HasDrawingRotation()) { y -= staff->GetDrawingRotationOffsetFor(x); } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); - if (m_doc->IsFacs() && element->HasFacs()) { - const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / 2); - const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / 1.4); - - FacsimileInterface *fi = element->GetFacsimileInterface(); - fi->GetZone()->SetUlx(x); - fi->GetZone()->SetUly(ToDeviceContextY(y)); - fi->GetZone()->SetLrx(x + noteWidth); - fi->GetZone()->SetLry(ToDeviceContextY(y - noteHeight)); - } - /************ Draw children (accidentals, etc) ************/ // Drawing the children should be done before ending the graphic. Otherwise the SVG tree will not match the MEI one this->DrawLayerChildren(dc, custos, layer, staff, measure); From b61ac03b9891361d3a43142f0e3e99a2e080b64b Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Apr 2024 17:18:52 -0400 Subject: [PATCH 050/383] Skip writing coord.x1 for neume lines --- src/iomei.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iomei.cpp b/src/iomei.cpp index e02dc730a74..4e2349c0e26 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2233,7 +2233,7 @@ void MEIOutput::WriteStaff(pugi::xml_node currentNode, Staff *staff) staff->WriteVisibility(currentNode); // y position - if (staff->m_drawingFacsY != VRV_UNSET) { + if (staff->m_drawingFacsY != VRV_UNSET && !(m_doc->IsNeumeLines())) { staff->SetCoordY1(staff->m_drawingFacsY / DEFINITION_FACTOR); staff->WriteCoordY1(currentNode); } @@ -2313,7 +2313,7 @@ void MEIOutput::WriteLayerElement(pugi::xml_node currentNode, LayerElement *elem this->WriteLinkingInterface(currentNode, element); element->WriteLabelled(currentNode); element->WriteTyped(currentNode); - if (element->m_drawingFacsX != VRV_UNSET && !(m_doc->IsTranscription() && m_doc->HasFacsimile())) { + if (element->m_drawingFacsX != VRV_UNSET && !(m_doc->IsNeumeLines())) { element->SetCoordX1(element->m_drawingFacsX / DEFINITION_FACTOR); element->WriteCoordX1(currentNode); } From 33d23eeab2ad068e85446656a33ccbd009432a4f Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Apr 2024 18:30:44 -0400 Subject: [PATCH 051/383] Fix neume/nc/syllable drawing and dragging methods --- src/editortoolkit_neume.cpp | 15 +++++------- src/view_neume.cpp | 49 ++++--------------------------------- 2 files changed, 11 insertions(+), 53 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 48c1ccb03ba..3d5e409f257 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -4174,15 +4174,12 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) } pi->SetOct(octave); - // Use the same pitchDifference equation for both syllables and custos - const int pitchDifference - = round((double)(staff->GetDrawingY() + (2 * staffSize * (staff->m_drawingLines - clef->GetLine())) - - fi->GetZone()->GetUly() - - ((fi->GetZone()->GetUlx() - staff->GetZone()->GetUlx()) - * tan(-staff->GetDrawingRotate() * M_PI / 180.0))) - / (double)(staffSize)); - - pi->AdjustPitchByOffset(pitchDifference); + const int pitchDifference = round( + (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) + - m_view->ToLogicalY(fi->GetZone()->GetUly())) + / staffSize + - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); + pi->AdjustPitchByOffset(-pitchDifference); } return true; diff --git a/src/view_neume.cpp b/src/view_neume.cpp index f98e05fdda2..1cfe79cbeaa 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -205,23 +205,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (nc->GetLigated() == BOOLEAN_true) { int pitchDifference = 0; bool isFirst; - // Check if this is the first or second part of a ligature - // Object *nextSibling = neume->GetChild(position + 1); - // if (nextSibling != NULL) { - // Nc *nextNc = dynamic_cast(nextSibling); - // assert(nextNc); - // if (nextNc->GetLigated() == BOOLEAN_true) { // first part of the ligature - // isFirst = true; - // pitchDifference = nextNc->PitchDifferenceTo(nc); - // params.at(0).yOffset = pitchDifference; - // } - // else { - // isFirst = false; - // } - // } - // else { - // isFirst = false; - // } int ligCount = neume->GetLigatureCount(position); if (ligCount % 2 == 0) { @@ -243,14 +226,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } } - // if (!isFirst) { // still need to get pitchDifference - // Nc *lastnc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); - // assert(lastnc); - // pitchDifference = nc->PitchDifferenceTo(lastnc); - // params.at(0).xOffset = -1; - // params.at(0).yOffset = -pitchDifference; - // } - // set the glyph switch (pitchDifference) { case -1: @@ -285,13 +260,13 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); int noteY, noteX; int yValue; - if (nc->HasFacs() && m_doc->IsFacs()) { - noteY = ToLogicalY(staff->GetDrawingY()); + if (nc->HasFacs() && m_doc->IsNeumeLines()) { + noteY = staff->GetDrawingY(); noteX = nc->GetDrawingX(); params.at(0).xOffset = 0; } - else if (neume->HasFacs() && m_doc->IsFacs()) { - noteY = ToLogicalY(staff->GetDrawingY()); + else if (neume->HasFacs() && m_doc->IsNeumeLines()) { + noteY = staff->GetDrawingY(); noteX = neume->GetDrawingX() + position * noteWidth; } else { @@ -310,12 +285,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); int rotationOffset = 0; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = noteX - staff->GetDrawingX(); - rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); - } - else if (staff->HasDrawingRotation()) { + if (staff->HasDrawingRotation()) { rotationOffset = staff->GetDrawingRotationOffsetFor(noteX); } @@ -339,15 +309,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } } - // adjust facsimile values of element based on where it is rendered if necessary - if (m_doc->IsFacs() && element->HasFacs()) { - FacsimileInterface *fi = element->GetFacsimileInterface(); - fi->GetZone()->SetUlx(noteX); - fi->GetZone()->SetUly(ToDeviceContextY(yValue)); - fi->GetZone()->SetLrx(noteX + noteWidth); - fi->GetZone()->SetLry(ToDeviceContextY(yValue - noteHeight)); - } - // Draw the children this->DrawLayerChildren(dc, nc, layer, staff, measure); From ac5531a42fda93246de11d4020f721608b8d744b Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Apr 2024 18:35:31 -0400 Subject: [PATCH 052/383] Clean up View::DrawLiquescent --- src/view_neume.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 1cfe79cbeaa..51ea1373dff 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -112,8 +112,8 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); int noteY, noteX; int yValue; - if (nc->HasFacs() && m_doc->IsFacs()) { - noteY = ToLogicalY(staff->GetDrawingY()); + if (nc->HasFacs() && m_doc->IsNeumeLines()) { + noteY = staff->GetDrawingY(); noteX = nc->GetDrawingX(); } else { @@ -132,14 +132,9 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); } int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotateOffset; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = noteX - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); - } - else { - rotateOffset = 0; + int rotateOffset = 0; + if (staff->HasDrawingRotation()) { + rotateOffset = staff->GetDrawingRotationOffsetFor(noteX); } if (clef->GetShape() == CLEFSHAPE_C) { From 770afde7e4494c8d95cb5658c069dcfa2864e9ae Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 10 Apr 2024 14:32:00 -0400 Subject: [PATCH 053/383] Skip writing measure@coord.x1 for neume lines --- src/iomei.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iomei.cpp b/src/iomei.cpp index 4e2349c0e26..dfbb59380d3 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -1901,7 +1901,7 @@ void MEIOutput::WriteMeasure(pugi::xml_node currentNode, Measure *measure) measure->WritePointing(currentNode); measure->WriteTyped(currentNode); // For now we copy the adjusted value of coord.x1 and coord.x2 to xAbs and xAbs2 respectively - if ((measure->m_drawingFacsX1 != VRV_UNSET) && (measure->m_drawingFacsX2 != VRV_UNSET)) { + if ((measure->m_drawingFacsX1 != VRV_UNSET) && (measure->m_drawingFacsX2 != VRV_UNSET) && !m_doc->IsNeumeLines()) { measure->SetCoordX1(measure->m_drawingFacsX1 / DEFINITION_FACTOR); measure->SetCoordX2(measure->m_drawingFacsX2 / DEFINITION_FACTOR); measure->WriteCoordX1(currentNode); From 4cd18908e6eef0a91f8fd0add249d3e83e0afafd Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 11 Apr 2024 00:49:08 -0400 Subject: [PATCH 054/383] Add m_drawingFacsY for accid and syl --- .../vrv/adjustyrelfortranscriptionfunctor.h | 56 +++++++++++++++++++ include/vrv/layerelement.h | 1 + src/adjustyrelfortranscriptionfunctor.cpp | 35 ++++++++++++ src/facsimilefunctor.cpp | 6 ++ src/layerelement.cpp | 3 + src/miscfunctor.cpp | 1 + src/page.cpp | 3 + src/view_element.cpp | 22 +------- 8 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 include/vrv/adjustyrelfortranscriptionfunctor.h create mode 100644 src/adjustyrelfortranscriptionfunctor.cpp diff --git a/include/vrv/adjustyrelfortranscriptionfunctor.h b/include/vrv/adjustyrelfortranscriptionfunctor.h new file mode 100644 index 00000000000..af26cbda9e2 --- /dev/null +++ b/include/vrv/adjustyrelfortranscriptionfunctor.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: adjustyrelfortranscriptionfunctor.h +// Author: Yinan Zhou +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ +#define __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ + +#include "functor.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// AdjustYRelForTranscriptionFunctor +//---------------------------------------------------------------------------- + +/** + * This class adjusts the YRel positions taking into account the bounding boxes. + */ +class AdjustYRelForTranscriptionFunctor : public Functor { +public: + /** + * @name Constructors, destructors + */ + ///@{ + AdjustYRelForTranscriptionFunctor(); + virtual ~AdjustYRelForTranscriptionFunctor() = default; + ///@} + + /* + * Abstract base implementation + */ + bool ImplementsEndInterface() const override { return false; } + + /* + * Functor interface + */ + ///@{ + FunctorCode VisitLayerElement(LayerElement *layerElement) override; + ///@} + +protected: + // +private: + // +public: + // +private: + // +}; + +} // namespace vrv + +#endif // __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ diff --git a/include/vrv/layerelement.h b/include/vrv/layerelement.h index 085fa811845..248554b5a1d 100644 --- a/include/vrv/layerelement.h +++ b/include/vrv/layerelement.h @@ -387,6 +387,7 @@ class LayerElement : public Object, public: /** Absolute position X. This is used for facsimile (transcription) encoding */ int m_drawingFacsX; + int m_drawingFacsY; // This is used only for accid, syl /** * This stores a pointer to the cross-staff (if any) and the appropriate layer * See PrepareCrossStaffFunctor diff --git a/src/adjustyrelfortranscriptionfunctor.cpp b/src/adjustyrelfortranscriptionfunctor.cpp new file mode 100644 index 00000000000..8bcc92402d1 --- /dev/null +++ b/src/adjustyrelfortranscriptionfunctor.cpp @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: adjustyrelfortranscriptionfunctor.cpp +// Author: Yinan Zhou +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "adjustyrelfortranscriptionfunctor.h" + +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// AdjustYRelForTranscriptionFunctor +//---------------------------------------------------------------------------- + +AdjustYRelForTranscriptionFunctor::AdjustYRelForTranscriptionFunctor() : Functor() {} + +FunctorCode AdjustYRelForTranscriptionFunctor::VisitLayerElement(LayerElement *layerElement) +{ + if (layerElement->m_drawingFacsY == VRV_UNSET) return FUNCTOR_CONTINUE; + + if (layerElement->IsScoreDefElement()) return FUNCTOR_SIBLINGS; + + if (!layerElement->HasSelfBB()) return FUNCTOR_CONTINUE; + + layerElement->SetDrawingYRel(-layerElement->GetSelfY1()); + + return FUNCTOR_CONTINUE; +} + +} // namespace vrv diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 7bac6c4e1ec..f70d6804d9c 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -45,6 +45,9 @@ FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerEleme Zone *zone = layerElement->GetZone(); assert(zone); layerElement->m_drawingFacsX = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + if (layerElement->Is({ ACCID, SYL })) { + layerElement->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); + } return FUNCTOR_CONTINUE; } @@ -191,6 +194,9 @@ FunctorCode SyncToFacsimileFunctor::VisitLayerElement(LayerElement *layerElement Zone *zone = this->GetZone(layerElement, layerElement->GetClassName()); zone->SetUlx(m_view.ToDeviceContextX(layerElement->GetDrawingX()) / DEFINITION_FACTOR + m_pageMarginLeft); + if (layerElement->Is({ ACCID, SYL })) { + zone->SetUly(m_view.ToDeviceContextY(layerElement->GetDrawingY()) / DEFINITION_FACTOR + m_pageMarginTop); + } return FUNCTOR_CONTINUE; } diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 17d0aa31cd4..25dd5d9ad77 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -129,6 +129,7 @@ void LayerElement::Reset() m_drawingFacsX = VRV_UNSET; m_drawingYRel = 0; + m_drawingFacsY = VRV_UNSET; m_drawingXRel = 0; m_drawingCueSize = false; @@ -437,6 +438,8 @@ int LayerElement::GetDrawingX() const int LayerElement::GetDrawingY() const { + if (m_drawingFacsY != VRV_UNSET) return m_drawingFacsY + this->GetDrawingYRel(); + if (m_cachedDrawingY != VRV_UNSET) return m_cachedDrawingY; // Look if we have a crossStaff situation diff --git a/src/miscfunctor.cpp b/src/miscfunctor.cpp index 9c3677a2823..f10936c906e 100644 --- a/src/miscfunctor.cpp +++ b/src/miscfunctor.cpp @@ -33,6 +33,7 @@ FunctorCode ApplyPPUFactorFunctor::VisitLayerElement(LayerElement *layerElement) if (layerElement->IsScoreDefElement()) return FUNCTOR_SIBLINGS; if (layerElement->m_drawingFacsX != VRV_UNSET) layerElement->m_drawingFacsX /= m_page->GetPPUFactor(); + if (layerElement->m_drawingFacsY != VRV_UNSET) layerElement->m_drawingFacsY /= m_page->GetPPUFactor(); return FUNCTOR_CONTINUE; } diff --git a/src/page.cpp b/src/page.cpp index 5e3bb7ff276..614b64180bc 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -33,6 +33,7 @@ #include "adjustxposfunctor.h" #include "adjustxrelfortranscriptionfunctor.h" #include "adjustyposfunctor.h" +#include "adjustyrelfortranscriptionfunctor.h" #include "alignfunctor.h" #include "bboxdevicecontext.h" #include "cachehorizontallayoutfunctor.h" @@ -270,6 +271,8 @@ void Page::LayOutTranscription(bool force) AdjustXRelForTranscriptionFunctor adjustXRelForTranscription; this->Process(adjustXRelForTranscription); + AdjustYRelForTranscriptionFunctor adjustYRelForTranscription; + this->Process(adjustYRelForTranscription); CalcLedgerLinesFunctor calcLedgerLines(doc); this->Process(calcLedgerLines); diff --git a/src/view_element.cpp b/src/view_element.cpp index 6820fb8dbd7..1608a8c4867 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -302,22 +302,6 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta y = (accid->GetPlace() == STAFFREL_below) ? y - extend.m_ascent - unit : y + extend.m_descent + unit; } - if (notationType == NOTATIONTYPE_neume) { - int rotationOffset = 0; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); - } - else if (staff->HasDrawingRotation()) { - rotationOffset = staff->GetDrawingRotationOffsetFor(x); - } - if (accid->HasFacs() && m_doc->IsFacs()) { - y = ToLogicalY(y); - } - y -= rotationOffset; - } - this->DrawSmuflString( dc, x, y, accidStr, HORIZONTALALIGNMENT_center, staff->m_drawingStaffSize, accid->GetDrawingCueSize(), true); @@ -1754,9 +1738,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff Syl *syl = vrv_cast(element); assert(syl); - bool isNeume = (staff->m_drawingNotationType == NOTATIONTYPE_neume); - - if (!syl->GetStart() && !isNeume) { + if (!syl->GetStart() && !(staff->m_drawingNotationType == NOTATIONTYPE_neume)) { LogWarning("Parent note for was not found"); return; } @@ -1786,7 +1768,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff TextDrawingParams params; params.m_x = syl->GetDrawingX(); params.m_y = syl->GetDrawingY(); - if (m_doc->IsFacs()) { + if (m_doc->IsFacs() || m_doc->IsNeumeLines()) { params.m_width = syl->GetDrawingWidth(); params.m_height = syl->GetDrawingHeight(); } From 3d8937090e3b0a5d238a56651acb7489a935cb8c Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Thu, 11 Apr 2024 09:47:41 -0700 Subject: [PATCH 055/383] Implementation for issue https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/857 --- src/iohumdrum.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 498cda39c6f..b47083ccec3 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -25936,6 +25936,8 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta // int accidCount = hum::Convert::kernToAccidentalCount(tstring); bool showInAccid = token->hasVisibleAccidental(stindex); bool showInAccidGes = !showInAccid; + bool brackQ = hasLayoutParameter(token, "ACC", "brack"); + bool parenQ = hasLayoutParameter(token, "ACC", "paren"); std::string loaccid = token->getLayoutParameter("N", "acc", subtoken); if (!loaccid.empty()) { // show the performance accidental in @accid.ges, and the @@ -25985,7 +25987,6 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta else { if (editorialQ) { - accid->SetGlyphAuth("smufl"); switch (accidCount) { case +3: @@ -26080,6 +26081,12 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta } } if (!editorialQ) { + if (brackQ) { + accid->SetEnclose(ENCLOSURE_brack); + } + else if (parenQ) { + accid->SetEnclose(ENCLOSURE_paren); + } if (showInAccid) { switch (accidCount) { case +3: accid->SetAccid(ACCIDENTAL_WRITTEN_xs); break; From bccf2a615e12bef2e81e257f14d31d9e45dd83a6 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sat, 13 Apr 2024 18:25:42 +0200 Subject: [PATCH 056/383] reduce scope --- src/drawinginterface.cpp | 7 ++----- src/view_neume.cpp | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/drawinginterface.cpp b/src/drawinginterface.cpp index cc4a339e0b7..7d4b2622981 100644 --- a/src/drawinginterface.cpp +++ b/src/drawinginterface.cpp @@ -136,9 +136,6 @@ void BeamDrawingInterface::InitCoords(const ListOfObjects &childList, Staff *sta m_beamStaff = staff; - // duration variables - int lastDur, currentDur; - m_beamElementCoords.reserve(childList.size()); for ([[maybe_unused]] auto child : childList) { m_beamElementCoords.push_back(new BeamElementCoord()); @@ -149,7 +146,7 @@ void BeamDrawingInterface::InitCoords(const ListOfObjects &childList, Staff *sta // Beam list should contain only DurationInterface objects assert(current->GetDurationInterface()); - lastDur = (current->GetDurationInterface())->GetActualDur(); + int lastDur = (current->GetDurationInterface())->GetActualDur(); /******************************************************************/ // Populate BeamElementCoord for each element in the beam @@ -165,7 +162,7 @@ void BeamDrawingInterface::InitCoords(const ListOfObjects &childList, Staff *sta do { // Beam list should contain only DurationInterface objects assert(current->GetDurationInterface()); - currentDur = (current->GetDurationInterface())->GetActualDur(); + const int currentDur = (current->GetDurationInterface())->GetActualDur(); if (current->Is(CHORD)) { m_beamHasChord = true; diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 971a8bf3304..1ff0b13ad49 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -219,7 +219,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Calculating proper y offset based on pname, clef, staff, and staff rotate int clefYPosition = noteY - (staffSize * (staffLineNumber - clefLine)); - int pitchOffset = 0; // The default octave = 3, but the actual octave is calculated by // taking into account the displacement of the clef @@ -242,6 +241,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff yValue = noteY + (nc->GetLoc() - 2 * (staffLineNumber - 1)) * (staffSize / 2); } else { + int pitchOffset = 0; if (clef->GetShape() == CLEFSHAPE_C) { pitchOffset = (nc->GetPname() - 1) * (staffSize / 2); } From 89e31ddf18d535d8c385696cc6b060a23be30f9b Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sat, 13 Apr 2024 18:26:31 +0200 Subject: [PATCH 057/383] remove useless condition --- src/preparedatafunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index 2d9226da362..abe3d35d174 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -1882,7 +1882,7 @@ FunctorCode PrepareBeamSpanElementsFunctor::VisitBeamSpan(BeamSpan *beamSpan) if (!elementStaff) continue; if (elementStaff->GetN() != staff->GetN()) { Layer *elementLayer = vrv_cast(layerElem->GetFirstAncestor(LAYER)); - if (!elementStaff || !elementLayer) continue; + if (!elementLayer) continue; layerElem->m_crossStaff = elementStaff; layerElem->m_crossLayer = elementLayer; } From f5804433126464ad779d061fac7dcb7d7cda64d2 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sat, 13 Apr 2024 18:27:52 +0200 Subject: [PATCH 058/383] remove always true conditions --- src/calcstemfunctor.cpp | 2 +- src/ioabc.cpp | 2 +- src/metersiggrp.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calcstemfunctor.cpp b/src/calcstemfunctor.cpp index ffc1d5dcee7..ed2da41d127 100644 --- a/src/calcstemfunctor.cpp +++ b/src/calcstemfunctor.cpp @@ -607,7 +607,7 @@ data_STEMDIRECTION CalcStemFunctor::CalcStemDirection(const Chord *chord, int ve else if (middlePoint > verticalCenter) { return STEMDIRECTION_down; } - else if (middlePoint < verticalCenter) { + else { return STEMDIRECTION_up; } } diff --git a/src/ioabc.cpp b/src/ioabc.cpp index a7473bdae60..c3f6c053f7c 100644 --- a/src/ioabc.cpp +++ b/src/ioabc.cpp @@ -679,7 +679,7 @@ void ABCInput::parseKey(std::string &keyString) posStart = pitch.size() - posEnd; keyPitchAlterAmount = -1; } - else if (accidNum > 0) { + else { keySig = StringFormat("%ds", accidNum); keyPitchAlterAmount = 1; } diff --git a/src/metersiggrp.cpp b/src/metersiggrp.cpp index 78785634642..3890f28943b 100644 --- a/src/metersiggrp.cpp +++ b/src/metersiggrp.cpp @@ -130,7 +130,7 @@ MeterSig *MeterSigGrp::GetSimplifiedMeterSig() const const int ratio = maxUnit / currentUnit; currentCount += meterSig->GetTotalCount() * ratio; } - else if (maxUnit < currentUnit) { + else { const int ratio = currentUnit / maxUnit; currentCount *= ratio; currentCount += meterSig->GetTotalCount(); From 7c035724f96abef470755400a6333f0e95b235f7 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 18 Apr 2024 13:59:10 -0400 Subject: [PATCH 059/383] Fix flipped syl --- src/view_element.cpp | 4 +++- src/view_text.cpp | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 1608a8c4867..9b2cc39a933 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1743,7 +1743,9 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff return; } - syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); + if (m_doc->IsFacs()) { + syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); + } dc->StartGraphic(syl, "", syl->GetID()); dc->DeactivateGraphicY(); diff --git a/src/view_text.cpp b/src/view_text.cpp index ff281247add..f1f30653f9c 100644 --- a/src/view_text.cpp +++ b/src/view_text.cpp @@ -271,21 +271,21 @@ void View::DrawLyricString( std::u32string syl = U""; std::u32string lyricStr = str; - const int x = (params) ? params->m_x : VRV_UNSET; - const int y = (params) ? params->m_y : VRV_UNSET; + const int dcX = (params) ? ToDeviceContextX(params->m_x) : VRV_UNSET; + const int dcY = (params) ? ToDeviceContextY(params->m_y) : VRV_UNSET; const int width = (params) ? params->m_width : VRV_UNSET; const int height = (params) ? params->m_height : VRV_UNSET; if (m_doc->GetOptions()->m_lyricElision.GetValue() == ELISION_unicode) { std::replace(lyricStr.begin(), lyricStr.end(), U'_', UNICODE_UNDERTIE); - dc->DrawText(UTF32to8(lyricStr), lyricStr, x, y, width, height); + dc->DrawText(UTF32to8(lyricStr), lyricStr, dcX, dcY, width, height); } else { while (lyricStr.compare(syl) != 0) { wroteText = true; auto index = lyricStr.find_first_of(U"_"); syl = lyricStr.substr(0, index); - dc->DrawText(UTF32to8(syl), syl, x, y, width, height); + dc->DrawText(UTF32to8(syl), syl, dcX, dcY, width, height); // no _ if (index == std::string::npos) break; @@ -298,7 +298,7 @@ void View::DrawLyricString( bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(elision); vrvTxt.SetSmuflWithFallback(isFallbackNeeded); dc->SetFont(&vrvTxt); - dc->DrawText(UTF32to8(elision), elision, x, y, width, height); + dc->DrawText(UTF32to8(elision), elision, dcX, dcY, width, height); dc->ResetFont(); // next syllable @@ -310,7 +310,8 @@ void View::DrawLyricString( // This should only be called in facsimile mode where a zone is specified but there is // no text. This draws the bounds of the zone but leaves the space blank. if (!wroteText && params) { - dc->DrawText("", U"", params->m_x, params->m_y, params->m_width, params->m_height); + dc->DrawText( + "", U"", ToDeviceContextX(params->m_x), ToDeviceContextY(params->m_y), params->m_width, params->m_height); } } From 8ed2b4e6778e0e6ba818cd54c98ada6731c0f07b Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 29 Dec 2023 21:05:28 +0100 Subject: [PATCH 060/383] Add a neume-line measure types for representing line sections * Special handling of `
` for the neon editor --- include/vrv/doc.h | 14 ++++++++++++++ include/vrv/measure.h | 14 ++++++++++---- include/vrv/vrvdef.h | 8 ++++++++ src/convertfunctor.cpp | 18 ++++++++++++------ src/doc.cpp | 1 + src/iodarms.cpp | 2 +- src/iohumdrum.cpp | 2 +- src/iomei.cpp | 37 ++++++++++++++++++++++++++++++------- src/iopae.cpp | 4 ++-- src/measure.cpp | 6 +++--- src/savefunctor.cpp | 4 ++-- 11 files changed, 84 insertions(+), 26 deletions(-) diff --git a/include/vrv/doc.h b/include/vrv/doc.h index 687f6f068e2..48876eec45e 100644 --- a/include/vrv/doc.h +++ b/include/vrv/doc.h @@ -451,6 +451,14 @@ class Doc : public Object { bool IsMensuralMusicOnly() const { return m_isMensuralMusicOnly; } ///@} + /** + * @name Setter for and getter for neume-line flag + */ + ///@{ + void SetNeumeLines(bool isNeumeLines) { m_isNeumeLines = isNeumeLines; } + bool IsNeumeLines() const { return m_isNeumeLines; } + ///@} + /** * @name Setter and getter for facsimile */ @@ -660,6 +668,12 @@ class Doc : public Object { */ bool m_isMensuralMusicOnly; + /** + * A flag to indicate that the document contains neume lines. + * This is a special document type where neume lines are encoded with
+ */ + bool m_isNeumeLines; + /** Page width (MEI scoredef@page.width) - currently not saved */ int m_pageWidth; /** Page height (MEI scoredef@page.height) - currently not saved */ diff --git a/include/vrv/measure.h b/include/vrv/measure.h index 49ab432a0d7..dcf96d15243 100644 --- a/include/vrv/measure.h +++ b/include/vrv/measure.h @@ -53,7 +53,7 @@ class Measure : public Object, * Reset method resets all attribute classes */ ///@{ - Measure(bool measuredMusic = true, int logMeasureNb = -1); + Measure(MeasureType measuredMusic = MEASURED, int logMeasureNb = -1); virtual ~Measure(); Object *Clone() const override { return new Measure(*this); }; void Reset() override; @@ -79,7 +79,12 @@ class Measure : public Object, /** * Return true if measured music (otherwise we have fake measures) */ - bool IsMeasuredMusic() const { return m_measuredMusic; } + bool IsMeasuredMusic() const { return (m_measureType == MEASURED); } + + /** + * Return true if the measure represents a neume (section) line + */ + bool IsNeumeLine() const { return (m_measureType == NEUMELINE); } /** * Get and set the measure index @@ -404,9 +409,10 @@ class Measure : public Object, private: /** - * Indicates measured music (otherwise we have fake measures) + * Indicate measured music (CMN), unmeasured (fake measures for mensural or neumes) or neume lines + * Neume line measure are created from
*/ - bool m_measuredMusic; + MeasureType m_measureType; /** * The unique measure index diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index eb3644af011..9fe1acdbe03 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -661,6 +661,14 @@ enum SmuflTextFont { SMUFL_NONE = 0, SMUFL_FONT_SELECTED, SMUFL_FONT_FALLBACK }; enum GraphicID { PRIMARY = 0, SPANNING, SYMBOLREF }; +//---------------------------------------------------------------------------- +// Measure type +//---------------------------------------------------------------------------- + +enum MeasureType { MEASURED = 0, UNMEASURED, NEUMELINE }; + +#define NEUME_LINE_TYPE "neon-neume-line" + //---------------------------------------------------------------------------- // Legacy Wolfgang defines //---------------------------------------------------------------------------- diff --git a/src/convertfunctor.cpp b/src/convertfunctor.cpp index 61838252781..d80c5db720f 100644 --- a/src/convertfunctor.cpp +++ b/src/convertfunctor.cpp @@ -187,9 +187,12 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) bool nextIsBarline = (next && next->Is(BARLINE)); // See if we create proper measures and what to do with the barLine - bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); + MeasureType convertToMeasured = UNMEASURED; + if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { + convertToMeasured = MEASURED; + } - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { // barLine object will be deleted m_targetMeasure->SetRight(barLine->GetForm()); } @@ -212,7 +215,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { m_targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { m_targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1 + m_segmentIdx)); } m_targetSubSystem->AddChild(m_targetMeasure); @@ -277,7 +280,10 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) return FUNCTOR_CONTINUE; } - bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); + MeasureType convertToMeasured = UNMEASURED; + if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { + convertToMeasured = MEASURED; + } assert(m_targetSystem); assert(m_layerTree); @@ -289,7 +295,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) // Create the first measure segment - problem: we are dropping the section element - we should create a score-based // MEI file instead Measure *targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1)); } m_targetSubSystem->AddChild(targetMeasure); @@ -375,7 +381,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitSyllable(Syllable *syllable) // Make a segment break // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { - m_targetMeasure = new Measure(false); + m_targetMeasure = new Measure(UNMEASURED); m_targetSubSystem->AddChild(m_targetMeasure); // Add a staff with same attributes as in the previous segment m_targetStaff = new Staff(*m_targetStaff); diff --git a/src/doc.cpp b/src/doc.cpp index 5b13dd476d7..d47b5a0c188 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -132,6 +132,7 @@ void Doc::Reset() m_timemapTempo = 0.0; m_markup = MARKUP_DEFAULT; m_isMensuralMusicOnly = false; + m_isNeumeLines = false; m_isCastOff = false; m_visibleScores.clear(); diff --git a/src/iodarms.cpp b/src/iodarms.cpp index debf3f3181a..0f31bb84223 100644 --- a/src/iodarms.cpp +++ b/src/iodarms.cpp @@ -473,7 +473,7 @@ bool DarmsInput::Import(const std::string &data_str) score->AddChild(section); m_staff = new Staff(1); - m_measure = new Measure(true, 1); + m_measure = new Measure(MEASURED, 1); m_layer = new Layer(); m_layer->SetN(1); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 4353e83816a..5e7423a1e7b 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -29294,7 +29294,7 @@ void HumdrumInput::setupSystemMeasure(int startline, int endline) } if (hasMensuralStaff(&infile[startline])) { - m_measure = new Measure(false); + m_measure = new Measure(UNMEASURED); } else { m_measure = new Measure(); diff --git a/src/iomei.cpp b/src/iomei.cpp index aa139f99077..ebe8703ec85 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -445,7 +445,14 @@ bool MEIOutput::WriteObjectInternal(Object *object, bool useCustomScoreDef) this->WriteSymbolTable(m_currentNode, vrv_cast(object)); } else if (object->Is(MEASURE)) { - m_currentNode = m_currentNode.append_child("measure"); + Measure *measure = vrv_cast(object); + assert(measure); + std::string name = "measure"; + if (measure->IsNeumeLine()) { + name = "section"; + measure->SetType(NEUME_LINE_TYPE); + } + m_currentNode = m_currentNode.append_child(name.c_str()); this->WriteMeasure(m_currentNode, vrv_cast(object)); } else if (object->Is(STAFF)) { @@ -4394,7 +4401,13 @@ bool MEIInput::ReadScore(Object *parent, pugi::xml_node score) bool MEIInput::ReadSection(Object *parent, pugi::xml_node section) { Section *vrvSection = new Section(); - this->SetMeiID(section, vrvSection); + this->ReadSystemElement(section, vrvSection); + + if (vrvSection->GetType() == NEUME_LINE_TYPE) { + delete vrvSection; + m_doc->SetNeumeLines(true); + return ReadSectionChildren(parent, section); + } vrvSection->ReadNNumberLike(section); vrvSection->ReadSectionVis(section); @@ -4454,8 +4467,13 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) else if (std::string(current.name()) == "staff") { if (!unmeasured) { if (parent->Is(SECTION)) { - unmeasured = new Measure(false); - m_doc->SetMensuralMusicOnly(true); + if (m_doc->IsNeumeLines()) { + unmeasured = new Measure(NEUMELINE); + } + else { + unmeasured = new Measure(UNMEASURED); + m_doc->SetMensuralMusicOnly(true); + } parent->AddChild(unmeasured); } else { @@ -4484,8 +4502,13 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) // New for blank files in neume notation if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume)) { - unmeasured = new Measure(false); - m_doc->SetMensuralMusicOnly(true); + if (m_doc->IsNeumeLines()) { + unmeasured = new Measure(NEUMELINE); + } + else { + unmeasured = new Measure(UNMEASURED); + m_doc->SetMensuralMusicOnly(true); + } parent->AddChild(unmeasured); } return success; @@ -4628,7 +4651,7 @@ bool MEIInput::ReadSystemChildren(Object *parent, pugi::xml_node parentNode) if (parent->Is(SYSTEM)) { System *system = vrv_cast(parent); assert(system); - unmeasured = new Measure(false); + unmeasured = new Measure(UNMEASURED); m_doc->SetMensuralMusicOnly(true); if (m_doc->IsTranscription() && (m_meiversion == meiVersion_MEIVERSION_2013)) { UpgradeMeasureTo_3_0_0(unmeasured, system); diff --git a/src/iopae.cpp b/src/iopae.cpp index 1e844d31a06..005f1ce0d3d 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -2884,7 +2884,7 @@ bool PAEInput::Import(const std::string &input) } // Add a measure at the beginning of the data because there is always at least one measure - Measure *measure = new Measure(true, 1); + Measure *measure = new Measure(MEASURED, 1); // By default there is no end barline on an incipit measure->SetRight(BARRENDITION_invis); m_pae.push_back(pae::Token(0, pae::UNKOWN_POS, measure)); @@ -3403,7 +3403,7 @@ bool PAEInput::ConvertMeasure() // We can now create a new measure but not if we have reached the end of the data if (!token.IsEnd()) { measureCount++; - currentMeasure = new Measure(true, measureCount); + currentMeasure = new Measure(MEASURED, measureCount); currentMeasure->SetRight(BARRENDITION_invis); measureToken->m_object = currentMeasure; } diff --git a/src/measure.cpp b/src/measure.cpp index 7b566400378..a3c45c0bd12 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -54,7 +54,7 @@ namespace vrv { static const ClassRegistrar s_factory("measure", MEASURE); -Measure::Measure(bool measureMusic, int logMeasureNb) +Measure::Measure(MeasureType measureMusic, int logMeasureNb) : Object(MEASURE, "measure-") , FacsimileInterface() , AttBarring() @@ -76,7 +76,7 @@ Measure::Measure(bool measureMusic, int logMeasureNb) this->RegisterAttClass(ATT_TYPED); this->RegisterInterface(FacsimileInterface::GetAttClasses(), FacsimileInterface::IsInterface()); - m_measuredMusic = measureMusic; + m_measureType = measureMusic; // We set parent to it because we want to access the parent doc from the aligners m_measureAligner.SetParent(this); @@ -149,7 +149,7 @@ void Measure::Reset() m_rightBarLine.SetForm(this->GetRight()); m_leftBarLine.SetForm(this->GetLeft()); - if (!m_measuredMusic) { + if (!m_measureType) { m_drawingFacsX1 = VRV_UNSET; m_drawingFacsX2 = VRV_UNSET; } diff --git a/src/savefunctor.cpp b/src/savefunctor.cpp index 74172c5112e..082b9d9259a 100644 --- a/src/savefunctor.cpp +++ b/src/savefunctor.cpp @@ -96,12 +96,12 @@ FunctorCode SaveFunctor::VisitMdivEnd(Mdiv *mdiv) FunctorCode SaveFunctor::VisitMeasure(Measure *measure) { - return (measure->IsMeasuredMusic()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMeasureEnd(Measure *measure) { - return (measure->IsMeasuredMusic()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMNum(MNum *mNum) From 9cc8880a856782ce2b056692de83252227d6d856 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 21:22:59 +0100 Subject: [PATCH 061/383] Adjust the facsimile functor to take into account the PPU --- include/vrv/devicecontext.h | 7 +++++ include/vrv/facsimilefunctor.h | 6 +++- src/devicecontext.cpp | 5 ++++ src/doc.cpp | 8 ++++-- src/facsimilefunctor.cpp | 51 ++++++++++++++++++++++++++++++---- src/measure.cpp | 4 +++ src/svgdevicecontext.cpp | 4 +-- src/toolkit.cpp | 3 +- 8 files changed, 77 insertions(+), 11 deletions(-) diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index b319c835a1a..95c62a81208 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -74,6 +74,7 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; + m_viewBoxFactor = (double)DEFINITION_FACTOR; } DeviceContext(ClassId classId) { @@ -89,6 +90,7 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; + m_viewBoxFactor = (double)DEFINITION_FACTOR; } virtual ~DeviceContext(){}; ClassId GetClassId() const { return m_classId; } @@ -124,12 +126,14 @@ class DeviceContext { m_baseWidth = width; m_baseHeight = height; } + void SetViewBoxFactor(double ppuFactor); int GetWidth() const { return m_width; } int GetHeight() const { return m_height; } int GetContentHeight() const { return m_contentHeight; } double GetUserScaleX() { return m_userScaleX; } double GetUserScaleY() { return m_userScaleY; } std::pair GetBaseSize() const { return std::make_pair(m_baseWidth, m_baseHeight); } + double GetViewBoxFactor() const { return m_viewBoxFactor; } ///@} /** @@ -365,6 +369,9 @@ class DeviceContext { /** stores the scale as requested by the used */ double m_userScaleX; double m_userScaleY; + + /** stores the viewbox factor taking into account the DEFINTION_FACTOR and the PPU */ + double m_viewBoxFactor; }; } // namespace vrv diff --git a/include/vrv/facsimilefunctor.h b/include/vrv/facsimilefunctor.h index 1273343626c..7ccf8f3e772 100644 --- a/include/vrv/facsimilefunctor.h +++ b/include/vrv/facsimilefunctor.h @@ -42,7 +42,7 @@ class SyncFromFacsimileFunctor : public Functor { /* * Abstract base implementation */ - bool ImplementsEndInterface() const override { return false; } + bool ImplementsEndInterface() const override { return true; } /* * Functor interface @@ -51,6 +51,7 @@ class SyncFromFacsimileFunctor : public Functor { FunctorCode VisitLayerElement(LayerElement *layerElement) override; FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitPage(Page *page) override; + FunctorCode VisitPageEnd(Page *page) override; FunctorCode VisitPb(Pb *pb) override; FunctorCode VisitSb(Sb *sb) override; FunctorCode VisitStaff(Staff *staff) override; @@ -71,6 +72,9 @@ class SyncFromFacsimileFunctor : public Functor { // Page *m_currentPage; System *m_currentSystem; + Measure *m_currentNeumeLine; + /** map to store the zone corresponding to a staff */ + std::map m_staffZones; }; //---------------------------------------------------------------------------- diff --git a/src/devicecontext.cpp b/src/devicecontext.cpp index 09a868e56ab..e734a35919d 100644 --- a/src/devicecontext.cpp +++ b/src/devicecontext.cpp @@ -129,6 +129,11 @@ const Resources *DeviceContext::GetResources(bool showWarning) const return m_resources; } +void DeviceContext::SetViewBoxFactor(double ppuFactor) +{ + m_viewBoxFactor = double(DEFINITION_FACTOR) / ppuFactor; +} + void DeviceContext::SetPen(int color, int width, int style, int dashLength, int gapLength, int lineCap, int lineJoin) { float opacityValue; diff --git a/src/doc.cpp b/src/doc.cpp index d47b5a0c188..4e6e4b0b3d7 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -2129,8 +2129,10 @@ int Doc::GetAdjustedDrawingPageHeight() const { assert(m_drawingPage); + // Take into account the PPU when getting the page height in facsimile if (this->IsTranscription() || this->IsFacs()) { - return m_drawingPage->m_pageHeight / DEFINITION_FACTOR; + const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); + return m_drawingPage->m_pageHeight / factor; } int contentHeight = m_drawingPage->GetContentHeight(); @@ -2141,8 +2143,10 @@ int Doc::GetAdjustedDrawingPageWidth() const { assert(m_drawingPage); + // Take into account the PPU when getting the page width in facsimile if (this->IsTranscription() || this->IsFacs()) { - return m_drawingPage->m_pageWidth / DEFINITION_FACTOR; + const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); + return m_drawingPage->m_pageWidth / factor; } int contentWidth = m_drawingPage->GetContentWidth(); diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 73ec1ef23ca..6c01cbc5217 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -12,6 +12,7 @@ #include "doc.h" #include "layerelement.h" #include "measure.h" +#include "miscfunctor.h" #include "page.h" #include "pb.h" #include "sb.h" @@ -39,7 +40,7 @@ SyncFromFacsimileFunctor::SyncFromFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ CLEF, CUSTOS, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; Zone *zone = layerElement->GetZone(); assert(zone); @@ -50,22 +51,54 @@ FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerEleme FunctorCode SyncFromFacsimileFunctor::VisitMeasure(Measure *measure) { - Zone *zone = measure->GetZone(); - assert(zone); - measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); - measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + // neon specific code - measure have no zone, we will use the staff one in VisitStaff + if (measure->IsNeumeLine()) { + m_currentNeumeLine = measure; + } + else { + Zone *zone = measure->GetZone(); + assert(zone); + measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + } return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitPage(Page *page) { + m_staffZones.clear(); m_currentPage = page; m_doc->SetDrawingPage(m_currentPage->GetIdx()); return FUNCTOR_CONTINUE; } +FunctorCode SyncFromFacsimileFunctor::VisitPageEnd(Page *page) +{ + // Used for adjusting staff size in neon - filled in VisitStaff + if (m_staffZones.empty()) return FUNCTOR_CONTINUE; + + // The staff size is calculated based on the zone height and takes into acocunt the rotation + for (auto &[staff, zone] : m_staffZones) { + double rotate = (zone->HasRotate()) ? zone->GetRotate() : 0.0; + int yDiff + = zone->GetLry() - zone->GetUly() - (zone->GetLrx() - zone->GetUlx()) * tan(abs(rotate) * M_PI / 180.0); + staff->m_drawingStaffSize + = 100 * yDiff / (m_doc->GetOptions()->m_unit.GetValue() * 2 * (staff->m_drawingLines - 1)); + } + + // Since we multiply all values by the DEFINITION_FACTOR, set it as PPU + m_currentPage->SetPPUFactor(DEFINITION_FACTOR); + if (m_currentPage->GetPPUFactor() != 1.0) { + ApplyPPUFactorFunctor applyPPUFactor; + m_currentPage->Process(applyPPUFactor); + m_doc->UpdatePageDrawingSizes(); + } + + return FUNCTOR_CONTINUE; +} + FunctorCode SyncFromFacsimileFunctor::VisitPb(Pb *pb) { // This would happen if we run the functor on data not converted to page-based @@ -109,12 +142,20 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) assert(zone); staff->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); + // neon specific code - set the position of the pseudo measure (neume line) + if (m_currentNeumeLine) { + m_currentNeumeLine->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + m_staffZones[staff] = zone; + } + return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitSystem(System *system) { m_currentSystem = system; + m_currentNeumeLine = NULL; return FUNCTOR_CONTINUE; } diff --git a/src/measure.cpp b/src/measure.cpp index a3c45c0bd12..9b13faf70db 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -213,6 +213,7 @@ void Measure::AddChildBack(Object *child) int Measure::GetDrawingX() const { + /* if (!this->IsMeasuredMusic()) { const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); assert(system); @@ -220,6 +221,7 @@ int Measure::GetDrawingX() const return (system->m_systemLeftMar); } } + */ if (m_drawingFacsX1 != VRV_UNSET) return m_drawingFacsX1; @@ -353,6 +355,7 @@ int Measure::GetRightBarLineRight() const int Measure::GetWidth() const { + /* if (!this->IsMeasuredMusic()) { const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); assert(system); @@ -363,6 +366,7 @@ int Measure::GetWidth() const return page->m_pageWidth - system->m_systemLeftMar - system->m_systemRightMar; } } + */ if (m_drawingFacsX2 != VRV_UNSET) return (m_drawingFacsX2 - m_drawingFacsX1); diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 1769f1f138d..1cec345599f 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -496,8 +496,8 @@ void SvgDeviceContext::StartPage() = StringFormat("0 0 %d %d", this->GetWidth(), this->GetHeight()).c_str(); } else { - m_currentNode.append_attribute("viewBox") = StringFormat( - "0 0 %d %d", this->GetWidth() * DEFINITION_FACTOR, this->GetContentHeight() * DEFINITION_FACTOR) + m_currentNode.append_attribute("viewBox") = StringFormat("0 0 %d %d", + int(this->GetWidth() * this->GetViewBoxFactor()), int(this->GetContentHeight() * this->GetViewBoxFactor())) .c_str(); } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 2ae7b97c19b..d79ed929038 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1525,7 +1525,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) std::swap(height, width); } - double userScale = m_view.GetPPUFactor() * m_options->m_scale.GetValue() / 100; + double userScale = m_options->m_scale.GetValue() / 100.0; assert(userScale != 0.0); if (m_options->m_scaleToPageSize.GetValue()) { @@ -1537,6 +1537,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) deviceContext->SetUserScale(userScale, userScale); deviceContext->SetWidth(width); deviceContext->SetHeight(height); + deviceContext->SetViewBoxFactor(m_view.GetPPUFactor()); if (m_doc.IsFacs()) { deviceContext->SetWidth(m_doc.GetFacsimile()->GetMaxX()); From 77e8fce761b23f2cac12ccc308e0c468042b67c7 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 22:06:09 +0100 Subject: [PATCH 062/383] Store staff rotation in Staff and adjust facsimile functor --- include/vrv/staff.h | 16 ++++++++++++++++ src/facsimilefunctor.cpp | 8 ++++++++ src/staff.cpp | 2 ++ src/view_page.cpp | 7 ++++++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/include/vrv/staff.h b/include/vrv/staff.h index cd68fe3eaa4..f11daee3f8e 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -112,6 +112,16 @@ class Staff : public Object, } ///@} + /** + * @name Getters and setters for the rotation. + * Used only with facsimile rendering. + */ + ///@{ + void SetDrawingRotation(double drawingRotation) { m_drawingRotation = drawingRotation; } + double GetDrawingRotation() const { return m_drawingRotation; } + bool HasDrawingRotation() const { return (m_drawingRotation != 0.0); } + ///@} + /** * Delete all the legder line arrays. */ @@ -290,6 +300,12 @@ class Staff : public Object, ArrayOfLedgerLines m_ledgerLinesAboveCue; ArrayOfLedgerLines m_ledgerLinesBelowCue; ///@} + + /** + * The drawing rotation. + * Used only with facsimile rendering + */ + double m_drawingRotation; }; } // namespace vrv diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 6c01cbc5217..0287275a7fc 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -86,6 +86,7 @@ FunctorCode SyncFromFacsimileFunctor::VisitPageEnd(Page *page) = zone->GetLry() - zone->GetUly() - (zone->GetLrx() - zone->GetUlx()) * tan(abs(rotate) * M_PI / 180.0); staff->m_drawingStaffSize = 100 * yDiff / (m_doc->GetOptions()->m_unit.GetValue() * 2 * (staff->m_drawingLines - 1)); + staff->SetDrawingRotation(rotate); } // Since we multiply all values by the DEFINITION_FACTOR, set it as PPU @@ -147,6 +148,13 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) m_currentNeumeLine->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); m_staffZones[staff] = zone; + + // The staff slope is going up. The y left postion needs to be adjusted accordingly + if (zone->GetRotate() < 0) { + staff->m_drawingFacsY = staff->m_drawingFacsY + + (m_currentNeumeLine->m_drawingFacsX2 - m_currentNeumeLine->m_drawingFacsX1) + * tan(zone->GetRotate() * M_PI / 180.0); + } } return FUNCTOR_CONTINUE; diff --git a/src/staff.cpp b/src/staff.cpp index 28b69219213..1a37ab7ead0 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -75,6 +75,7 @@ void Staff::Reset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; + m_drawingRotation = 0.0; ClearLedgerLines(); } @@ -90,6 +91,7 @@ void Staff::CloneReset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; + m_drawingRotation = 0.0; } void Staff::ClearLedgerLines() diff --git a/src/view_page.cpp b/src/view_page.cpp index 51d8b78be5c..a8f41dfb829 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1295,7 +1295,12 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys x1 = measure->GetDrawingX(); x2 = x1 + measure->GetWidth(); y1 = staff->GetDrawingY(); - y2 = y1; + if (!staff->HasDrawingRotation()) { + y2 = y1; + } + else { + y2 = y1 - staff->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); + } } const int lineWidth = m_doc->GetDrawingStaffLineWidth(staff->m_drawingStaffSize); From 33fa4a6e98732d78a9948b23a0649178b24e9327 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 22:28:25 +0100 Subject: [PATCH 063/383] Handle rotation offset when rendering layer elements --- include/vrv/staff.h | 1 + src/facsimilefunctor.cpp | 2 +- src/staff.cpp | 6 ++++++ src/view_element.cpp | 15 ++++++++++++--- src/view_neume.cpp | 20 ++++++++++---------- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/include/vrv/staff.h b/include/vrv/staff.h index f11daee3f8e..92a699a43bf 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -120,6 +120,7 @@ class Staff : public Object, void SetDrawingRotation(double drawingRotation) { m_drawingRotation = drawingRotation; } double GetDrawingRotation() const { return m_drawingRotation; } bool HasDrawingRotation() const { return (m_drawingRotation != 0.0); } + int GetDrawingRotationOffsetFor(int x); ///@} /** diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 0287275a7fc..436939cf0cf 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -149,7 +149,7 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); m_staffZones[staff] = zone; - // The staff slope is going up. The y left postion needs to be adjusted accordingly + // The staff slope is going up. The y left position needs to be adjusted accordingly if (zone->GetRotate() < 0) { staff->m_drawingFacsY = staff->m_drawingFacsY + (m_currentNeumeLine->m_drawingFacsX2 - m_currentNeumeLine->m_drawingFacsX1) diff --git a/src/staff.cpp b/src/staff.cpp index 1a37ab7ead0..1e3c1f00a52 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -94,6 +94,12 @@ void Staff::CloneReset() m_drawingRotation = 0.0; } +int Staff::GetDrawingRotationOffsetFor(int x) +{ + int xDiff = x - this->GetDrawingX(); + return int(xDiff * tan(this->GetDrawingRotation() * M_PI / 180.0)); +} + void Staff::ClearLedgerLines() { m_ledgerLinesAbove.clear(); diff --git a/src/view_element.cpp b/src/view_element.cpp index 3c698c7add7..236df764961 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -300,16 +300,19 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta } if (notationType == NOTATIONTYPE_neume) { - int rotateOffset = 0; + int rotationOffset = 0; if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { double deg = staff->GetDrawingRotate(); int xDiff = x - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); + } + else if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(x); } if (accid->HasFacs() && m_doc->IsFacs()) { y = ToLogicalY(y); } - y -= rotateOffset; + y -= rotationOffset; } this->DrawSmuflString( @@ -676,6 +679,9 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf int xDiff = x - staff->GetDrawingX(); y -= int(xDiff * tan(deg * M_PI / 180.0)); } + else if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); + } } else if (clef->GetShape() == CLEFSHAPE_perc) { y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); @@ -794,6 +800,9 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St int xDiff = x - staff->GetDrawingX(); y -= int(xDiff * tan(deg * M_PI / 180.0)); } + else if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); + } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 971a8bf3304..1b972bab007 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -228,14 +228,14 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); } int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotateOffset; + int rotationOffset = 0; if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { double deg = staff->GetDrawingRotate(); int xDiff = noteX - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); } - else { - rotateOffset = 0; + else if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(noteX); } if (nc->HasLoc()) { @@ -248,7 +248,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (clef->GetShape() == CLEFSHAPE_F) { pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); } - yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; + yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; } for (auto it = params.begin(); it != params.end(); it++) { @@ -392,17 +392,17 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S y -= (m_doc->GetDrawingUnit(staff->m_drawingStaffSize)) * 3; - int rotateOffset; + int rotationOffset = 0; if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { double deg = staff->GetDrawingRotate(); int xDiff = x - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + rotationOffset = int(xDiff * tan(deg * M_PI / 180.0)); } - else { - rotateOffset = 0; + else if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(x); } - y -= rotateOffset; + y -= rotationOffset; DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); From a806111cb843635a083ec3a2f7b49e33ba0b2395 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 9 Jan 2024 07:54:34 +0100 Subject: [PATCH 064/383] Fix custos positioning without facs --- src/view_element.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 236df764961..4bbd8ec48a8 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -766,17 +766,18 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St const int sym = custos->GetCustosGlyph(staff->m_drawingNotationType); int x, y; - if (custos->HasFacs() && m_doc->IsFacs()) { + // For neume notation we ignore the value set in CalcAlignmentPitchPosFunctor + if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { x = custos->GetDrawingX(); // Recalculate y from pitch to prevent visual/meaning mismatch Clef *clef = layer->GetClef(element); - y = ToLogicalY(staff->GetDrawingY()); + y = staff->GetDrawingY(); PitchInterface pi; // Neume notation uses C3 for C clef rather than C4. // Take this into account when determining location. // However this doesn't affect the value for F clef. pi.SetPname(PITCHNAME_c); - if ((staff->m_drawingNotationType == NOTATIONTYPE_neume) && (clef->GetShape() == CLEFSHAPE_C)) { + if (clef->GetShape() == CLEFSHAPE_C) { pi.SetOct(3); } else { From 61c6d9ba3dfdff3a9fa362d5d250b7728c8003bd Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 9 Jan 2024 11:23:05 +0100 Subject: [PATCH 065/383] Resolve `@facs` pointing to `` --- include/vrv/facsimileinterface.h | 7 +++++++ src/facsimilefunctor.cpp | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/vrv/facsimileinterface.h b/include/vrv/facsimileinterface.h index 7afafef144b..84f46ddb9b8 100644 --- a/include/vrv/facsimileinterface.h +++ b/include/vrv/facsimileinterface.h @@ -58,6 +58,13 @@ class FacsimileInterface : public Interface, public AttFacsimile { Zone *GetZone() { return m_zone; } const Zone *GetZone() const { return m_zone; } ///@} + /// + + /** Get the surface */ + ///@{ + Surface *GetSurface() { return m_surface; } + const Surface *GetSurface() const { return m_surface; } + ///@} //-----------------// // Pseudo functors // diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 436939cf0cf..83f53d6b275 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -106,9 +106,11 @@ FunctorCode SyncFromFacsimileFunctor::VisitPb(Pb *pb) assert(m_currentPage); Zone *zone = pb->GetZone(); - assert(zone && zone->GetParent()); - Surface *surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; - // Use the parent surface attributes if given + Surface *surface = pb->GetSurface(); + if (!surface && zone && zone->GetParent()) + surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; + assert(zone || surface); + // Use the (parent) surface attributes if given if (surface && surface->HasLrx() && surface->HasLry()) { m_currentPage->m_pageHeight = surface->GetLry() * DEFINITION_FACTOR; m_currentPage->m_pageWidth = surface->GetLrx() * DEFINITION_FACTOR; From 434c0859cb5ac4794e4602ac89eab2b3f3b8023e Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 9 Jan 2024 14:29:46 +0100 Subject: [PATCH 066/383] Fix milestone end for added mdiv and score --- src/doc.cpp | 2 ++ src/iomei.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/doc.cpp b/src/doc.cpp index 4e6e4b0b3d7..4300e41b8d8 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -1408,6 +1408,8 @@ void Doc::SyncToFacsimileDoc() if (!m_facsimile->FindDescendantByType(SURFACE)) { m_facsimile->AddChild(new Surface()); } + this->ScoreDefSetCurrentDoc(); + m_facsimile->SetType("transcription"); m_facsimile->ClearChildren(); diff --git a/src/iomei.cpp b/src/iomei.cpp index ebe8703ec85..badac886885 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -8206,12 +8206,14 @@ void MEIInput::UpgradePageTo_5_0(Page *page) PageMilestoneEnd *scoreEnd = new PageMilestoneEnd(score); page->AddChild(scoreEnd); + score->SetEnd(scoreEnd); Mdiv *mdiv = new Mdiv(); page->InsertChild(mdiv, 0); PageMilestoneEnd *mdivEnd = new PageMilestoneEnd(mdiv); page->AddChild(mdivEnd); + mdiv->SetEnd(mdivEnd); } void MEIInput::UpgradePgHeadFootTo_5_0(pugi::xml_node element) From c372f803c66ff5cae8e42e9c391e8cbb9070ccc5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 10 Jan 2024 08:01:15 +0100 Subject: [PATCH 067/383] Remove unused code in Measure --- src/measure.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/measure.cpp b/src/measure.cpp index 9b13faf70db..d5e48b679f7 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -213,16 +213,6 @@ void Measure::AddChildBack(Object *child) int Measure::GetDrawingX() const { - /* - if (!this->IsMeasuredMusic()) { - const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); - assert(system); - if (system->m_drawingFacsY != VRV_UNSET) { - return (system->m_systemLeftMar); - } - } - */ - if (m_drawingFacsX1 != VRV_UNSET) return m_drawingFacsX1; if (m_cachedDrawingX != VRV_UNSET) return m_cachedDrawingX; @@ -355,19 +345,6 @@ int Measure::GetRightBarLineRight() const int Measure::GetWidth() const { - /* - if (!this->IsMeasuredMusic()) { - const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); - assert(system); - if (system->m_drawingFacsY != VRV_UNSET) { - const Page *page = vrv_cast(system->GetFirstAncestor(PAGE)); - assert(page); - // xAbs2 = page->m_pageWidth - system->m_systemRightMar; - return page->m_pageWidth - system->m_systemLeftMar - system->m_systemRightMar; - } - } - */ - if (m_drawingFacsX2 != VRV_UNSET) return (m_drawingFacsX2 - m_drawingFacsX1); assert(m_measureAligner.GetRightAlignment()); From 6a3f7b8465461c9a85445636ba2028320f2a2091 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 10 Jan 2024 09:26:59 +0100 Subject: [PATCH 068/383] Use measure width for calculating staff slope --- src/view_page.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view_page.cpp b/src/view_page.cpp index a8f41dfb829..3a13c9d1cb9 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1299,7 +1299,7 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys y2 = y1; } else { - y2 = y1 - staff->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); + y2 = y1 - measure->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); } } From 5f03910431eab4e618627d09a54788c3334f9ad3 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sun, 21 Apr 2024 23:53:51 -0700 Subject: [PATCH 069/383] humlib update --- include/hum/humlib.h | 363 +- src/hum/humlib.cpp | 7767 +++++++++++++++++++++++++----------------- 2 files changed, 4912 insertions(+), 3218 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index a636e8698a5..609ab1f6f65 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Apr 4 23:30:44 PDT 2024 +// Last Modified: Fri Apr 19 18:10:19 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -2998,7 +2998,11 @@ class HumdrumFile : public HUMDRUMFILE_PARENT { -// Reference: Beyond Midi, page 410. +////////////////////////////// +// +// MuseData line types, reference: Beyond Midi, page 410. +// + #define E_muserec_note_regular 'N' // 'A' --> use type E_muserec_note_regular // 'B' --> use type E_muserec_note_regular @@ -3028,33 +3032,46 @@ class HumdrumFile : public HUMDRUMFILE_PARENT { #define E_muserec_comment_toggle '&' #define E_muserec_comment_line '@' #define E_muserec_musical_directions '*' + #define E_muserec_copyright '1' // reserved for copyright notice #define E_muserec_header_1 '1' // reserved for copyright notice + #define E_muserec_header_2 '2' // reserved for identification #define E_muserec_id '2' // reserved for identification + #define E_muserec_header_3 '3' // reserved + #define E_muserec_header_4 '4' // #define E_muserec_encoder '4' // + #define E_muserec_header_5 '5' // WK#: MV#: #define E_muserec_work_info '5' // WK#: MV#: + #define E_muserec_header_6 '6' // #define E_muserec_source '6' // + #define E_muserec_header_7 '7' // #define E_muserec_work_title '7' // + #define E_muserec_header_8 '8' // #define E_muserec_movement_title '8' // + #define E_muserec_header_9 '9' // #define E_muserec_header_part_name '9' // + #define E_muserec_header_10 '0' // misc designations + #define E_muserec_header_11 'A' // group memberships #define E_muserec_group_memberships 'A' // group memberships -// multiple musered_head_12 lines can occur: + +// multiple muserec_head_12 lines can occur: #define E_muserec_header_12 'B' // : part of #define E_muserec_group 'B' // : part of + #define E_muserec_unknown 'U' // unknown record type -#define E_muserec_empty 'E' // nothing on line and not header - // or multi-line comment +#define E_muserec_empty 'E' // nothing on line and not header or multi-line comment #define E_muserec_deleted 'D' // deleted line + // non-standard record types for MuseDataSet #define E_muserec_filemarker '+' #define E_muserec_filename 'F' @@ -3148,15 +3165,16 @@ class MuseRecordBasic { bool isBodyRecord (void); bool isChordGraceNote (void); bool isChordNote (void); - bool isDirection (void); + bool isDirection (void); // starts with "*" + bool isMusicalDirection (void); // starts with "*" bool isAnyComment (void); bool isLineComment (void); bool isBlockComment (void); - bool isCopyright (void); - bool isCueNote (void); - bool isEncoder (void); - bool isFiguredHarmony (void); - bool isGraceNote (void); + bool isCopyright (void); // 1st non-comment line in file + bool isCueNote (void); // starts with "c" + bool isEncoder (void); // 4th non-comment line in file + bool isFiguredHarmony (void); // starts with "f" + bool isGraceNote (void); // starts with "g" bool isGroup (void); bool isGroupMembership (void); bool isHeaderRecord (void); @@ -3167,7 +3185,7 @@ class MuseRecordBasic { bool isAnyRest (void); bool isRegularRest (void); bool isInvisibleRest (void); - bool isPrintSuggestion (void); + bool isPrintSuggestion (void); // starts with "P" bool isSource (void); bool isWorkInfo (void); bool isWorkTitle (void); @@ -3176,27 +3194,42 @@ class MuseRecordBasic { void setTpq (int value); static std::string musedataToUtf8 (std::string& input); + protected: - std::string m_recordString; // actual characters on line + std::string m_recordString; // actual characters on line + + std::vector m_printSuggestions; // print suggestions for this line (if applicable) + // print suggestions start with the letter "P" and + // follow a note/rest line, as well as musical + // direction lines that start with "*". The value + // int the difference in indexes between the current + // line and the print suggestion (typically +1). + + std::vector m_musicalDirections; // Musical directions associated with this line + // (if applicable) stored as delta indexes. Musical + // direction lines start with "*" and are used for + // dynamics, hairpins, etc. Typically -1 from a note + // or -2 if there is a print suggestion for the musical + // direction. // mark-up data for the line: - int m_lineindex; // index into original file - int m_type; // category of MuseRecordBasic record - HumNum m_absbeat; // dur in quarter notes from start - HumNum m_lineduration; // duration of line - HumNum m_noteduration; // duration of note - - int m_b40pitch; // base 40 pitch - int m_nexttiednote; // line number of next note tied to - // this one (-1 if no tied note) - int m_lasttiednote; // line number of previous note tied - // to this one (-1 if no tied note) + int m_lineindex; // index into original file + int m_type; // category of MuseRecordBasic record + HumNum m_absbeat; // dur in quarter notes from start + HumNum m_lineduration; // duration of line + HumNum m_noteduration; // duration of note + + int m_b40pitch; // base 40 pitch + int m_nexttiednote; // line number of next note tied to + // this one (-1 if no tied note) + int m_lasttiednote; // line number of previous note tied + // to this one (-1 if no tied note) int m_roundBreve; - int m_header = -1; // -1 = undefined, 0 = no, 1 = yes - int m_layer = 0; // voice/layer (track info but may be analyzed) - int m_tpq = 0; // ticks-per-quarter for durations - std::string m_graphicrecip; // graphical duration of note/rest - GridVoice* m_voice = NULL; // conversion structure that token is stored in. + int m_header = -1; // -1 = undefined, 0 = no, 1 = yes + int m_layer = 0; // voice/layer (track info but may be analyzed) + int m_tpq = 0; // ticks-per-quarter for durations + std::string m_graphicrecip; // graphical duration of note/rest + GridVoice* m_voice = NULL; // conversion structure that token is stored in. MuseData* m_owner = NULL; void setOwner (MuseData* owner); @@ -3204,6 +3237,7 @@ class MuseRecordBasic { public: static std::string trimSpaces (std::string input); + friend class MuseRecord; friend class MuseData; }; @@ -3217,18 +3251,20 @@ std::ostream& operator<<(std::ostream& out, MuseRecordBasic* aRecord); class MuseRecord : public MuseRecordBasic { public: - MuseRecord (void); - MuseRecord (const std::string& aLine); - MuseRecord (MuseRecord& aRecord); - ~MuseRecord (); + MuseRecord (void); + MuseRecord (const std::string& aLine); + MuseRecord (MuseRecord& aRecord); + ~MuseRecord (); - MuseRecord& operator=(MuseRecord& aRecord); + MuseRecord& operator= (MuseRecord& aRecord); - ////////////////////////////// - // functions which work with regular note, cue note, and grace note records - // (A..G, c, g) - // columns 1 -- 5: pitch field information + ////////////////////////////// + // + // functions which process regular notes (A-G), cue notes (c), grace notes (g), + // and chords (" "). Definitions stored in MuseRecord-note.cpp. + // + // columns 1-5: pitch field information std::string getNoteField (void); int getOctave (void); std::string getOctaveString (void); @@ -3251,7 +3287,7 @@ class MuseRecord : public MuseRecordBasic { void setStemDown (void); void setStemUp (void); - // columns 6 -- 9: duration field information + // columns 6-9: duration field information std::string getTickDurationField (void); std::string getTickDurationString (void); int getTickDuration (void); @@ -3295,9 +3331,9 @@ class MuseRecord : public MuseRecordBasic { void setNotehead128thMensural (void); void setNotehead256thMensural (void); - // columns 10 -- 12 ---> blank + // columns 10-12 ---> blank - // columns 13 -- 80: graphical and interpretive information + // columns 13-80: graphical and interpretive information // column 13: footnote flag std::string getFootnoteFlagField (void); @@ -3334,13 +3370,13 @@ class MuseRecord : public MuseRecordBasic { std::string getStringProlongation (void); int prolongationQ (void); - // column 19: actual notated accidentals + // column 19: notated accidentals std::string getNotatedAccidentalField (void); std::string getNotatedAccidentalString (void); int getNotatedAccidental (void); int notatedAccidentalQ (void); - // columns 20 -- 22: time modification + // columns 20-22: time modification std::string getTimeModificationField (void); std::string getTimeModification (void); std::string getTimeModificationLeftField (void); @@ -3353,21 +3389,21 @@ class MuseRecord : public MuseRecordBasic { int timeModificationLeftQ (void); int timeModificationRightQ (void); - // column 23 + // column 23: stem direction std::string getStemDirectionField (void); std::string getStemDirectionString (void); int getStemDirection (void); int stemDirectionQ (void); - // column 24 + // column 24: staff number for multi-staff parts std::string getStaffField (void); std::string getStaffString (void); int getStaff (void); int staffQ (void); - // column 25 ---> blank + // column 25: blank - // columns 26 - 31: beam codes + // columns 26-31: beaming std::string getBeamField (void); int beamQ (void); char getBeam8 (void); @@ -3382,11 +3418,12 @@ class MuseRecord : public MuseRecordBasic { int beam64Q (void); int beam128Q (void); int beam256Q (void); - std::string getKernBeamStyle (void); void setBeamInfo (std::string& strang); - // columns 32 -- 43: additional notation - std::string getAdditionalNotationsField (void); + // columns 32-43: additional notation + std::string getAdditionalNotationsField (void); // merge with below + std::string getOtherNotations (void); // merge with above + int hasFermata (void); int additionalNotationsQ (void); int getAddCount (void); std::string getAddItem (int elementIndex); @@ -3402,78 +3439,83 @@ class MuseRecord : public MuseRecordBasic { // int getNotationLevel int getSlurStartColumn (void); std::string getSlurParameterRegion (void); - void getSlurInfo (std::string& slurstarts, - std::string& slurends); + void getSlurInfo (std::string& slurstarts, std::string& slurends); - // columns 44 -- 80: text underlay + + // columns 44-80: text underlay std::string getTextUnderlayField (void); int textUnderlayQ (void); int getVerseCount (void); std::string getVerse (int index); std::string getVerseUtf8 (int index); - // general functions for note records: - std::string getKernNoteStyle (int beams = 0, int stems = 0); - std::string getKernNoteAccents (void); ////////////////////////////// - // functions which work with basso continuo figuration records ('f'): - + // + // functions which work with basso continuo figuration records ('f'), definitions + // stored in MuseRecord-figure.cpp + // // column 2: number of figure fields std::string getFigureCountField (void); std::string getFigureCountString (void); int getFigureCount (void); - // columns 3 -- 5 ---> blank + // columns 3-5: blank - // columns 6 -- 8: figure division pointer advancement (duration) + // columns 6-8: figure division pointer advancement (duration) std::string getFigurePointerField (void); int figurePointerQ (void); // same as note records: getDuration - // columns 9 -- 12 ---> blank + // columns 9-12: blank - // columns 13 -- 15: footnote and level information - // column 13 --> footnote: uses same functions as note records in col 13. - // column 14 --> level: uses same functions as note records on column 14. - // column 15 ---> blank + // columns 13-15: footnote and level information + // column 13: footnote: uses same functions as note records in col 13. + // column 14: level: uses same functions as note records on column 14. + // column 15: blank - // columns 17 -- 80: figure fields + // columns 17-80: figure fields std::string getFigureFields (void); std::string getFigureString (void); int figureFieldsQ (void); std::string getFigure (int index = 0); + ////////////////////////////// - // functions which work with combined records ('b', 'i'): + // + // functions that work with combined records ('b', 'i'): + // - ////////////////////////////// - // functions which work with measure records ('m'): - // columns 1 -- 7: measure style information - std::string getMeasureTypeField (void); + ////////////////////////////// + // + // functions which work with measure records ('m'), definitions stored + // in MuseRecord-measure.cpp. + // + // columns 1-7: measure style information + std::string getMeasureType (void); - // columns 9 -- 12: measure number (left justified) + // columns 9-12: measure number (left justified) std::string getMeasureNumberField (void); - std::string getMeasureNumberString (void); - int getMeasureNumber (void); - int measureNumberQ (void); + std::string getMeasureNumber (void); + bool measureNumberQ (void); - // columns 17 -- 80: measure flags - std::string getMeasureFlagsString (void); + // columns 17-80: measure flags + std::string getMeasureFlags (void); int measureFermataQ (void); - int measureFlagQ (const std::string& key); + bool measureFlagEqual (const std::string& key); void addMeasureFlag (const std::string& strang); - // general functions for measure records: - std::string getKernMeasureStyle (void); ////////////////////////////// - // functions which work with musical attributes records ('$'): + // + // Notation Attributes: functions which process musical attributes records ('$'), + // definitions stored in MuseRecord-attributes.cpp. + // std::string getAttributes (void); void getAttributeMap (std::map& amap); @@ -3481,37 +3523,74 @@ class MuseRecord : public MuseRecordBasic { int getAttributeInt (char attribute); int getAttributeField (std::string& output, const std::string& attribute); - ////////////////////////////// - // functions which work with musical direction records ('$'): + + ////////////////////////////// + // + // functions which work with musical direction records ('*'), + // definitions stored in MuseRecord-directions.cpp. + // // columns 17-18: type of direction std::string getDirectionTypeField (void); std::string getDirectionTypeString (void); - bool isTextDirection (void); + bool isDashStart (void); + bool isDashStop (void); + bool isDynamic (void); bool isHairpin (void); bool isHairpinStart (void); bool isHairpinStop (void); - bool isDashStart (void); - bool isDashStop (void); - bool isPedalStart (void); - bool isPedalEnd (void); - bool isRehearsal (void); bool isOctaveUpStart (void); bool isOctaveDownStart (void); bool isOctaveStop (void); + bool isPedalStart (void); + bool isPedalEnd (void); + bool isRehearsal (void); + bool isTextDirection (void); std::string getDirectionText (void); std::string getTextDirection (void) { return getDirectionText(); } - // - ////////////////////////////// + // Musical Directions: Lines starting with * + // Functions stored in src/MuseRecordBasic-directions.cpp + void addMusicDirection (int deltaIndex); + std::string getDirectionAsciiCharacters (void); + bool hasMusicalDirection (void); + MuseRecord* getMusicalDirection (int index = 0); + std::string getDynamicText (void); + MuseRecord* getDirectionRecord (int deltaIndex); + std::string getDirectionType (void); - std::string getKernRestStyle (void); + + ////////////////////////////// + // + // Print Suggestings: Lines starting with "P". Definititions stored + // in MuseRecord-suggestions.cpp. + // + + void addPrintSuggestion (int deltaIndex); bool hasPrintSuggestions (void); void getAllPrintSuggestions (std::vector& suggestions); void getPrintSuggestions (std::vector& suggestions, int column); + + + ////////////////////////////// + // + // Humdrum conversion related functions, store in MuseRecord-humdrum.cpp: + // + + std::string getKernMeasure (void); + std::string getKernBeamStyle (void); + std::string getKernNoteStyle (int beams = 0, int stems = 0); + std::string getKernNoteAccents (void); + std::string getKernNoteOtherNotations (void); + std::string getKernRestStyle (void); + + + // + ////////////////////////////// + protected: void allowNotesOnly (const std::string& functionName); void allowNotesAndRestsOnly (const std::string& functionName); @@ -3627,6 +3706,7 @@ class MuseData { // line-based (file-order indexing) accessor functions: MuseRecord& operator[] (int lindex); MuseRecord& getRecord (int lindex); + MuseRecord* getRecordPointer (int lindex); HumNum getTiedDuration (int lindex); HumNum getAbsBeat (int lindex); @@ -3669,6 +3749,8 @@ class MuseData { int getPartNameIndex (void); std::string getPartName (int index); void assignHeaderBodyState(void); + void linkPrintSuggestions (void); + void linkMusicDirections (void); public: static std::string trimSpaces (const std::string& input); @@ -4479,6 +4561,7 @@ class GridMeasure : public std::list { MeasureStyle getBarStyle (void) { return getStyle(); } void setStyle (MeasureStyle style) { m_style = style; } void setBarStyle (MeasureStyle style) { setStyle(style); } + void setKernBar (const std::string& tok); void setInvisibleBarline(void) { setStyle(MeasureStyle::Invisible); } void setFinalBarlineStyle(void) { setStyle(MeasureStyle::Final); } void setRepeatEndStyle(void) { setStyle(MeasureStyle::RepeatBackward); } @@ -4521,6 +4604,7 @@ class GridMeasure : public std::list { HumNum m_timestamp; HumNum m_timesigdur; MeasureStyle m_style; + std::string m_kernBar; int m_barnum = -1; }; @@ -5533,6 +5617,95 @@ class Tool_addic : public HumTool { }; +class Tool_addkey : public HumTool { + public: + Tool_addkey (void); + ~Tool_addkey () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void getLineIndexes (HumdrumFile& infile); + void insertReferenceKey (HumdrumFile& infile); + void addInputKey (HumdrumFile& infile); + void insertKeyDesig (HumdrumFile& infile, const string& keyDesig); + void printKeyDesig (HumdrumFile& infile, int index, const string& desig, int direction); + + private: + std::string m_key; + bool m_keyQ = false; + bool m_addKeyRefQ = false; + + int m_exinterpIndex = -1; + int m_refKeyIndex = -1; + int m_keyDesigIndex = -1; + int m_keySigIndex = -1; + int m_dataStartIndex = -1; + +}; + + +class Tool_addlabels : public HumTool { + public: + Tool_addlabels (void); + ~Tool_addlabels () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + int getExpansionIndex (HumdrumFile& infile); + void printExpansionLists(HumdrumFile& infile, int index); + void assignLabels (std::vector& llist, HumdrumFile& infile); + void addLabel (std::vector& llist, HumdrumFile& infile, + int barnum, int subbarnum, const std::string& label); + private: + std::string m_default; // set with the -d option + std::string m_norep; // set with the -r option + std::string m_zeroth; // m0 label (right after default and norep expansion lists) + int m_defaultIndex = -1; // line to place default expansions list above (and then norep) + std::vector m_barnums; // set with -l option + std::vector m_subbarnums; // set with -l option + std::vector m_labels; // set with -l option +}; + + +class Tool_addtempo : public HumTool { + public: + Tool_addtempo (void); + ~Tool_addtempo () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void assignTempoChanges (std::vector& tlist, + HumdrumFile& infile); + void addTempo (vector& tlist, + HumdrumFile& infile, + int measure, double tempo); + void addTempoToStart (vector& tlist, + HumdrumFile& infile, double tempo); + + private: + std::vector> m_tempos; + +}; + + class Tool_autoaccid : public HumTool { public: Tool_autoaccid (void); @@ -8983,6 +9156,10 @@ class Tool_musedata2hum : public HumTool { std::string cleanString (const std::string& input); void addTextDirection (GridMeasure* gm, int part, int staff, MuseRecord& mr, HumNum timestamp); + void addAboveBelowKernRdf (void); + bool needsAboveBelowKernRdf(void); + void addDirectionDynamics(GridSlice* slice, int part, MuseRecord& mr); + void printLine (std::ostream& out, HumdrumLine& line); private: // options: @@ -8991,6 +9168,7 @@ class Tool_musedata2hum : public HumTool { bool m_recipQ = false; // used with -r option std::string m_group = "score"; // used with -g option std::string m_omd = ""; // initial tempo designation (store for later output) + bool m_noOmvQ = false; // used with --omd option // state variables: int m_part = 0; // staff index currently being processed @@ -9000,6 +9178,7 @@ class Tool_musedata2hum : public HumTool { int m_lastbarnum = -1; // barnumber carried over from previous bar HTp m_lastnote = NULL; // for dealing with chords. double m_tempo = 0.0; // for initial tempo from MIDI settings + bool m_aboveBelowKernRdf = false; // output RDF**kern above/below markers std::map m_usedReferences; std::vector m_postReferences; diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 337cd5a0b0c..61e30070c71 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Apr 4 23:30:44 PDT 2024 +// Last Modified: Fri Apr 19 18:10:19 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -2397,6 +2397,8 @@ string Convert::museFiguredBassToKernFiguredBass(const string& mfb) { output += 'X'; } else if (mfb[i] == 'f') { // flat output += '-'; + } else if (mfb[i] == 'x') { // sharp + output += '#'; } else if ((mfb[i] == '&') && (i < (int)mfb.size()-1) && (mfb[i+1] == '0')) { output += ":"; i++; @@ -6321,6 +6323,17 @@ GridSlice* GridMeasure::addBarlineToken(const string& tok, HumNum timestamp, +////////////////////////////// +// +// GridMeasure::setKernBar -- set the token for the barline. +// + +void GridMeasure::setKernBar(const string& kernBar) { + m_kernBar = kernBar; +} + + + ////////////////////////////// // // GridMeasure::addFiguredBass -- @@ -8792,7 +8805,8 @@ void GridStaff::setNullTokenLayer(int layerindex, SliceType type, } cerr << "Warning, replacing existing token: " << *this->at(layerindex)->getToken() - << " with a null token" + << " with a null token around time " + << nextdur << endl; } } @@ -10870,6 +10884,12 @@ string HumGrid::createBarToken(int m, int barnum, GridMeasure* measure) { // void HumGrid::getMetricBarNumbers(vector& barnums) { +return; + +/* Disabling for now. Causes problems in MuseData conversion, but usually needed for MusicXML conversion + * to get correct measures numbers (related to pickup measures, particularly in older MusicXML files). + * For MuseData, the first barline in a score is not explicitly given, which is the source of the problem. + int mcount = (int)this->size(); barnums.resize(mcount); @@ -10923,6 +10943,7 @@ void HumGrid::getMetricBarNumbers(vector& barnums) { barnums[m] = counter++; } } +*/ } @@ -36567,6 +36588,17 @@ MuseRecord& MuseData::getRecord(int lindex) { } + +////////////////////////////// +// +// MuseData::getRecordPointer -- +// + +MuseRecord* MuseData::getRecordPointer(int lindex) { + return m_data[lindex]; +} + + ////////////////////////////// // // MuseData::getRecord -- This version with two index inputs is @@ -36677,6 +36709,8 @@ void MuseData::doAnalyses(void) { if (hasError()) { return; } analyzeTies(); if (hasError()) { return; } + linkPrintSuggestions(); + linkMusicDirections(); } @@ -37035,8 +37069,8 @@ void MuseData::analyzeType(void) { case 'P': thing[i].setType(E_muserec_print_suggestion); break; case 'S': thing[i].setType(E_muserec_sound_directives); break; case '/': thing[i].setType(E_muserec_end); - foundend = 1; - break; + foundend = 1; + break; case 'a': thing[i].setType(E_muserec_append); break; case 'b': thing[i].setType(E_muserec_backspace); break; case 'f': thing[i].setType(E_muserec_figured_harmony); break; @@ -37045,8 +37079,8 @@ void MuseData::analyzeType(void) { case 'r': thing[i].setType(E_muserec_rest); break; case '*': thing[i].setType(E_muserec_musical_directions); break; case '$': thing[i].setType(E_muserec_musical_attributes); - foundattributes = 1; - break; + foundattributes = 1; + break; } } } @@ -38826,6 +38860,83 @@ void MuseData::assignHeaderBodyState(void) { } +////////////////////////////// +// +// MuseData::linkPrintSuggestions -- Store print suggestions with +// the record that they apply to. A print suggestion starts +// with the letter "P" and follows immediately after the +// record to which they apply (unless another print suggestion +// or a comment. +// + +void MuseData::linkPrintSuggestions(void) { + // don't go all of the way to 0: stop at header: + vector Plines; + for (int i=getLineCount()-1; i>=0; i--) { + if (!m_data[i]->isPrintSuggestion()) { + continue; + } + Plines.clear(); + Plines.push_back(i); + i--; + while (i>=0 && (m_data[i]->isPrintSuggestion() || m_data[i]->isAnyComment())) { + if (m_data[i]->isPrintSuggestion()) { + cerr << "PRINT SUGGESTION: " << m_data[i] << endl; + Plines.push_back(i); + } + i--; + } + if (i<0) { + break; + } + // Store the print suggestions on the current line, which is at least + // a note/rest or musical direction. + for (int j=0; j<(int)Plines.size(); j++) { + m_data[i]->addPrintSuggestion(Plines[j] - i); + } + Plines.clear(); + } +} + + + +////////////////////////////// +// +// MuseData::linkMusicDirections -- Store music directions with +// the record that they apply to. A music direction starts +// with "*" and precedes the record to which they apply (unless +// a print suggestion or a comment intervenes. +// + + +void MuseData::linkMusicDirections(void) { + vector Dlines; + for (int i=0; iisDirection()) { + continue; + } + Dlines.clear(); + Dlines.push_back(i); + i++; + while (iisAnyNoteOrRest()) { + if (m_data[i]->isMusicalDirection()) { + Dlines.push_back(i); + } + i++; + } + if (i>=getLineCount()) { + break; + } + // Store the print suggestions on the current line, which is hopefully + // a note/rest or musical direction. + for (int j=0; j<(int)Dlines.size(); j++) { + m_data[i]->addMusicDirection(Dlines[j] - i); + } + Dlines.clear(); + } +} + + /////////////////////////////////////////////////////////////////////////// // @@ -39315,81 +39426,262 @@ ostream& operator<<(ostream& out, MuseDataSet& musedataset) { ////////////////////////////// // -// MuseRecord::MuseRecord -- +// MuseRecord::getAttributeMap -- // -MuseRecord::MuseRecord(void) : MuseRecordBasic() { } -MuseRecord::MuseRecord(const string& aLine) : MuseRecordBasic(aLine) { } -MuseRecord::MuseRecord(MuseRecord& aRecord) : MuseRecordBasic(aRecord) { } +void MuseRecord::getAttributeMap(map& amap) { + amap.clear(); + // Should be "3" on the next line, but "1" or "2" might catch poorly formatted data. + string contents = getLine().substr(2); + if (contents.empty()) { + return; + } + int i = 0; + string key; + string value; + int state = 0; // 0 outside, 1 = in key, 2 = in value + while (i < (int)contents.size()) { + switch (state) { + case 0: // outside of key or value + if (!isspace(contents[i])) { + if (contents[i] == ':') { + // Strange: should not happen + key.clear(); + state = 2; + } else { + state = 1; + key += contents[i]; + } + } + break; + case 1: // in key + if (!isspace(contents[i])) { + if (contents[i] == ':') { + value.clear(); + state = 2; + } else { + // Add to key, such as "C2" for second staff clef. + key += contents[i]; + } + } + break; + case 2: // in value + if (key == "D") { + value += contents[i]; + } else if (isspace(contents[i])) { + // store parameter and clear variables + amap[key] = value; + state = 0; + key.clear(); + value.clear(); + } else { + value += contents[i]; + } + break; + } + i++; + } + + if ((!key.empty()) && (!value.empty())) { + amap[key] = value; + } +} ////////////////////////////// // -// MuseRecord::~MuseRecord -- +// MuseRecord::getAttributes -- // -MuseRecord::~MuseRecord() { - // do nothing +string MuseRecord::getAttributes(void) { + string output; + switch (getType()) { + case E_muserec_musical_attributes: + break; + default: + cerr << "Error: cannot use getAttributes function on line: " + << getLine() << endl; + return ""; + } + + int ending = 0; + int tempcol; + for (int column=4; column <= getLength(); column++) { + if (getColumn(column) == ':') { + tempcol = column - 1; + while (tempcol > 0 && getColumn(tempcol) != ' ') { + tempcol--; + } + tempcol++; + while (tempcol <= column) { + output += getColumn(tempcol); + if (output.back() == 'D') { + ending = 1; + } + tempcol++; + } + } + if (ending) { + break; + } + } + return output; } ////////////////////////////// // -// MuseRecord::operator= -- +// MuseRecord::attributeQ -- // -MuseRecord& MuseRecord::operator=(MuseRecord& aRecord) { - // don't copy onto self - if (&aRecord == this) { - return *this; +int MuseRecord::attributeQ(const string& attribute) { + switch (getType()) { + case E_muserec_musical_attributes: + break; + default: + cerr << "Error: cannot use getAttributes function on line: " + << getLine() << endl; + return 0; } - setLine(aRecord.getLine()); - setType(aRecord.getType()); - m_lineindex = aRecord.m_lineindex; - m_absbeat = aRecord.m_absbeat; - m_lineduration = aRecord.m_lineduration; - m_noteduration = aRecord.m_noteduration; + string attributelist = getAttributes(); - m_b40pitch = aRecord.m_b40pitch; - m_nexttiednote = aRecord.m_nexttiednote; - m_lasttiednote = aRecord.m_lasttiednote; + int output = 0; + int attstrlength = (int)attributelist.size(); + int attlength = (int)attribute.size(); - return *this; + for (int i=0; i 0 && getColumn(tempcol) != ' ') { + tempcol--; + } + tempcol++; + while (tempcol <= column) { + if (getColumn(tempcol) == attribute) { + ending = 2; + } else if (getColumn(tempcol) == 'D') { + ending = 1; + } + tempcol++; + // index++; + } + } + if (ending) { + break; + } + } + + if (ending == 0 || ending == 1) { + return output; + } else { + string value = &getColumn(column+1); + if (value.empty()) { + output = std::stoi(value); + return output; + } else { + return 0; + } + } +} + + ////////////////////////////// // -// MuseRecord::getNoteField -- returns the string containing the pitch, -// accidental and octave characters. +// MuseRecord::getAttributeField -- returns true if found attribute // -string MuseRecord::getNoteField(void) { +int MuseRecord::getAttributeField(string& value, const string& key) { switch (getType()) { - case E_muserec_note_regular: - return extract(1, 4); - break; - case E_muserec_note_chord: - case E_muserec_note_cue: - case E_muserec_note_grace: - return extract(2, 5); + case E_muserec_musical_attributes: break; default: - cerr << "Error: cannot use getNoteField function on line: " - << getLine() << endl; + cerr << "Error: cannot use getAttributeInt function on line: " + << getLine() << endl; + return 0; + } + + int returnValue = 0; + int ending = 0; + // int index = 0; + int tempcol; + int column; + for (column=4; column <= getLength(); column++) { + if (getColumn(column) == ':') { + tempcol = column - 1; + while (tempcol > 0 && getColumn(tempcol) != ' ') { + tempcol--; + } + tempcol++; + while (tempcol <= column) { + if (getColumn(tempcol) == key[0]) { + ending = 2; + } else if (getColumn(tempcol) == 'D') { + ending = 1; + } + tempcol++; + // index++; + } + } + if (ending) { + break; + } + } + + value.clear(); + if (ending == 0 || ending == 1) { + return returnValue; + } else { + returnValue = 1; + column++; + while (getColumn(column) != ' ') { + value += getColumn(column++); + } + return returnValue; } - return ""; } @@ -39397,327 +39689,235 @@ string MuseRecord::getNoteField(void) { ////////////////////////////// // -// MuseRecord::getOctave -- returns the first numeric character -// in the note field of a MuseData note record +// MuseRecord::addMusicDirection -- add a delta index for associated +// print suggestion. // -int MuseRecord::getOctave(void) { - string recordInfo = getNoteField(); - int index = 0; - while ((index < (int)recordInfo.size()) && !std::isdigit(recordInfo[index])) { - index++; - } - if (index >= (int)recordInfo.size()) { - cerr << "Error: no octave specification in note field: " << recordInfo - << endl; - return 0; - } - return recordInfo[index] - '0'; +void MuseRecord::addMusicDirection(int deltaIndex) { + m_musicalDirections.push_back(deltaIndex); } -string MuseRecord::getOctaveString(void) { - string recordInfo = getNoteField(); - int index = 0; - while ((index < (int)recordInfo.size()) && !std::isdigit(recordInfo[index])) { - index++; + +////////////////////////////// +// +// MuseRecord::getDirectionAsciiCharacters -- returns columns 25 +// and later, but with the return string removing any trailing spaces. +// + +std::string MuseRecord::getDirectionAsciiCharacters(void) { + if (!isDirection()) { + return ""; } - if (index >= (int)recordInfo.size()) { - cerr << "Error: no octave specification in note field: " << recordInfo - << endl; + string& mrs = m_recordString; + if (mrs.size() < 25) { return ""; } - string output; - output += recordInfo[index]; - return output; + string output = mrs.substr(24); + size_t endpos = output.find_last_not_of(" \t\r\n"); + return (endpos != std::string::npos) ? output.substr(0, endpos + 1) : ""; } ////////////////////////////// // -// MuseRecord::getPitch -- int version returns the base40 representation +// MuseRecord::hasMusicalDirection -- // -int MuseRecord::getPitch(void) { - string recordInfo = getNoteField(); - return Convert::museToBase40(recordInfo); -} - - -string MuseRecord::getPitchString(void) { - string output = getNoteField(); - int len = (int)output.size(); - int index = len-1; - while (index >= 0 && output[index] == ' ') { - output.resize(index); - index--; +bool MuseRecord::hasMusicalDirection(void) { + if (isDirection()) { + return true; } - return output; + if (!m_musicalDirections.empty()) { + return true; + } + return false; } ////////////////////////////// // -// MuseRecord::getPitchClass -- returns the pitch without the octave information +// MuseRecord::getMusicalDuration -- return any associated +// Musical Direction record for the current record. If there +// is no linked direction, then return NULL. If the record is +// itself a muscial direction, return the pointer to the record. +// Default value for index is 0. // -int MuseRecord::getPitchClass(void) { - return getPitch() % 40; -} - - -string MuseRecord::getPitchClassString(void) { - string output = getNoteField(); - int index = 0; - while ((index < (int)output.size()) && !std::isdigit(output[index])) { - index++; +MuseRecord* MuseRecord::getMusicalDirection(int index) { + if (m_musicalDirections.empty()) { + return NULL; } - output.resize(index); - return output; + if (index >= (int)m_musicalDirections.size()) { + return NULL; + } + return getDirectionRecord(m_musicalDirections.at(index)); } ////////////////////////////// // -// MuseRecord::getAccidental -- int version return -2 for double flat, -// -1 for flat, 0 for natural, +1 for sharp, +2 for double sharp +// MuseRecord::getDirectionRecord -- return the given direction from the store +// delta index for the musical direction line. Default value index = 0. // -int MuseRecord::getAccidental(void) { - string recordInfo = getNoteField(); - int output = 0; - int index = 0; - while ((index < (int)recordInfo.size()) && (index < 16)) { - if (recordInfo[index] == 'f') { - output--; - } else if (recordInfo[index] == '#') { - output++; - } - index++; +MuseRecord* MuseRecord::getDirectionRecord(int deltaIndex) { + int index = m_lineindex + deltaIndex; + if (index < 0) { + return NULL; } - return output; + if (!m_owner) { + return NULL; + } + int lineCount = m_owner->getLineCount(); + if (index >= lineCount) { + return NULL; + } + return m_owner->getRecordPointer(index); } -string MuseRecord::getAccidentalString(void) { - string output; - int type = getAccidental(); - switch (type) { - case -2: output = "ff"; break; - case -1: output = "f"; break; - case 0: output = ""; break; - case 1: output = "#"; break; - case 2: output = "##"; break; - default: - output = getNoteField(); - cerr << "Error: unknown type of accidental: " << output << endl; - return ""; + +////////////////////////////// +// +// MuseRecord::getDirectionType -- columns 17 and 18 of +// musical directions. This function will remove space +// chaters from the columns. +// +// Direction Types: +// ================= +// A = segno sign +// E = dynamics hairpin start (qualifiers [BCDG]) +// F = dynamics hairpin start +// G = dynamics letters (in columns 25+) +// H = dash line start (qualifiers [BCDG]) +// J = dash line end (qualifiers [BCDG]) +// P = piano pedal start +// Q = piano pedal end +// R = rehearsal number or letter +// U = shift notes up (usually by 8va) +// V = shift notes down (usually by 8va) +// W = stop octave shift +// X = tie terminator +// + +string MuseRecord::getDirectionType(void) { + if (!isDirection()) { + return ""; } - return output; + string value = getColumns(17, 18); + if (value[1] == ' ') { + value.resize(1); + } + if (value[0] == ' ') { + value.resize(0); + } + return value; } ////////////////////////////// // -// MuseRecord::getBase40 -- return the base40 pitch value of the data -// line. Middle C set to 40 * 4 + 2; Returns -100 for non-pitched items. -// (might have to update for note_cur_chord and note_grace_chord which -// do not exist yet. +// MuseRecord::isDynamic -- helper function for getDirectionType() == "G". // -int MuseRecord::getBase40(void) { - switch (getType()) { - case E_muserec_note_regular: - case E_muserec_note_chord: - case E_muserec_note_cue: - case E_muserec_note_grace: - break; - default: - return -100; +bool MuseRecord::isDynamic(void) { + string dirtype = getDirectionType(); + if (dirtype.empty()) { + return false; + } + if (dirtype.at(0) == 'G') { + return true; + } else { + return false; } - return getPitch(); } ////////////////////////////// // -// MuseRecord::setStemDown -- +// MuseRecord::getDynamicText -- // -void MuseRecord::setStemDown(void) { - getColumn(23) = 'd'; +string MuseRecord::getDynamicText(void) { + return getDirectionAsciiCharacters(); } + ////////////////////////////// // -// MuseRecord::setStemUp -- +// MuseRecord::getFigureCountField -- column 2. // -void MuseRecord::setStemUp(void) { - getColumn(23) = 'u'; +string MuseRecord::getFigureCountField(void) { + allowFigurationOnly("getFigureCountField"); + return extract(2, 2); } ////////////////////////////// // -// MuseRecord::setPitch -- input is a base40 value which gets converted -// to a diatonic pitch name. -// Default value: chordnote = 0 -// Default value: gracenote = 0 +// MuseRecord::getFigurationCountString -- // -void MuseRecord::setPitch(int base40, int chordnote, int gracenote) { - string diatonic; - switch (Convert::base40ToDiatonic(base40) % 7) { - case 0: diatonic = 'C'; break; - case 1: diatonic = 'D'; break; - case 2: diatonic = 'E'; break; - case 3: diatonic = 'F'; break; - case 4: diatonic = 'G'; break; - case 5: diatonic = 'A'; break; - case 6: diatonic = 'B'; break; - default: diatonic = 'X'; +string MuseRecord::getFigureCountString(void) { + allowFigurationOnly("getFigureCount"); + string output = extract(2, 2); + if (output[0] == ' ') { + output = ""; } + return output; +} - string octave; - octave += char('0' + base40 / 40); - - string accidental; - int acc = Convert::base40ToAccidental(base40); - switch (acc) { - case -2: accidental = "ff"; break; - case -1: accidental = "f"; break; - case +1: accidental = "#"; break; - case +2: accidental = "##"; break; - } - string pitchname = diatonic + accidental + octave; - if (chordnote) { - if (gracenote) { - setGraceChordPitch(pitchname); - } else { - setChordPitch(pitchname); - } - } else { - setPitch(pitchname); - } -} +////////////////////////////// +// +// MuseRecord::getFigurationCount -- +// -void MuseRecord::setChordPitch(const string& pitchname) { - getColumn(1) = ' '; - setPitchAtIndex(1, pitchname); -} - -void MuseRecord::setGracePitch(const string& pitchname) { - getColumn(1) = 'g'; - setPitchAtIndex(1, pitchname); -} - -void MuseRecord::setGraceChordPitch(const string& pitchname) { - getColumn(1) = 'g'; - getColumn(2) = ' '; - setPitchAtIndex(2, pitchname); -} - -void MuseRecord::setCuePitch(const string& pitchname) { - getColumn(1) = 'c'; - setPitchAtIndex(1, pitchname); -} - - -void MuseRecord::setPitch(const string& pitchname) { - int start = 0; - // If the record is already set to a grace note or a cue note, - // then place pitch information starting at column 2 (index 1). - if ((getColumn(1) == 'g') || (getColumn(1) == 'c')) { - start = 1; - } - setPitchAtIndex(start, pitchname); -} - - -void MuseRecord::setPitchAtIndex(int index, const string& pitchname) { - int len = (int)pitchname.size(); - if ((len > 4) && (pitchname != "irest")) { - cerr << "Error in MuseRecord::setPitchAtIndex: " << pitchname << endl; - return; - } - insertString(index+1, pitchname); - - // Clear any text fields not used by current pitch data. - for (int i=4-len-1; i>=0; i--) { - (*this)[index + len + i] = ' '; - } +int MuseRecord::getFigureCount(void) { + allowFigurationOnly("getFigureCount"); + string temp = getFigureCountString(); + int output = (int)strtol(temp.c_str(), NULL, 36); + return output; } ////////////////////////////// // -// MuseRecord::getTickDurationField -- returns the string containing the -// duration, and tie information. +// getFigurePointerField -- columns 6 -- 8. // -string MuseRecord::getTickDurationField(void) { - switch (getType()) { - case E_muserec_figured_harmony: - case E_muserec_note_regular: - case E_muserec_note_chord: - case E_muserec_rest: - case E_muserec_backward: - case E_muserec_forward: - return extract(6, 9); - break; - // these record types do not have duration, per se: - case E_muserec_note_cue: - case E_muserec_note_grace: - default: - return " "; - // cerr << "Error: cannot use getTickDurationField function on line: " - // << getLine() << endl; - // return ""; - } - return ""; +string MuseRecord::getFigurePointerField(void) { + allowFigurationOnly("getFigurePointerField"); + return extract(6, 8); } - ////////////////////////////// // -// MuseRecord::getTickDurationString -- returns the string containing the duration, +// figurePointerQ -- // -string MuseRecord::getTickDurationString(void) { - string output = getTickDurationField(); - int length = (int)output.size(); - int i = length - 1; - while (i>0 && (output[i] == '-' || output[i] == ' ')) { - output.resize(i); - i--; - length--; - } - - int start = 0; - while (output[start] == ' ') { - start++; - } - - if (start != 0) { - for (i=0; i= 0; i--) { + if (isspace(output[i])) { + output.resize((int)output.size() - 1); + } else { + break; + } } - return std::stoi(recordInfo); + return output; } ////////////////////////////// // -// MuseRecord::getLineTickDuration -- returns the logical duration of the -// data line. Supresses the duration field of secondary chord notes. +// MuseRecord::getFigureFields -- columns 17 -- 80 // -int MuseRecord::getLineTickDuration(void) { - if (getType() == E_muserec_note_chord) { - return 0; - } - - string recordInfo = getTickDurationString(); - if (recordInfo.empty()) { - return 0; - } - int value = std::stoi(recordInfo); - if (getType() == E_muserec_backspace) { - return -value; - } - - return value; +string MuseRecord::getFigureFields(void) { + allowFigurationOnly("getFigureFields"); + return extract(17, 80); } - ////////////////////////////// // -// MuseRecord::getTicks -- similar to getLineTickDuration, but is non-zero -// for secondary chord notes. +// MuseRecord::figureFieldsQ -- // -int MuseRecord::getTicks(void) { - string recordInfo = getTickDurationString(); - if (recordInfo.empty()) { - return 0; - } - int value = std::stoi(recordInfo); - if (getType() == E_muserec_backspace) { - return -value; +int MuseRecord::figureFieldsQ(void) { + allowFigurationOnly("figureFieldsQ"); + int output = 0; + if (getLength() < 17) { + output = 0; + } else { + for (int i=17; i<=80; i++) { + if (getColumn(i) != ' ') { + output = 1; + break; + } + } } - - return value; + return output; } + ////////////////////////////// // -// MuseRecord::getNoteTickDuration -- Similar to getLineTickDuration, -// but do not suppress the duration of secondary chord-tones. +// getFigure -- // -int MuseRecord::getNoteTickDuration(void) { - string recordInfo = getTickDurationString(); - int value = 0; - if (recordInfo.empty()) { - return value; +string MuseRecord::getFigure(int index) { + string output; + allowFigurationOnly("getFigure"); + if (index >= getFigureCount()) { + return output; } - value = std::stoi(recordInfo); - if (getType() == E_muserec_backspace) { - return -value; + string temp = getFigureString(); + if (index == 0) { + return temp; } - return value; -} - - - -////////////////////////////// -// -// MuseRecord::setDots -- -// - -void MuseRecord::setDots(int value) { - switch (value) { - case 0: getColumn(18) = ' '; break; - case 1: getColumn(18) = '.'; break; - case 2: getColumn(18) = ':'; break; - case 3: getColumn(18) = ';'; break; - case 4: getColumn(18) = '!'; break; - default: cerr << "Error in MuseRecord::setDots : " << value << endl; + HumRegex hre; + vector pieces; + hre.split(pieces, temp, " +"); + if (index < (int)pieces.size()) { + output = pieces[index]; } + return output; } + ////////////////////////////// // -// MuseRecord::getDotCount -- +// MuseRecord::getKernBeamStyle -- // -int MuseRecord::getDotCount(void) { - char value = getColumn(18); - switch (value) { - case ' ': return 0; - case '.': return 1; - case ':': return 2; - case ';': return 3; - case '!': return 4; +string MuseRecord::getKernBeamStyle(void) { + string output; + string beams = getBeamField(); + for (int i=0; i<(int)beams.size(); i++) { + switch (beams[i]) { + case '[': // start beam + output += "L"; + break; + case '=': // continue beam + // do nothing + break; + case ']': // end beam + output += "J"; + break; + case '/': // forward hook + output += "K"; + break; + case '\\': // backward hook + output += "k"; + break; + default: + ; // do nothing + } } - return 0; + return output; } ////////////////////////////// // -// MuseRecord::setNoteheadShape -- Duration with augmentation dot component -// removed. Duration of 1 is quarter note. +// MuseRecord::getKernNoteStyle -- +// default values: beams = 0, stems = 0 // -void MuseRecord::setNoteheadShape(HumNum duration) { - HumNum note8th(1,2); - HumNum note16th(1,4); - HumNum note32nd(1,8); - HumNum note64th(1,16); - HumNum note128th(1,32); - HumNum note256th(1,64); +string MuseRecord::getKernNoteStyle(int beams, int stems) { + string output; - if (duration > 16) { // maxima - setNoteheadMaxima(); - } else if (duration > 8) { // long - setNoteheadLong(); - } else if (duration > 4) { // breve - if (m_roundBreve) { - setNoteheadBreveRound(); + if (!isAnyNote()) { + // not a note, so return nothing + return ""; + } + + // place the rhythm + stringstream tempdur; + int notetype = getGraphicNoteType(); + if (timeModificationLeftQ()) { + notetype = notetype / 4 * getTimeModificationLeft(); + if (timeModificationRightQ()) { + notetype = notetype * getTimeModificationRight(); } else { - setNoteheadBreve(); + notetype = notetype * 2; } - } else if (duration > 2) { // whole note - setNoteheadWhole(); - } else if (duration > 1) { // half note - setNoteheadHalf(); - } else if (duration > note8th) { // quarter note - setNoteheadQuarter(); - } else if (duration > note16th) { // eighth note - setNotehead8th(); - } else if (duration > note32nd) { // 16th note - setNotehead16th(); - } else if (duration > note64th) { // 32nd note - setNotehead32nd(); - } else if (duration > note128th) { // 64th note - setNotehead64th(); - } else if (duration > note256th) { // 128th note - setNotehead128th(); - } else if (duration == note256th) { // 256th note - // not allowing tuplets on the 256th note level. - setNotehead256th(); - } else { - cerr << "Error in duration: " << duration << endl; - return; } -} + // logical duration of the note + HumNum logicalduration = getTicks(); + logicalduration /= getTpq(); + string durrecip = Convert::durationToRecip(logicalduration); + // graphical duration of the note + string graphicrecip = getGraphicRecip(); + HumNum graphicdur = Convert::recipToDuration(graphicrecip); -////////////////////////////// -// -// MuseRecord::setNoteheadShape -- Duration with augmentation dot component -// removed. Duration of 1 is quarter note. -// + string displayrecip; -void MuseRecord::setNoteheadShapeMensural(HumNum duration) { - HumNum note8th(1, 2); - HumNum note16th(1, 4); - HumNum note32th(1, 8); - HumNum note64th(1, 16); - HumNum note128th(1, 32); - HumNum note256th(1, 64); + if (graphicdur != logicalduration) { + // switch to the logical duration and store the graphic + // duration. The logical duration will be used on the + // main kern token, and the graphic duration will be stored + // as a layout parameter, such as !LO:N:vis=4. to display + // the note as a dotted quarter regardless of the logical + // duration. - if (duration > 16) { // maxima - setNoteheadMaxima(); - } else if (duration > 8) { // long - setNoteheadLong(); - } else if (duration > 4) { // breve - setNoteheadBreve(); - } else if (duration > 2) { // whole note - setNoteheadWholeMensural(); - } else if (duration > 1) { // half note - setNoteheadHalfMensural(); - } else if (duration > note8th) { // quarter note - setNoteheadQuarterMensural(); - } else if (duration > note16th) { // eighth note - setNotehead8thMensural(); - } else if (duration > note32th) { // 16th note - setNotehead16thMensural(); - } else if (duration > note64th) { // 32nd note - setNotehead32ndMensural(); - } else if (duration > note128th) { // 64th note - setNotehead64thMensural(); - } else if (duration > note256th) { // 128th note - setNotehead128thMensural(); - } else if (duration >= note256th) { // 256th note - // don't allow tuplets on 256th note level. - setNotehead256thMensural(); - } else { - cerr << "Error in duration: " << duration << endl; - return; - } -} + // Current test file has encoding bug related to triplets, so + // disable graphic notation dealing with tuplets for now. -void MuseRecord::setNoteheadMaxima(void) { - if ((*this)[0] == 'c' || ((*this)[0] == 'g')) { - cerr << "Error: cue/grace notes cannot be maximas in setNoteheadLong" - << endl; - return; - } else { - getColumn(17) = 'M'; + // for now just looking to see if one has a dot and the other does not + if ((durrecip.find(".") != string::npos) && + (graphicrecip.find(".") == string::npos)) { + m_graphicrecip = graphicrecip; + displayrecip = durrecip; + } else if ((durrecip.find(".") == string::npos) && + (graphicrecip.find(".") != string::npos)) { + m_graphicrecip = graphicrecip; + displayrecip = durrecip; + } } -} -void MuseRecord::setNoteheadLong(void) { - if ((*this)[0] == 'c' || ((*this)[0] == 'g')) { - cerr << "Error: cue/grace notes cannot be longs in setNoteheadLong" - << endl; - return; + if (displayrecip.size() > 0) { + output = displayrecip; } else { - getColumn(17) = 'L'; + tempdur << notetype; + output = tempdur.str(); + // add any dots of prolongation to the output string + output += getStringProlongation(); } -} -void MuseRecord::setNoteheadBreve(void) { - setNoteheadBreveSquare(); -} + // add the pitch to the output string + string musepitch = getPitchString(); + string kernpitch = Convert::musePitchToKernPitch(musepitch); + output += kernpitch; -void MuseRecord::setNoteheadBreveSquare(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = 'A'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = 'A'; - } else { // normal note - getColumn(17) = 'B'; - } -} + string logicalAccidental = getAccidentalString(); + string notatedAccidental = getNotatedAccidentalString(); -void MuseRecord::setNoteheadBreveRound(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = 'A'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = 'A'; - } else { // normal note - getColumn(17) = 'b'; + if (notatedAccidental.empty() && !logicalAccidental.empty()) { + // Indicate that the logical accidental should not be + // displayed (because of key signature or previous + // note in the measure that alters the accidental + // state of the current note). + output += "y"; + } else if ((logicalAccidental == notatedAccidental) && !notatedAccidental.empty()) { + // Indicate that the accidental should be displayed + // and is not suppressed by the key signature or a + // previous note in the measure. + output += "X"; } -} - -void MuseRecord::setNoteheadBreveMensural(void) { - setNoteheadBreveSquare(); -} + // There can be cases where the logical accidental + // is natural but the notated accidetnal is sharp (but + // the notated accidental means play a natural accidetnal). + // Deal with this later. -void MuseRecord::setNoteheadWhole(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '9'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '9'; - } else { // normal note - getColumn(17) = 'w'; + // if there is a notated natural sign, then add it now: + string temp = getNotatedAccidentalField(); + if (temp == "n") { + output += "n"; } -} -void MuseRecord::setNoteheadWholeMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '9'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '9'; - } else { // normal note - getColumn(17) = 'W'; + // check if a grace note + if (getType() == 'g') { + output += "Q"; } -} -void MuseRecord::setNoteheadHalf(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '8'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '8'; - } else { // normal note - getColumn(17) = 'h'; + // if stems is true, then show stem directions + if (stems && stemDirectionQ()) { + switch (getStemDirection()) { + case 1: // 'u' = up + output += "/"; + break; + case -1: // 'd' = down + output += "\\"; + default: + ; // nothing // ' ' = no stem (if stage 2) + } } -} -void MuseRecord::setNoteheadHalfMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '8'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '8'; - } else { // normal note - getColumn(17) = 'H'; + // if beams is true, then show any beams + if (beams && beamQ()) { + temp = getKernBeamStyle(); + output += temp; } -} -void MuseRecord::setNoteheadQuarter(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '7'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '7'; - } else { // normal note - getColumn(17) = 'q'; + if (isTied()) { + string tiestarts; + string tieends; + int lasttie = getLastTiedNoteLineIndex(); + int nexttie = getNextTiedNoteLineIndex(); + int state = 0; + if (lasttie >= 0) { + state |= 2; + } + if (nexttie >= 0) { + state |= 1; + } + switch (state) { + case 1: + tiestarts += "["; + break; + case 2: + tieends += "]"; + break; + case 3: + tieends += "_"; + break; + } + if (state) { + output = tiestarts + output + tieends; + } } -} -void MuseRecord::setNoteheadQuarterMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '7'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '7'; - } else { // normal note - getColumn(17) = 'Q'; + string slurstarts; + string slurends; + getSlurInfo(slurstarts, slurends); + if ((!slurstarts.empty()) || (!slurends.empty())) { + output = slurstarts + output + slurends; } -} -void MuseRecord::setNotehead8th(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '6'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '6'; - } else { // normal note - getColumn(17) = 'e'; - } + return output; } -void MuseRecord::setNotehead8thMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '6'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '6'; - } else { // normal note - getColumn(17) = 'E'; - } -} -void MuseRecord::setNotehead16th(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '5'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '5'; - } else { // normal note - getColumn(17) = 's'; - } -} +////////////////////////////// +// +// MuseRecord::getKernNoteAccents -- +// -void MuseRecord::setNotehead16thMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '5'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '5'; - } else { // normal note - getColumn(17) = 'S'; +string MuseRecord::getKernNoteAccents(void) { + string output; + int addnotecount = getAddCount(); + for (int i=0; i': output += "^"; break; // horizontal accent + case '.': output += "'"; break; // staccato + case '_': output += "~"; break; // tenuto + case '=': output += "~'"; break; // detached legato + case 'i': output += "s"; break; // spiccato + case '\'': output += ","; break; // breath mark + case 'F': output += ";"; break; // fermata up + case 'E': output += ";"; break; // fermata down + case 'S': output += ":"; break; // staccato + case 't': output += "O"; break; // trill (to generic) + case 'r': output += "S"; break; // turn + case 'k': output += "O"; break; // delayed turn (to generic) + case 'w': output += "O"; break; // shake (to generic) + case 'M': output += "O"; break; // mordent (to generic) + case 'j': output += "H"; break; // glissando (slide) + } } -} -void MuseRecord::setNotehead32nd(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '4'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '4'; - } else { // normal note - getColumn(17) = 't'; - } + return output; } -void MuseRecord::setNotehead32ndMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '4'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '4'; - } else { // normal note - getColumn(17) = 'T'; - } -} -void MuseRecord::setNotehead64th(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '3'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '3'; - } else { // normal note - getColumn(17) = 'x'; - } -} -void MuseRecord::setNotehead64thMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '3'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '3'; - } else { // normal note - getColumn(17) = 'X'; - } -} +////////////////////////////// +// +// MuseRecord::getKernRestStyle -- +// -void MuseRecord::setNotehead128th(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '2'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '2'; - } else { // normal note - getColumn(17) = 'y'; - } -} +string MuseRecord::getKernRestStyle(void) { -void MuseRecord::setNotehead128thMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '2'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '2'; - } else { // normal note - getColumn(17) = 'Y'; - } -} + string output; + string rhythmstring; -void MuseRecord::setNotehead256th(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '1'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '1'; - } else { // normal note - getColumn(17) = 'z'; + // place the rhythm + stringstream tempdur; + + if (!isAnyRest()) { + // not a rest, so return nothing + return ""; } -} -void MuseRecord::setNotehead256thMensural(void) { - if ((*this)[0] == 'g') { // grace note - getColumn(8) = '1'; - } else if ((*this)[0] == 'c') { // cue-sized note (with duration) - getColumn(17) = '1'; - } else { // normal note - getColumn(17) = 'Z'; + // logical duration of the note + HumNum logicalduration = getTicks(); + logicalduration /= getTpq(); + string durrecip = Convert::durationToRecip(logicalduration); + + /* + int notetype; + if (graphicNoteTypeQ()) { + notetype = getGraphicNoteType(); + + if (timeModificationLeftQ()) { + notetype = notetype / 4 * getTimeModificationLeft(); + } + if (timeModificationRightQ()) { + notetype = notetype * getTimeModificationRight() / 2; + } + tempdur << notetype; + output = tempdur.str(); + + // add any dots of prolongation to the output string + output += getStringProlongation(); + } else { // stage 1 data: + HumNum dnotetype(getTickDuration(), quarter); + rhythmstring = Convert::durationToRecip(dnotetype); + output += rhythmstring; } -} + */ + output = durrecip; -///////////////////////////// -// -// MuseRecord::setBack -- -// + // add the pitch to the output string + output += "r"; -void MuseRecord::setBack(int value) { - insertString(1, "back"); - setTicks(value); + if (isInvisibleRest()) { + output += "yy"; + } + + return output; } -///////////////////////////// +////////////////////////////// // -// MuseRecord::setTicks -- return the numeric value in columns 6-9. +// MuseRecord::getKernMeasure -- Return the **kern measure token +// for barline. // -void MuseRecord::setTicks(int value) { - if ((value < 0) || (value >= 1000)) { - cerr << "@ Error: ticks out of range in MuseRecord::setTicks" << endl; +string MuseRecord::getKernMeasure(void) { + if (!isBarline()) { + return ""; } - stringstream ss; - ss << value; - int len = (int)ss.str().size(); - insertString(5+3-len+1, ss.str()); + string measureStyle = getMeasureType(); + string measureFlag = getMeasureFlags(); + + string output = "="; + if ((measureStyle.find("mheavy") != string::npos) && measureFlag.empty()) { + output += "="; + } + + if ((output != "==") && measureNumberQ()) { + output += getMeasureNumber(); + } + + if (measureStyle == "mheavy1") { + output += "!"; + } else if (measureStyle == "mheavy2") { + if (measureFlagEqual(":||:")) { + output += ":|!|:"; + } else if (measureFlagEqual("|: :|")) { + // Vivaldi op. 1, no. 1, mvmt. 1, m. 10: mheavy4 |: :| + output += ":|!|:"; + } + } else if (measureStyle == "mheavy3") { + output += "!|"; + } else if (measureStyle == "mheavy4") { + if (measureFlagEqual(":||:")) { + output += ":!!:"; + } else if (measureFlagEqual(":||:")) { + output += ":|!|:"; + } else if (measureFlagEqual("|: :|")) { + output += ":|!|:"; + } else { + output += "!!"; + } + } + return output; } ////////////////////////////// // -// MuseRecord::getTie -- +// MuseRecord::getKernNoteOtherNotations -- Extract note-level ornaments +// and articulations. See MuseRecord::getOtherNotation() for list +// of "other notations". // -string MuseRecord::getTieString(void) { +string MuseRecord::getKernNoteOtherNotations(void) { string output; - output += getColumn(9); - if (output == " ") { - output = ""; + string notations = getOtherNotations(); + for (int i=0; i<(int)notations.size(); i++) { + switch(notations[i]) { + case 'F': // fermata above + output += ";"; + break; + case 'E': // fermata below + output += ";<"; + break; + case '.': // staccato + output += "'"; + break; + case ',': // breath mark + output += ","; + break; + case '=': // tenuto-staccato + output += "~'"; + break; + case '>': // accent + output += "^"; + break; + case 'A': // heavy accent + output += "^^"; + break; + case 'M': // mordent + output += "M"; + break; + case 'r': // turn + output += "S"; + break; + case 't': // trill + output += "T"; + break; + case 'n': // down bow + output += "u"; + break; + case 'v': // up bow + output += "v"; + break; + case 'Z': // sfz + output += "zz"; + break; + } } return output; } -int MuseRecord::getTie(void) { - return tieQ(); -} ////////////////////////////// // -// MuseRecord::getTie -- Set a tie marker in column 9. Currently -// the function does not check the type of data, so will overr-write any -// data found in column 9 (such as if the record is not for a note). -// -// If the input parameter hidden is true, then the visual tie is not -// displayed, but the sounding tie is displayed. +// MuseRecord::getMeasureNumberField -- Columns 9-12 contain the measure number. // -int MuseRecord::setTie(int hidden) { - getColumn(9) = '-'; - if (!hidden) { - return addAdditionalNotation('-'); - } else { - return -1; +string MuseRecord::getMeasureNumberField(void) { + if (!isBarline()) { + return ""; } + return extract(9, 12); } ////////////////////////////// // -// MuseRecord::addAdditionalNotation -- ties, slurs and tuplets. -// Currently not handling editorial levels. +// MuseRecord::getMeasureNumber -- Remove spaces from field. // -int MuseRecord::addAdditionalNotation(char symbol) { - // search columns 32 to 43 for the specific symbol. - // if it is found, then don't add. If it is not found, - // then do add. - int i; - int blank = -1; - int nonempty = 0; // true if a non-space character was found. +string MuseRecord::getMeasureNumber(void) { + return trimSpaces(getMeasureNumberField()); +} - for (i=43; i>=32; i--) { - if (getColumn(i) == symbol) { - return i; - } else if (!nonempty && (getColumn(i) == ' ')) { - blank = i; - } else { - nonempty = i; - } - } - if (symbol == '-') { - // give preferential treatment to placing only ties in - // column 32 - if (getColumn(32) == ' ') { - getColumn(32) = '-'; - return 32; - } - } - if (blank < 0) { - cerr << "Error in MuseRecord::addAdditionalNotation: " - << "no empty space for notation" << endl; - return 0; - } +////////////////////////////// +// +// MuseRecord::getMeasureType -- Columns 1-7. +// - if ((blank <= 32) && (getColumn(33) == ' ')) { - // avoid putting non-tie items in column 32. - blank = 33; +string MuseRecord::getMeasureType(void) { + if (!isBarline()) { + return ""; } - - getColumn(blank) = symbol; - return blank; + return extract(1, 7); } -// add a multi-character additional notation (such as a dynamic like mf): -int MuseRecord::addAdditionalNotation(const string& symbol) { - int len = (int)symbol.size(); - // search columns 32 to 43 for the specific symbol. - // if it is found, then don't add. If it is not found, - // then do add. - int i, j; - int blank = -1; - int found = 0; - int nonempty = 0; // true if a non-space character was found. +////////////////////////////// +// +// MuseRecord::measureNumberQ -- Returns true if barline +// has a measure number. +// - for (i=43-len; i>=32; i--) { - found = 1; - for (j=0; j= (int)recordInfo.size()) { + cerr << "Error: no octave specification in note field: " << recordInfo + << endl; + return 0; + } + return recordInfo[index] - '0'; } -int MuseRecord::getLevel(void) { - int output = 1; - string recordInfo = getLevelField(); - if (recordInfo[0] == ' ') { - output = 1; - } else { - output = (int)strtol(recordInfo.c_str(), NULL, 36); +string MuseRecord::getOctaveString(void) { + string recordInfo = getNoteField(); + int index = 0; + while ((index < (int)recordInfo.size()) && !std::isdigit(recordInfo[index])) { + index++; } + if (index >= (int)recordInfo.size()) { + cerr << "Error: no octave specification in note field: " << recordInfo + << endl; + return ""; + } + string output; + output += recordInfo[index]; return output; } @@ -40483,16 +40646,22 @@ int MuseRecord::getLevel(void) { ////////////////////////////// // -// MuseRecord::levelQ -- +// MuseRecord::getPitch -- int version returns the base40 representation // -int MuseRecord::levelQ(void) { - int output = 0; - string recordInfo = getLevelField(); - if (recordInfo[0] == ' ') { - output = 0; - } else { - output = 1; +int MuseRecord::getPitch(void) { + string recordInfo = getNoteField(); + return Convert::museToBase40(recordInfo); +} + + +string MuseRecord::getPitchString(void) { + string output = getNoteField(); + int len = (int)output.size(); + int index = len-1; + while (index >= 0 && output[index] == ' ') { + output.resize(index); + index--; } return output; } @@ -40501,28 +40670,61 @@ int MuseRecord::levelQ(void) { ////////////////////////////// // -// MuseRecord::getTrackField -- return column 15 +// MuseRecord::getPitchClass -- returns the pitch without the octave information // -string MuseRecord::getTrackField(void) { - if (!isAnyNoteOrRest()) { - return extract(15, 15); - } else { - return " "; +int MuseRecord::getPitchClass(void) { + return getPitch() % 40; +} + + +string MuseRecord::getPitchClassString(void) { + string output = getNoteField(); + int index = 0; + while ((index < (int)output.size()) && !std::isdigit(output[index])) { + index++; } + output.resize(index); + return output; } ////////////////////////////// // -// MuseRecord::getTrackString -- +// MuseRecord::getAccidental -- int version return -2 for double flat, +// -1 for flat, 0 for natural, +1 for sharp, +2 for double sharp // -string MuseRecord::getTrackString(void) { - string output = getTrackField(); - if (output[0] == ' ') { - output = ""; +int MuseRecord::getAccidental(void) { + string recordInfo = getNoteField(); + int output = 0; + int index = 0; + while ((index < (int)recordInfo.size()) && (index < 16)) { + if (recordInfo[index] == 'f') { + output--; + } else if (recordInfo[index] == '#') { + output++; + } + index++; + } + return output; +} + + +string MuseRecord::getAccidentalString(void) { + string output; + int type = getAccidental(); + switch (type) { + case -2: output = "ff"; break; + case -1: output = "f"; break; + case 0: output = ""; break; + case 1: output = "#"; break; + case 2: output = "##"; break; + default: + output = getNoteField(); + cerr << "Error: unknown type of accidental: " << output << endl; + return ""; } return output; } @@ -40531,624 +40733,881 @@ string MuseRecord::getTrackString(void) { ////////////////////////////// // -// MuseRecord::getTrack -- Return 0 if no track information (implicitly track 1, -// or unlabelled higher track). +// MuseRecord::getBase40 -- return the base40 pitch value of the data +// line. Middle C set to 40 * 4 + 2; Returns -100 for non-pitched items. +// (might have to update for note_cur_chord and note_grace_chord which +// do not exist yet. // -int MuseRecord::getTrack(void) { - int output = 1; - string recordInfo = getTrackField(); - if (recordInfo[0] == ' ') { - output = 0; - } else { - output = (int)strtol(recordInfo.c_str(), NULL, 36); +int MuseRecord::getBase40(void) { + switch (getType()) { + case E_muserec_note_regular: + case E_muserec_note_chord: + case E_muserec_note_cue: + case E_muserec_note_grace: + break; + default: + return -100; } - return output; + return getPitch(); } ////////////////////////////// // -// MuseRecord::trackQ -- +// MuseRecord::setStemDown -- // -int MuseRecord::trackQ(void) { - int output = 0; - string recordInfo = getTrackField(); - if (recordInfo[0] == ' ') { - output = 0; - } else { - output = 1; - } - - return output; +void MuseRecord::setStemDown(void) { + getColumn(23) = 'd'; } ////////////////////////////// // -// MuseRecord::getGraphicNoteTypeField -- return column 17 +// MuseRecord::setStemUp -- // -string MuseRecord::getGraphicNoteTypeField(void) { -// allowNotesOnly("getGraphicNoteTypefield"); - if (getLength() < 17) { - return " "; - } else { - return extract(17, 17); - } +void MuseRecord::setStemUp(void) { + getColumn(23) = 'u'; } ////////////////////////////// // -// MuseRecord::getGraphicNoteType -- +// MuseRecord::setPitch -- input is a base40 value which gets converted +// to a diatonic pitch name. +// Default value: chordnote = 0 +// Default value: gracenote = 0 // -string MuseRecord::getGraphicNoteTypeString(void) { - string output = getGraphicNoteTypeField(); - if (output[0] == ' ') { - output = ""; +void MuseRecord::setPitch(int base40, int chordnote, int gracenote) { + string diatonic; + switch (Convert::base40ToDiatonic(base40) % 7) { + case 0: diatonic = 'C'; break; + case 1: diatonic = 'D'; break; + case 2: diatonic = 'E'; break; + case 3: diatonic = 'F'; break; + case 4: diatonic = 'G'; break; + case 5: diatonic = 'A'; break; + case 6: diatonic = 'B'; break; + default: diatonic = 'X'; + } + + string octave; + octave += char('0' + base40 / 40); + + string accidental; + int acc = Convert::base40ToAccidental(base40); + switch (acc) { + case -2: accidental = "ff"; break; + case -1: accidental = "f"; break; + case +1: accidental = "#"; break; + case +2: accidental = "##"; break; + } + string pitchname = diatonic + accidental + octave; + + if (chordnote) { + if (gracenote) { + setGraceChordPitch(pitchname); + } else { + setChordPitch(pitchname); + } + } else { + setPitch(pitchname); + } +} + + +void MuseRecord::setChordPitch(const string& pitchname) { + getColumn(1) = ' '; + setPitchAtIndex(1, pitchname); +} + +void MuseRecord::setGracePitch(const string& pitchname) { + getColumn(1) = 'g'; + setPitchAtIndex(1, pitchname); +} + +void MuseRecord::setGraceChordPitch(const string& pitchname) { + getColumn(1) = 'g'; + getColumn(2) = ' '; + setPitchAtIndex(2, pitchname); +} + +void MuseRecord::setCuePitch(const string& pitchname) { + getColumn(1) = 'c'; + setPitchAtIndex(1, pitchname); +} + + +void MuseRecord::setPitch(const string& pitchname) { + int start = 0; + // If the record is already set to a grace note or a cue note, + // then place pitch information starting at column 2 (index 1). + if ((getColumn(1) == 'g') || (getColumn(1) == 'c')) { + start = 1; + } + setPitchAtIndex(start, pitchname); +} + + +void MuseRecord::setPitchAtIndex(int index, const string& pitchname) { + int len = (int)pitchname.size(); + if ((len > 4) && (pitchname != "irest")) { + cerr << "Error in MuseRecord::setPitchAtIndex: " << pitchname << endl; + return; + } + insertString(index+1, pitchname); + + // Clear any text fields not used by current pitch data. + for (int i=4-len-1; i>=0; i--) { + (*this)[index + len + i] = ' '; } - return output; } ////////////////////////////// // -// MuseRecord::getGraphicRecip -- +// MuseRecord::getTickDurationField -- returns the string containing the +// duration, and tie information. // -string MuseRecord::getGraphicRecip(void) { - int notetype = getGraphicNoteType(); - string output; - switch (notetype) { - case -3: output = "0000"; break; // double-maxima - case -2: output = "000"; break; // maxima - case -1: output = "00"; break; // long +string MuseRecord::getTickDurationField(void) { + switch (getType()) { + case E_muserec_figured_harmony: + case E_muserec_note_regular: + case E_muserec_note_chord: + case E_muserec_rest: + case E_muserec_backward: + case E_muserec_forward: + return extract(6, 9); + break; + // these record types do not have duration, per se: + case E_muserec_note_cue: + case E_muserec_note_grace: default: - output = to_string(notetype); // regular **recip number - } - int dotcount = getDotCount(); - for (int i=0; i= 32) { - return -2; - } else if (value >= 16) { - return -1; - } else if (value >= 8) { - return 0; - } else if (value >= 4) { - return 1; - } else if (value >= 2) { - return 2; - } else if (value >= 1) { - return 4; - } else if (value.getFloat() >= 0.5) { - return 8; - } else if (value.getFloat() >= 0.25) { - return 16; - } else if (value.getFloat() >= 0.125) { - return 32; - } else if (value.getFloat() >= 0.0625) { - return 64; - } else if (value.getFloat() >= 1.0/128) { - return 128; - } else if (value.getFloat() >= 1.0/256) { - return 256; - } else if (value.getFloat() >= 1.0/512) { - return 512; - } else { - return 0; - } - } else { - cerr << "Error: no graphic note type specified: " << getLine() << endl; - return 0; - } +string MuseRecord::getTickDurationString(void) { + string output = getTickDurationField(); + int length = (int)output.size(); + int i = length - 1; + while (i>0 && (output[i] == '-' || output[i] == ' ')) { + output.resize(i); + i--; + length--; + } + + int start = 0; + while (output[start] == ' ') { + start++; } - switch (recordInfo[0]) { - case 'M': // Maxima - output = -2; break; - case 'L': case 'B': // Longa - output = -1; break; - case 'b': case 'A': // Breve - output = 0; break; - case 'w': case '9': // Whole - output = 1; break; - case 'h': case '8': // Half - output = 2; break; - case 'q': case '7': // Quarter - output = 4; break; - case 'e': case '6': // Eighth - output = 8; break; - case 's': case '5': // Sixteenth - output = 16; break; - case 't': case '4': // 32nd note - output = 32; break; - case 'x': case '3': // 64th note - output = 64; break; - case 'y': case '2': // 128th note - output = 128; break; - case 'z': case '1': // 256th note - output = 256; break; - default: - cerr << "Error: unknown graphical note type in column 17: " - << getLine() << endl; + if (start != 0) { + for (i=0; i rests also - if (getLength() < 18) { - return " "; - } else { - return extract(18, 18); +int MuseRecord::getTicks(void) { + string recordInfo = getTickDurationString(); + if (recordInfo.empty()) { + return 0; + } + int value = std::stoi(recordInfo); + if (getType() == E_muserec_backspace) { + return -value; } -} + return value; +} ////////////////////////////// // -// MuseRecord::getProlongationString -- +// MuseRecord::getNoteTickDuration -- Similar to getLineTickDuration, +// but do not suppress the duration of secondary chord-tones. // -string MuseRecord::getProlongationString(void) { - string output = getProlongationField(); - if (output[0] == ' ') { - output = ""; +int MuseRecord::getNoteTickDuration(void) { + string recordInfo = getTickDurationString(); + int value = 0; + if (recordInfo.empty()) { + return value; } - return output; + value = std::stoi(recordInfo); + if (getType() == E_muserec_backspace) { + return -value; + } + return value; } ////////////////////////////// // -// MuseRecord::getProlongation -- +// MuseRecord::setDots -- // -int MuseRecord::getProlongation(void) { - int output = 0; - string recordInfo = getProlongationField(); - switch (recordInfo[0]) { - case ' ': output = 0; break; - case '.': output = 1; break; - case ':': output = 2; break; - default: - cerr << "Error: unknon prologation character (column 18): " - << getLine() << endl; - return 0; +void MuseRecord::setDots(int value) { + switch (value) { + case 0: getColumn(18) = ' '; break; + case 1: getColumn(18) = '.'; break; + case 2: getColumn(18) = ':'; break; + case 3: getColumn(18) = ';'; break; + case 4: getColumn(18) = '!'; break; + default: cerr << "Error in MuseRecord::setDots : " << value << endl; } - return output; } ////////////////////////////// // -// MuseRecord::getStringProlongation -- +// MuseRecord::getDotCount -- // -string MuseRecord::getStringProlongation(void) { - switch (getProlongation()) { - case 0: return ""; break; - case 1: return "."; break; - case 2: return ".."; break; - case 3: return "..."; break; - case 4: return "...."; break; - default: - cerr << "Error: unknown number of prolongation dots (column 18): " - << getLine() << endl; - return ""; +int MuseRecord::getDotCount(void) { + char value = getColumn(18); + switch (value) { + case ' ': return 0; + case '.': return 1; + case ':': return 2; + case ';': return 3; + case '!': return 4; } - return ""; + return 0; } ////////////////////////////// // -// MuseRecord::prolongationQ -- +// MuseRecord::setNoteheadShape -- Duration with augmentation dot component +// removed. Duration of 1 is quarter note. // -int MuseRecord::prolongationQ(void) { - return getProlongation(); +void MuseRecord::setNoteheadShape(HumNum duration) { + HumNum note8th(1,2); + HumNum note16th(1,4); + HumNum note32nd(1,8); + HumNum note64th(1,16); + HumNum note128th(1,32); + HumNum note256th(1,64); + + if (duration > 16) { // maxima + setNoteheadMaxima(); + } else if (duration > 8) { // long + setNoteheadLong(); + } else if (duration > 4) { // breve + if (m_roundBreve) { + setNoteheadBreveRound(); + } else { + setNoteheadBreve(); + } + } else if (duration > 2) { // whole note + setNoteheadWhole(); + } else if (duration > 1) { // half note + setNoteheadHalf(); + } else if (duration > note8th) { // quarter note + setNoteheadQuarter(); + } else if (duration > note16th) { // eighth note + setNotehead8th(); + } else if (duration > note32nd) { // 16th note + setNotehead16th(); + } else if (duration > note64th) { // 32nd note + setNotehead32nd(); + } else if (duration > note128th) { // 64th note + setNotehead64th(); + } else if (duration > note256th) { // 128th note + setNotehead128th(); + } else if (duration == note256th) { // 256th note + // not allowing tuplets on the 256th note level. + setNotehead256th(); + } else { + cerr << "Error in duration: " << duration << endl; + return; + } } + ////////////////////////////// // -// MuseRecord::getNotatedAccidentalField -- actual notated accidental is -// stored in column 19. +// MuseRecord::setNoteheadShape -- Duration with augmentation dot component +// removed. Duration of 1 is quarter note. // -string MuseRecord::getNotatedAccidentalField(void) { - allowNotesOnly("getNotatedAccidentalField"); - if (getLength() < 19) { - return " "; +void MuseRecord::setNoteheadShapeMensural(HumNum duration) { + HumNum note8th(1, 2); + HumNum note16th(1, 4); + HumNum note32th(1, 8); + HumNum note64th(1, 16); + HumNum note128th(1, 32); + HumNum note256th(1, 64); + + if (duration > 16) { // maxima + setNoteheadMaxima(); + } else if (duration > 8) { // long + setNoteheadLong(); + } else if (duration > 4) { // breve + setNoteheadBreve(); + } else if (duration > 2) { // whole note + setNoteheadWholeMensural(); + } else if (duration > 1) { // half note + setNoteheadHalfMensural(); + } else if (duration > note8th) { // quarter note + setNoteheadQuarterMensural(); + } else if (duration > note16th) { // eighth note + setNotehead8thMensural(); + } else if (duration > note32th) { // 16th note + setNotehead16thMensural(); + } else if (duration > note64th) { // 32nd note + setNotehead32ndMensural(); + } else if (duration > note128th) { // 64th note + setNotehead64thMensural(); + } else if (duration > note256th) { // 128th note + setNotehead128thMensural(); + } else if (duration >= note256th) { // 256th note + // don't allow tuplets on 256th note level. + setNotehead256thMensural(); } else { - string temp; - temp += getColumn(19); - return temp; + cerr << "Error in duration: " << duration << endl; + return; } } +void MuseRecord::setNoteheadMaxima(void) { + if ((*this)[0] == 'c' || ((*this)[0] == 'g')) { + cerr << "Error: cue/grace notes cannot be maximas in setNoteheadLong" + << endl; + return; + } else { + getColumn(17) = 'M'; + } +} +void MuseRecord::setNoteheadLong(void) { + if ((*this)[0] == 'c' || ((*this)[0] == 'g')) { + cerr << "Error: cue/grace notes cannot be longs in setNoteheadLong" + << endl; + return; + } else { + getColumn(17) = 'L'; + } +} -////////////////////////////// -// -// MuseRecord::getNotatedAccidentalString -- -// +void MuseRecord::setNoteheadBreve(void) { + setNoteheadBreveSquare(); +} -string MuseRecord::getNotatedAccidentalString(void) { - string output = getNotatedAccidentalField(); - if (output[0] == ' ') { - output = ""; +void MuseRecord::setNoteheadBreveSquare(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = 'A'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = 'A'; + } else { // normal note + getColumn(17) = 'B'; } - return output; } +void MuseRecord::setNoteheadBreveRound(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = 'A'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = 'A'; + } else { // normal note + getColumn(17) = 'b'; + } +} +void MuseRecord::setNoteheadBreveMensural(void) { + setNoteheadBreveSquare(); +} -////////////////////////////// -// -// MuseRecord::getNotatedAccidental -- -// +void MuseRecord::setNoteheadWhole(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '9'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '9'; + } else { // normal note + getColumn(17) = 'w'; + } +} -int MuseRecord::getNotatedAccidental(void) { - int output = 0; - string recordInfo = getNotatedAccidentalField(); - switch (recordInfo[0]) { - case ' ': output = 0; break; - case '#': output = 1; break; - case 'n': output = 0; break; - case 'f': output = -1; break; - case 'x': output = 2; break; - case 'X': output = 2; break; - case '&': output = -2; break; - case 'S': output = 1; break; - case 'F': output = -1; break; - default: - cerr << "Error: unknown accidental: " << recordInfo[0] << endl; - return 0; +void MuseRecord::setNoteheadWholeMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '9'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '9'; + } else { // normal note + getColumn(17) = 'W'; } - return output; } +void MuseRecord::setNoteheadHalf(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '8'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '8'; + } else { // normal note + getColumn(17) = 'h'; + } +} +void MuseRecord::setNoteheadHalfMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '8'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '8'; + } else { // normal note + getColumn(17) = 'H'; + } +} -////////////////////////////// -// -// MuseRecord::notatedAccidentalQ -- -// +void MuseRecord::setNoteheadQuarter(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '7'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '7'; + } else { // normal note + getColumn(17) = 'q'; + } +} -int MuseRecord::notatedAccidentalQ(void) { - int output; - string recordInfo = getNotatedAccidentalField(); - if (recordInfo[0] == ' ') { - output = 0; - } else { - output = 1; +void MuseRecord::setNoteheadQuarterMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '7'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '7'; + } else { // normal note + getColumn(17) = 'Q'; } - return output; } +void MuseRecord::setNotehead8th(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '6'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '6'; + } else { // normal note + getColumn(17) = 'e'; + } +} +void MuseRecord::setNotehead8thMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '6'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '6'; + } else { // normal note + getColumn(17) = 'E'; + } +} -/////////////////////////////// -// -// MuseRecord::getTimeModificationField -- return columns 20 -- 22. -// +void MuseRecord::setNotehead16th(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '5'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '5'; + } else { // normal note + getColumn(17) = 's'; + } +} -string MuseRecord::getTimeModificationField(void) { -// allowNotesOnly("getTimeModificationField"); ---> rests also - if (getLength() < 20) { - return " "; - } else { - return extract(20, 22); +void MuseRecord::setNotehead16thMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '5'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '5'; + } else { // normal note + getColumn(17) = 'S'; } } +void MuseRecord::setNotehead32nd(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '4'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '4'; + } else { // normal note + getColumn(17) = 't'; + } +} +void MuseRecord::setNotehead32ndMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '4'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '4'; + } else { // normal note + getColumn(17) = 'T'; + } +} -////////////////////////////// -// -// MuseRecord::getTimeModification -- -// +void MuseRecord::setNotehead64th(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '3'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '3'; + } else { // normal note + getColumn(17) = 'x'; + } +} -string MuseRecord::getTimeModification(void) { - string output = getTimeModificationField(); - int index = 2; - while (index >= 0 && output[index] == ' ') { - output.resize(index); - index--; +void MuseRecord::setNotehead64thMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '3'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '3'; + } else { // normal note + getColumn(17) = 'X'; } - if (output.size() > 2) { - if (output[0] == ' ') { - output[0] = output[1]; - output[1] = output[2]; - output.resize(2); - } +} + +void MuseRecord::setNotehead128th(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '2'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '2'; + } else { // normal note + getColumn(17) = 'y'; } - if (output.size() > 1) { - if (output[0] == ' ') { - output[0] = output[1]; - output.resize(1); - } +} + +void MuseRecord::setNotehead128thMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '2'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '2'; + } else { // normal note + getColumn(17) = 'Y'; } - if (output[0] == ' ') { - cerr << "Error: funny error occured in time modification " - << "(columns 20-22): " << getLine() << endl; - return ""; +} + +void MuseRecord::setNotehead256th(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '1'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '1'; + } else { // normal note + getColumn(17) = 'z'; } - return output; } +void MuseRecord::setNotehead256thMensural(void) { + if ((*this)[0] == 'g') { // grace note + getColumn(8) = '1'; + } else if ((*this)[0] == 'c') { // cue-sized note (with duration) + getColumn(17) = '1'; + } else { // normal note + getColumn(17) = 'Z'; + } +} -////////////////////////////// +///////////////////////////// // -// MuseRecord::getTimeModificationLeftField -- return column 20 +// MuseRecord::setBack -- // -string MuseRecord::getTimeModificationLeftField(void) { - string output = getTimeModificationField(); - output.resize(1); - return output; +void MuseRecord::setBack(int value) { + insertString(1, "back"); + setTicks(value); } -////////////////////////////// +///////////////////////////// // -// MuseRecord::getTimeModificationLeftString -- +// MuseRecord::setTicks -- return the numeric value in columns 6-9. // -string MuseRecord::getTimeModificationLeftString(void) { - string output = getTimeModificationField(); - if (output[0] == ' ') { - output = ""; - } else { - output.resize(1); +void MuseRecord::setTicks(int value) { + if ((value < 0) || (value >= 1000)) { + cerr << "@ Error: ticks out of range in MuseRecord::setTicks" << endl; } - return output; + stringstream ss; + ss << value; + int len = (int)ss.str().size(); + insertString(5+3-len+1, ss.str()); } ////////////////////////////// // -// MuseRecord::getTimeModificationLeft -- +// MuseRecord::getTie -- // -int MuseRecord::getTimeModificationLeft(void) { - int output = 0; - string recordInfo = getTimeModificationLeftString(); - if (recordInfo[0] == ' ') { - output = 0; - } else { - output = (int)strtol(recordInfo.c_str(), NULL, 36); +string MuseRecord::getTieString(void) { + string output; + output += getColumn(9); + if (output == " ") { + output = ""; } return output; } - -////////////////////////////// -// -// MuseRecord::getTimeModificationRightField -- return column 20 -// - -string MuseRecord::getTimeModificationRightField(void) { - string output = getTimeModificationField(); - output = output[2]; - return output; +int MuseRecord::getTie(void) { + return tieQ(); } - ////////////////////////////// // -// MuseRecord::getTimeModificationRight -- +// MuseRecord::getTie -- Set a tie marker in column 9. Currently +// the function does not check the type of data, so will overr-write any +// data found in column 9 (such as if the record is not for a note). +// +// If the input parameter hidden is true, then the visual tie is not +// displayed, but the sounding tie is displayed. // -string MuseRecord::getTimeModificationRightString(void) { - string output = getTimeModificationField(); - if (output[2] == ' ') { - output = ""; +int MuseRecord::setTie(int hidden) { + getColumn(9) = '-'; + if (!hidden) { + return addAdditionalNotation('-'); } else { - output = output[2]; + return -1; } - return output; } ////////////////////////////// // -// MuseRecord::getTimeModificationRight -- +// MuseRecord::addAdditionalNotation -- ties, slurs and tuplets. +// Currently not handling editorial levels. // -int MuseRecord::getTimeModificationRight(void) { - int output = 0; - string recordInfo = getTimeModificationRightString(); - if (recordInfo[2] == ' ') { - output = 0; - } else { - string temp = recordInfo.substr(2); - output = (int)strtol(temp.c_str(), NULL, 36); - } - return output; -} +int MuseRecord::addAdditionalNotation(char symbol) { + // search columns 32 to 43 for the specific symbol. + // if it is found, then don't add. If it is not found, + // then do add. + int i; + int blank = -1; + int nonempty = 0; // true if a non-space character was found. + for (i=43; i>=32; i--) { + if (getColumn(i) == symbol) { + return i; + } else if (!nonempty && (getColumn(i) == ' ')) { + blank = i; + } else { + nonempty = i; + } + } + if (symbol == '-') { + // give preferential treatment to placing only ties in + // column 32 + if (getColumn(32) == ' ') { + getColumn(32) = '-'; + return 32; + } + } -////////////////////////////// -// -// MuseRecord::timeModificationQ -- -// + if (blank < 0) { + cerr << "Error in MuseRecord::addAdditionalNotation: " + << "no empty space for notation" << endl; + return 0; + } -int MuseRecord::timeModificationQ(void) { - int output = 0; - string recordInfo = getTimeModificationField(); - if (recordInfo[0] != ' ' || recordInfo[1] != ' ' || recordInfo[2] != ' ') { - output = 1; - } else { - output = 0; + if ((blank <= 32) && (getColumn(33) == ' ')) { + // avoid putting non-tie items in column 32. + blank = 33; } - return output; + + getColumn(blank) = symbol; + return blank; } +// add a multi-character additional notation (such as a dynamic like mf): -////////////////////////////// -// -// MuseRecord::timeModificationLeftQ -- -// +int MuseRecord::addAdditionalNotation(const string& symbol) { + int len = (int)symbol.size(); + // search columns 32 to 43 for the specific symbol. + // if it is found, then don't add. If it is not found, + // then do add. + int i, j; + int blank = -1; + int found = 0; + int nonempty = 0; // true if a non-space character was found. -int MuseRecord::timeModificationLeftQ(void) { - int output = 0; - string recordInfo = getTimeModificationField(); - if (recordInfo[0] == ' ') { - output = 0; - } else { - output = 1; + for (i=43-len; i>=32; i--) { + found = 1; + for (j=0; j= 32) { + return -2; + } else if (value >= 16) { + return -1; + } else if (value >= 8) { + return 0; + } else if (value >= 4) { + return 1; + } else if (value >= 2) { + return 2; + } else if (value >= 1) { + return 4; + } else if (value.getFloat() >= 0.5) { + return 8; + } else if (value.getFloat() >= 0.25) { + return 16; + } else if (value.getFloat() >= 0.125) { + return 32; + } else if (value.getFloat() >= 0.0625) { + return 64; + } else if (value.getFloat() >= 1.0/128) { + return 128; + } else if (value.getFloat() >= 1.0/256) { + return 256; + } else if (value.getFloat() >= 1.0/512) { + return 512; + } else { + return 0; + } + } else { + cerr << "Error: no graphic note type specified: " << getLine() << endl; + return 0; + } + } -////////////////////////////// -// -// MuseRecord::getBeam256 -- column 31 -// + switch (recordInfo[0]) { + case 'M': // Maxima + output = -2; break; + case 'L': case 'B': // Longa + output = -1; break; + case 'b': case 'A': // Breve + output = 0; break; + case 'w': case '9': // Whole + output = 1; break; + case 'h': case '8': // Half + output = 2; break; + case 'q': case '7': // Quarter + output = 4; break; + case 'e': case '6': // Eighth + output = 8; break; + case 's': case '5': // Sixteenth + output = 16; break; + case 't': case '4': // 32nd note + output = 32; break; + case 'x': case '3': // 64th note + output = 64; break; + case 'y': case '2': // 128th note + output = 128; break; + case 'z': case '1': // 256th note + output = 256; break; + default: + cerr << "Error: unknown graphical note type in column 17: " + << getLine() << endl; + } -char MuseRecord::getBeam256(void) { - allowNotesOnly("getBeam256"); - return getColumn(31); + return output; } - ////////////////////////////// // -// MuseRecord::beam8Q -- +// MuseRecord::graphicNoteTypeQ -- // -int MuseRecord::beam8Q(void) { +int MuseRecord::graphicNoteTypeQ(void) { int output = 0; - if (getBeam8() == ' ') { + string recordInfo = getGraphicNoteTypeField(); + if (recordInfo[0] == ' ') { output = 0; } else { output = 1; @@ -41406,16 +41935,34 @@ int MuseRecord::beam8Q(void) { ////////////////////////////// // -// MuseRecord::beam16Q -- +// MuseRecord::graphicNoteTypeSize -- return 0 if cue note size, +// otherwise, it will return 1 if regular size // -int MuseRecord::beam16Q(void) { - int output = 0; - if (getBeam16() == ' ') { - output = 0; - } else { - output = 1; +int MuseRecord::getGraphicNoteTypeSize(void) { + int output = 1; + string recordInfo = getGraphicNoteTypeField(); + if (recordInfo[0] == ' ') { + cerr << "Error: not graphic note specified in column 17: " + << getLine() << endl; + return 0; + } + + switch (recordInfo[0]) { + case 'L': case 'b': case 'w': case 'h': case 'q': case 'e': + case 's': case 't': case 'x': case 'y': case 'z': + output = 1; + break; + case 'B': case 'A': case '9': case '8': case '7': case '6': + case '5': case '4': case '3': case '2': case '1': + output = 0; + break; + default: + cerr << "Error: unknown graphical note type in column 17: " + << getLine() << endl; + return 0; } + return output; } @@ -41423,32 +41970,29 @@ int MuseRecord::beam16Q(void) { ////////////////////////////// // -// MuseRecord::beam32Q -- +// MuseRecord::getProlongationField -- returns column 18 // -int MuseRecord::beam32Q(void) { - int output = 0; - if (getBeam32() == ' ') { - output = 0; +string MuseRecord::getProlongationField(void) { +// allowNotesOnly("getProlongationField"); ---> rests also + if (getLength() < 18) { + return " "; } else { - output = 1; + return extract(18, 18); } - return output; } ////////////////////////////// // -// MuseRecord::beam64Q -- +// MuseRecord::getProlongationString -- // -int MuseRecord::beam64Q(void) { - int output = 0; - if (getBeam64() == ' ') { - output = 0; - } else { - output = 1; +string MuseRecord::getProlongationString(void) { + string output = getProlongationField(); + if (output[0] == ' ') { + output = ""; } return output; } @@ -41457,15 +42001,20 @@ int MuseRecord::beam64Q(void) { ////////////////////////////// // -// MuseRecord::beam128Q -- +// MuseRecord::getProlongation -- // -int MuseRecord::beam128Q(void) { +int MuseRecord::getProlongation(void) { int output = 0; - if (getBeam128() == ' ') { - output = 0; - } else { - output = 1; + string recordInfo = getProlongationField(); + switch (recordInfo[0]) { + case ' ': output = 0; break; + case '.': output = 1; break; + case ':': output = 2; break; + default: + cerr << "Error: unknon prologation character (column 18): " + << getLine() << endl; + return 0; } return output; } @@ -41474,84 +42023,64 @@ int MuseRecord::beam128Q(void) { ////////////////////////////// // -// MuseRecord::beam256Q -- +// MuseRecord::getStringProlongation -- // -int MuseRecord::beam256Q(void) { - int output = 0; - if (getBeam256() == ' ') { - output = 0; - } else { - output = 1; +string MuseRecord::getStringProlongation(void) { + switch (getProlongation()) { + case 0: return ""; break; + case 1: return "."; break; + case 2: return ".."; break; + case 3: return "..."; break; + case 4: return "...."; break; + default: + cerr << "Error: unknown number of prolongation dots (column 18): " + << getLine() << endl; + return ""; } - return output; + return ""; } ////////////////////////////// // -// MuseRecord::getKernBeamStyle -- +// MuseRecord::prolongationQ -- // -string MuseRecord::getKernBeamStyle(void) { - string output; - string beams = getBeamField(); - for (int i=0; i<(int)beams.size(); i++) { - switch (beams[i]) { - case '[': // start beam - output += "L"; - break; - case '=': // continue beam - // do nothing - break; - case ']': // end beam - output += "J"; - break; - case '/': // forward hook - output += "K"; - break; - case '\\': // backward hook - output += "k"; - break; - default: - ; // do nothing - } - } - return output; +int MuseRecord::prolongationQ(void) { + return getProlongation(); } - ////////////////////////////// // -// MuseRecord::getAdditionalNotationsField -- returns the contents -// of columns 32-43. +// MuseRecord::getNotatedAccidentalField -- actual notated accidental is +// stored in column 19. // -string MuseRecord::getAdditionalNotationsField(void) { - allowNotesOnly("getAdditionalNotationsField"); - return extract(32, 43); +string MuseRecord::getNotatedAccidentalField(void) { + allowNotesOnly("getNotatedAccidentalField"); + if (getLength() < 19) { + return " "; + } else { + string temp; + temp += getColumn(19); + return temp; + } } ////////////////////////////// // -// MuseRecord::additionalNotationsQ -- +// MuseRecord::getNotatedAccidentalString -- // -int MuseRecord::additionalNotationsQ(void) { - int output = 0; - if (getLength() < 32) { - output = 0; - } else { - for (int i=32; i<=43; i++) { - if (getColumn(i) != ' ') { - output = 1; - break; - } - } +string MuseRecord::getNotatedAccidentalString(void) { + string output = getNotatedAccidentalField(); + if (output[0] == ' ') { + output = ""; } return output; } @@ -41560,78 +42089,95 @@ int MuseRecord::additionalNotationsQ(void) { ////////////////////////////// // -// MuseRecord::getAddCount -- returns the number of items -// in the additional notations field +// MuseRecord::getNotatedAccidental -- // -int MuseRecord::getAddCount(void) { - string addString = getAdditionalNotationsField(); - string addElement; // element from the notation field - - int count = 0; - int index = 0; - while (getAddElementIndex(index, addElement, addString)) { - count++; +int MuseRecord::getNotatedAccidental(void) { + int output = 0; + string recordInfo = getNotatedAccidentalField(); + switch (recordInfo[0]) { + case ' ': output = 0; break; + case '#': output = 1; break; + case 'n': output = 0; break; + case 'f': output = -1; break; + case 'x': output = 2; break; + case 'X': output = 2; break; + case '&': output = -2; break; + case 'S': output = 1; break; + case 'F': output = -1; break; + default: + cerr << "Error: unknown accidental: " << recordInfo[0] << endl; + return 0; } - - return count; + return output; } ////////////////////////////// // -// MuseRecord::getAddItem -- returns the specified item -// in the additional notations field +// MuseRecord::notatedAccidentalQ -- // -string MuseRecord::getAddItem(int elementIndex) { - string output; - int count = 0; - int index = 0; - string addString = getAdditionalNotationsField(); - - while (count <= elementIndex) { - getAddElementIndex(index, output, addString); - count++; +int MuseRecord::notatedAccidentalQ(void) { + int output; + string recordInfo = getNotatedAccidentalField(); + if (recordInfo[0] == ' ') { + output = 0; + } else { + output = 1; } - return output; } -////////////////////////////// +/////////////////////////////// // -// MuseRecord::getAddItemLevel -- returns the specified item's -// editorial level in the additional notations field +// MuseRecord::getTimeModificationField -- return columns 20 -- 22. // -int MuseRecord::getAddItemLevel(int elementIndex) { - int count = 0; - int index = 0; - string number; - string addString = getAdditionalNotationsField(); - string elementString; // element field - - while (count < elementIndex) { - getAddElementIndex(index, elementString, addString); - count++; +string MuseRecord::getTimeModificationField(void) { +// allowNotesOnly("getTimeModificationField"); ---> rests also + if (getLength() < 20) { + return " "; + } else { + return extract(20, 22); } +} - int output = -1; -repeating: - while (addString[index] != '&' && index >= 0) { + + +////////////////////////////// +// +// MuseRecord::getTimeModification -- +// + +string MuseRecord::getTimeModification(void) { + string output = getTimeModificationField(); + int index = 2; + while (index >= 0 && output[index] == ' ') { + output.resize(index); index--; } - if (addString[index] == '&' && !isalnum(addString[index+1])) { - index--; - goto repeating; - } else if (addString[index] == '&') { - number = addString[index+1]; - output = (int)strtol(number.c_str(), NULL, 36); + if (output.size() > 2) { + if (output[0] == ' ') { + output[0] = output[1]; + output[1] = output[2]; + output.resize(2); + } + } + if (output.size() > 1) { + if (output[0] == ' ') { + output[0] = output[1]; + output.resize(1); + } + } + if (output[0] == ' ') { + cerr << "Error: funny error occured in time modification " + << "(columns 20-22): " << getLine() << endl; + return ""; } - return output; } @@ -41639,18 +42185,12 @@ int MuseRecord::getAddItemLevel(int elementIndex) { ////////////////////////////// // -// MuseRecord::getEditorialLevels -- returns a string containing the -// edit levels given in the additional notation fields +// MuseRecord::getTimeModificationLeftField -- return column 20 // -string MuseRecord::getEditorialLevels(void) { - string output; - string addString = getAdditionalNotationsField(); - for (int index = 0; index < 12-1; index++) { - if (addString[index] == '&' && isalnum(addString[index+1])) { - output += addString[index+1]; - } - } +string MuseRecord::getTimeModificationLeftField(void) { + string output = getTimeModificationField(); + output.resize(1); return output; } @@ -41658,17 +42198,15 @@ string MuseRecord::getEditorialLevels(void) { ////////////////////////////// // -// MuseRecord::addEditorialLevelQ -- returns true if there are any editorial -// levels present in the additional notations fields +// MuseRecord::getTimeModificationLeftString -- // -int MuseRecord::addEditorialLevelQ(void) { - string addString = getAdditionalNotationsField(); - int output = 0; - for (int i=0; i<12-1; i++) { // minus one for width 2 (&0) - if (addString[i] == '&' && isalnum(addString[i+1])) { - output = 1; - } +string MuseRecord::getTimeModificationLeftString(void) { + string output = getTimeModificationField(); + if (output[0] == ' ') { + output = ""; + } else { + output.resize(1); } return output; } @@ -41677,32 +42215,17 @@ int MuseRecord::addEditorialLevelQ(void) { ////////////////////////////// // -// MuseRecord::findField -- returns true when it finds the first -// instance of the key in the additional fields record. +// MuseRecord::getTimeModificationLeft -- // -int MuseRecord::findField(const string& key) { - int len = (int)key.size(); - string notations = getAdditionalNotationsField(); +int MuseRecord::getTimeModificationLeft(void) { int output = 0; - for (int i=0; i<12-len; i++) { - if (notations[i] == key[0]) { - output = 1; - for (int j=0; j stop) { - return -1; - } - if (maxcol < stop) { - stop = maxcol; - } - int i; - for (i=start; i<=stop; i++) { - if (m_recordString[i-1] == key) { - return i; // return the column which is offset from 1 - } - } +////////////////////////////// +// +// MuseRecord::getTimeModificationRight -- +// - return -1; +string MuseRecord::getTimeModificationRightString(void) { + string output = getTimeModificationField(); + if (output[2] == ' ') { + output = ""; + } else { + output = output[2]; + } + return output; } ////////////////////////////// // -// MuseRecord::getSlurParameterRegion -- +// MuseRecord::getTimeModificationRight -- // -string MuseRecord::getSlurParameterRegion(void) { - return getColumns(31, 43); +int MuseRecord::getTimeModificationRight(void) { + int output = 0; + string recordInfo = getTimeModificationRightString(); + if (recordInfo[2] == ' ') { + output = 0; + } else { + string temp = recordInfo.substr(2); + output = (int)strtol(temp.c_str(), NULL, 36); + } + return output; } ////////////////////////////// // -// MuseRecord::getSlurStartColumn -- search column 32 to 43 for a slur -// marker. Returns the first one found from left to right. -// returns -1 if a slur character was not found. +// MuseRecord::timeModificationQ -- // -int MuseRecord::getSlurStartColumn(void) { - int start = 31; - int stop = getLength() - 1; - if (stop >= 43) { - stop = 42; - } - int i; - for (i=start; i<=stop; i++) { - switch (m_recordString[i]) { - case '(': // slur level 1 - case '[': // slur level 2 - case '{': // slur level 3 - case 'z': // slur level 4 - return i+1; // column is offset from 1 - } +int MuseRecord::timeModificationQ(void) { + int output = 0; + string recordInfo = getTimeModificationField(); + if (recordInfo[0] != ' ' || recordInfo[1] != ' ' || recordInfo[2] != ' ') { + output = 1; + } else { + output = 0; } - - return -1; + return output; } ////////////////////////////// // -// MuseRecord::getTextUnderlayField -- returns the contents -// of columns 44-80. +// MuseRecord::timeModificationLeftQ -- // -string MuseRecord::getTextUnderlayField(void) { - allowNotesOnly("getTextUnderlayField"); - return extract(44, 80); +int MuseRecord::timeModificationLeftQ(void) { + int output = 0; + string recordInfo = getTimeModificationField(); + if (recordInfo[0] == ' ') { + output = 0; + } else { + output = 1; + } + return output; } ////////////////////////////// // -// MuseRecord::textUnderlayQ -- +// MuseRecord::timeModificationRightQ -- // -int MuseRecord::textUnderlayQ(void) { +int MuseRecord::timeModificationRightQ(void) { int output = 0; - if (getLength() < 44) { + string recordInfo = getTimeModificationField(); + if (recordInfo[2] == ' ') { output = 0; } else { - for (int i=44; i<=80; i++) { - if (getColumn(i) != ' ') { - output = 1; - break; - } - } + output = 1; } return output; } @@ -41814,73 +42336,53 @@ int MuseRecord::textUnderlayQ(void) { ////////////////////////////// // -// MuseRecord::getVerseCount -- +// MuseRecord::getStemDirectionField -- // -int MuseRecord::getVerseCount(void) { - if (!textUnderlayQ()) { - return 0; - } - - int count = 1; - for (int i=44; i<=getLength() && i <= 80; i++) { - if (getColumn(i) == '|') { - count++; - } +string MuseRecord::getStemDirectionField(void) { + allowNotesOnly("getStemDirectionField"); + if (getLength() < 23) { + return " "; + } else { + string temp; + temp += getColumn(23); + return temp; } - - return count; } ////////////////////////////// // -// MuseRecord::getVerse -- +// MuseRecord::getStemDirectionString -- // -string MuseRecord::getVerse(int index) { - string output; - if (!textUnderlayQ()) { - return output; - } - int verseCount = getVerseCount(); - if (index >= verseCount) { - return output; - } - - int tindex = 44; - int c = 0; - while (c < index && tindex < 80) { - if (getColumn(tindex) == '|') { - c++; - } - tindex++; +string MuseRecord::getStemDirectionString(void) { + string output = getStemDirectionField(); + if (output[0] == ' ') { + output = ""; } + return output; +} - while (tindex <= 80 && getColumn(tindex) != '|') { - output += getColumn(tindex++); - } - // remove trailing spaces - int zindex = (int)output.size() - 1; - while (output[zindex] == ' ') { - zindex--; - } - zindex++; - output.resize(zindex); - // remove leading spaces - int spacecount = 0; - while (output[spacecount] == ' ') { - spacecount++; - } +////////////////////////////// +// +// MuseRecord::getStemDirection -- +// - // problem here? - for (int rr = 0; rr <= zindex-spacecount; rr++) { - output[rr] = output[rr+spacecount]; +int MuseRecord::getStemDirection(void) { + int output = 0; + string recordInfo = getStemDirectionField(); + switch (recordInfo[0]) { + case 'u': output = 1; break; + case 'd': output = -1; break; + case ' ': output = 0; break; + default: + cerr << "Error: unknown stem direction: " << recordInfo[0] << endl; + return 0; } - return output; } @@ -41888,249 +42390,136 @@ string MuseRecord::getVerse(int index) { ////////////////////////////// // -// MuseRecord::getVerseUtf8 -- +// MuseRecord::stemDirectionQ -- // -string MuseRecord::getVerseUtf8(int index) { - string tverse = getVerse(index); - return MuseRecord::musedataToUtf8(tverse); +int MuseRecord::stemDirectionQ(void) { + int output = 0; + string recordInfo = getStemDirectionField(); + if (recordInfo[0] == ' ') { + output = 0; + } else { + output = 1; + } + return output; } ////////////////////////////// // -// MuseRecord::getKernNoteStyle -- -// default values: beams = 0, stems = 0 +// MuseRecord::getStaffField -- returns column 24. // -string MuseRecord::getKernNoteStyle(int beams, int stems) { - string output; - - if (!isAnyNote()) { - // not a note, so return nothing - return ""; +string MuseRecord::getStaffField(void) { + allowNotesOnly("getStaffField"); + if (getLength() < 24) { + return " "; + } else { + string temp; + temp += getColumn(24); + return temp; } +} - // place the rhythm - stringstream tempdur; - int notetype = getGraphicNoteType(); - if (timeModificationLeftQ()) { - notetype = notetype / 4 * getTimeModificationLeft(); - if (timeModificationRightQ()) { - notetype = notetype * getTimeModificationRight(); - } else { - notetype = notetype * 2; - } - } - // logical duration of the note - HumNum logicalduration = getTicks(); - logicalduration /= getTpq(); - string durrecip = Convert::durationToRecip(logicalduration); - // graphical duration of the note - string graphicrecip = getGraphicRecip(); - HumNum graphicdur = Convert::recipToDuration(graphicrecip); +////////////////////////////// +// +// MuseRecord::getStaffString -- +// - string displayrecip; +string MuseRecord::getStaffString(void) { + string output = getStaffField(); + if (output[0] == ' ') { + output = ""; + } + return output; +} - if (graphicdur != logicalduration) { - // switch to the logical duration and store the graphic - // duration. The logical duration will be used on the - // main kern token, and the graphic duration will be stored - // as a layout parameter, such as !LO:N:vis=4. to display - // the note as a dotted quarter regardless of the logical - // duration. - // Current test file has encoding bug related to triplets, so - // disable graphic notation dealing with tuplets for now. - // for now just looking to see if one has a dot and the other does not - if ((durrecip.find(".") != string::npos) && - (graphicrecip.find(".") == string::npos)) { - m_graphicrecip = graphicrecip; - displayrecip = durrecip; - } else if ((durrecip.find(".") == string::npos) && - (graphicrecip.find(".") != string::npos)) { - m_graphicrecip = graphicrecip; - displayrecip = durrecip; - } - } +////////////////////////////// +// +// MuseRecord::getStaff -- +// - if (displayrecip.size() > 0) { - output = displayrecip; +int MuseRecord::getStaff(void) { + int output = 1; + string recordInfo = getStaffField(); + if (recordInfo[0] == ' ') { + output = 1; } else { - tempdur << notetype; - output = tempdur.str(); - // add any dots of prolongation to the output string - output += getStringProlongation(); + output = (int)strtol(recordInfo.c_str(), NULL, 36); } + return output; +} - // add the pitch to the output string - string musepitch = getPitchString(); - string kernpitch = Convert::musePitchToKernPitch(musepitch); - output += kernpitch; - - string logicalAccidental = getAccidentalString(); - string notatedAccidental = getNotatedAccidentalString(); - if (notatedAccidental.empty() && !logicalAccidental.empty()) { - // Indicate that the logical accidental should not be - // displayed (because of key signature or previous - // note in the measure that alters the accidental - // state of the current note). - output += "y"; - } else if ((logicalAccidental == notatedAccidental) && !notatedAccidental.empty()) { - // Indicate that the accidental should be displayed - // and is not suppressed by the key signature or a - // previous note in the measure. - output += "X"; - } - // There can be cases where the logical accidental - // is natural but the notated accidetnal is sharp (but - // the notated accidental means play a natural accidetnal). - // Deal with this later. - // if there is a notated natural sign, then add it now: - string temp = getNotatedAccidentalField(); - if (temp == "n") { - output += "n"; - } +////////////////////////////// +// +// MuseRecord::staffQ -- +// - // check if a grace note - if (getType() == 'g') { - output += "Q"; +int MuseRecord::staffQ(void) { + int output = 0; + string recordInfo = getStaffField(); + if (recordInfo[0] == ' ') { + output = 0; + } else { + output = 1; } + return output; +} - // if stems is true, then show stem directions - if (stems && stemDirectionQ()) { - switch (getStemDirection()) { - case 1: // 'u' = up - output += "/"; - break; - case -1: // 'd' = down - output += "\\"; - default: - ; // nothing // ' ' = no stem (if stage 2) - } - } - // if beams is true, then show any beams - if (beams && beamQ()) { - temp = getKernBeamStyle(); - output += temp; - } - if (isTied()) { - string tiestarts; - string tieends; - int lasttie = getLastTiedNoteLineIndex(); - int nexttie = getNextTiedNoteLineIndex(); - int state = 0; - if (lasttie >= 0) { - state |= 2; - } - if (nexttie >= 0) { - state |= 1; - } - switch (state) { - case 1: - tiestarts += "["; - break; - case 2: - tieends += "]"; - break; - case 3: - tieends += "_"; - break; - } - if (state) { - output = tiestarts + output + tieends; - } - } +////////////////////////////// +// +// MuseRecord::getBeamField -- +// - string slurstarts; - string slurends; - getSlurInfo(slurstarts, slurends); - if ((!slurstarts.empty()) || (!slurends.empty())) { - output = slurstarts + output + slurends; +string MuseRecord::getBeamField(void) { + allowNotesOnly("getBeamField"); + if (getLength() < 26) { + return " "; + } else { + return extract(26, 31); } - - return output; } ////////////////////////////// // -// MuseRecord::getSlurInfo -- -// -// ( ) = regular slur -// [ ] = second levels slur, convert to &( and &) -// { } = third level slur, convert to &&( and &&) -// Z = fourth level slur (how to close?) +// MuseRecord::setBeamInfo -- // -void MuseRecord::getSlurInfo(string& slurstarts, string& slurends) { - slurstarts.clear(); - slurends.clear(); - - string data = getSlurParameterRegion(); - for (int i=0; i<(int)data.size(); i++) { - if (data[i] == '(') { - slurstarts += '('; - } else if (data[i] == ')') { - slurends += ')'; - } else if (data[i] == '[') { - slurstarts += "&{"; - } else if (data[i] == ']') { - slurends += "&)"; - } else if (data[i] == '{') { - slurstarts += "&&("; - } else if (data[i] == '}') { - slurends += "&&)"; - } - } +void MuseRecord::setBeamInfo(string& strang) { + setColumns(strang, 26, 31); } ////////////////////////////// // -// MuseRecord::getKernNoteAccents -- +// MuseRecord::beamQ -- // -string MuseRecord::getKernNoteAccents(void) { - string output; - int addnotecount = getAddCount(); - for (int i=0; i': output += "^"; break; // horizontal accent - case '.': output += "'"; break; // staccato - case '_': output += "~"; break; // tenuto - case '=': output += "~'"; break; // detached legato - case 'i': output += "s"; break; // spiccato - case '\'': output += ","; break; // breath mark - case 'F': output += ";"; break; // fermata up - case 'E': output += ";"; break; // fermata down - case 'S': output += ":"; break; // staccato - case 't': output += "O"; break; // trill (to generic) - case 'r': output += "S"; break; // turn - case 'k': output += "O"; break; // delayed turn (to generic) - case 'w': output += "O"; break; // shake (to generic) - case 'M': output += "O"; break; // mordent (to generic) - case 'j': output += "H"; break; // glissando (slide) - } +int MuseRecord::beamQ(void) { + int output = 0; + allowNotesOnly("beamQ"); + if (getLength() < 26) { + output = 0; + } else { + for (int i=26; i<=31; i++) { + if (getColumn(i) != ' ') { + output = 1; + break; + } + } } - return output; } @@ -42138,105 +42527,104 @@ string MuseRecord::getKernNoteAccents(void) { ////////////////////////////// // -// MuseRecord::getKernRestStyle -- +// MuseRecord::getBeam8 -- column 26 // -string MuseRecord::getKernRestStyle(void) { +char MuseRecord::getBeam8(void) { + allowNotesOnly("getBeam8"); + return getColumn(26); +} - string output; - string rhythmstring; - // place the rhythm - stringstream tempdur; - if (!isAnyRest()) { - // not a rest, so return nothing - return ""; - } +////////////////////////////// +// +// MuseRecord::getBeam16 -- column 27 +// - // logical duration of the note - HumNum logicalduration = getTicks(); - logicalduration /= getTpq(); - string durrecip = Convert::durationToRecip(logicalduration); +char MuseRecord::getBeam16(void) { + allowNotesOnly("getBeam16"); + return getColumn(27); +} - /* - int notetype; - if (graphicNoteTypeQ()) { - notetype = getGraphicNoteType(); - if (timeModificationLeftQ()) { - notetype = notetype / 4 * getTimeModificationLeft(); - } - if (timeModificationRightQ()) { - notetype = notetype * getTimeModificationRight() / 2; - } - tempdur << notetype; - output = tempdur.str(); - // add any dots of prolongation to the output string - output += getStringProlongation(); - } else { // stage 1 data: - HumNum dnotetype(getTickDuration(), quarter); - rhythmstring = Convert::durationToRecip(dnotetype); - output += rhythmstring; - } - */ +////////////////////////////// +// +// MuseRecord::getBeam32 -- column 28 +// - output = durrecip; +char MuseRecord::getBeam32(void) { + allowNotesOnly("getBeam32"); + return getColumn(28); +} - // add the pitch to the output string - output += "r"; - if (isInvisibleRest()) { - output += "yy"; - } - return output; +////////////////////////////// +// +// MuseRecord::getBeam64 -- column 29 +// + +char MuseRecord::getBeam64(void) { + allowNotesOnly("getBeam64"); + return getColumn(29); } -////////////////////////////////////////////////////////////////////////// +////////////////////////////// // -// functions that work with measure records +// MuseRecord::getBeam128 -- column 30 // +char MuseRecord::getBeam128(void) { + allowNotesOnly("getBeam128"); + return getColumn(30); +} + + ////////////////////////////// // -// MuseRecord::getMeasureNumberField -- columns 9-12 +// MuseRecord::getBeam256 -- column 31 // -string MuseRecord::getMeasureNumberField(void) { - allowMeasuresOnly("getMeasureNumberField"); - return extract(9, 12); +char MuseRecord::getBeam256(void) { + allowNotesOnly("getBeam256"); + return getColumn(31); } ////////////////////////////// // -// MuseRecord::getMeasureTypeField -- columns 1 -- 7 +// MuseRecord::beam8Q -- // -string MuseRecord::getMeasureTypeField(void) { - allowMeasuresOnly("getMeasureTypeField"); - return extract(1, 7); +int MuseRecord::beam8Q(void) { + int output = 0; + if (getBeam8() == ' ') { + output = 0; + } else { + output = 1; + } + return output; } ////////////////////////////// // -// MuseRecord::getMeasureNumberString -- +// MuseRecord::beam16Q -- // -string MuseRecord::getMeasureNumberString(void) { - string output = getMeasureNumberField(); - for (int i=3; i>=0; i--) { - if (output[i] == ' ') { - output.resize(i); - } +int MuseRecord::beam16Q(void) { + int output = 0; + if (getBeam16() == ' ') { + output = 0; + } else { + output = 1; } return output; } @@ -42245,33 +42633,32 @@ string MuseRecord::getMeasureNumberString(void) { ////////////////////////////// // -// MuseRecord::getMeasureNumber -- +// MuseRecord::beam32Q -- // -int MuseRecord::getMeasureNumber(void) { - string measureInfo = getMeasureNumberField(); - if (measureInfo.empty()) { - return 0; +int MuseRecord::beam32Q(void) { + int output = 0; + if (getBeam32() == ' ') { + output = 0; + } else { + output = 1; } - return std::stoi(measureInfo); + return output; } ////////////////////////////// // -// MuseRecord::measureNumberQ -- +// MuseRecord::beam64Q -- // -int MuseRecord::measureNumberQ(void) { - string temp = getMeasureNumberString(); - int i = 0; +int MuseRecord::beam64Q(void) { int output = 0; - while (temp[i] != '\0') { - if (temp[i] != ' ') { - output = 1; - break; - } + if (getBeam64() == ' ') { + output = 0; + } else { + output = 1; } return output; } @@ -42280,32 +42667,32 @@ int MuseRecord::measureNumberQ(void) { ////////////////////////////// // -// MuseRecord::getMeasureFlagsString -- Columns 17 to 80. +// MuseRecord::beam128Q -- // -string MuseRecord::getMeasureFlagsString(void) { - if (m_recordString.size() < 17) { - return ""; +int MuseRecord::beam128Q(void) { + int output = 0; + if (getBeam128() == ' ') { + output = 0; } else { - return trimSpaces(m_recordString.substr(16)); + output = 1; } + return output; } ////////////////////////////// // -// MuseRecord::measureFermataQ -- returns true if there is a -// fermata above or below the measure +// MuseRecord::beam256Q -- // -int MuseRecord::measureFermataQ(void) { +int MuseRecord::beam256Q(void) { int output = 0; - for (int i=17; i<=80 && i<= getLength(); i++) { - if (getColumn(i) == 'F' || getColumn(i) == 'E') { - output = 1; - break; - } + if (getBeam256() == ' ') { + output = 0; + } else { + output = 1; } return output; } @@ -42314,25 +42701,30 @@ int MuseRecord::measureFermataQ(void) { ////////////////////////////// // -// MuseRecord::measureFlagQ -- Returns true if there are non-space -// characters in columns 17 through 80. A more smarter way of -// doing this is checking the allocated length of the record, and -// do not search non-allocated columns for non-space characters... +// MuseRecord::getAdditionalNotationsField -- returns the contents +// of columns 32-43. +// + +string MuseRecord::getAdditionalNotationsField(void) { + allowNotesOnly("getAdditionalNotationsField"); + return extract(32, 43); +} + + + +////////////////////////////// +// +// MuseRecord::additionalNotationsQ -- // -int MuseRecord::measureFlagQ(const string& key) { +int MuseRecord::additionalNotationsQ(void) { int output = 0; - int len = (int)key.size(); - for (int i=17; i<=80-len && i& amap) { - amap.clear(); - // Should be "3" on the next line, but "1" or "2" might catch poorly formatted data. - string contents = getLine().substr(2); - if (contents.empty()) { - return; - } - int i = 0; - string key; - string value; - int state = 0; // 0 outside, 1 = in key, 2 = in value - while (i < (int)contents.size()) { - switch (state) { - case 0: // outside of key or value - if (!isspace(contents[i])) { - if (contents[i] == ':') { - // Strange: should not happen - key.clear(); - state = 2; - } else { - state = 1; - key += contents[i]; - } - } - break; - case 1: // in key - if (!isspace(contents[i])) { - if (contents[i] == ':') { - value.clear(); - state = 2; - } else { - // Add to key, such as "C2" for second staff clef. - key += contents[i]; - } - } - break; - case 2: // in value - if (key == "D") { - value += contents[i]; - } else if (isspace(contents[i])) { - // store parameter and clear variables - amap[key] = value; - state = 0; - key.clear(); - value.clear(); - } else { - value += contents[i]; - } - break; - } - i++; +int MuseRecord::getAddItemLevel(int elementIndex) { + int count = 0; + int index = 0; + string number; + string addString = getAdditionalNotationsField(); + string elementString; // element field + + while (count < elementIndex) { + getAddElementIndex(index, elementString, addString); + count++; } - if ((!key.empty()) && (!value.empty())) { - amap[key] = value; + int output = -1; +repeating: + while (addString[index] != '&' && index >= 0) { + index--; } + if (addString[index] == '&' && !isalnum(addString[index+1])) { + index--; + goto repeating; + } else if (addString[index] == '&') { + number = addString[index+1]; + output = (int)strtol(number.c_str(), NULL, 36); + } + + return output; } ////////////////////////////// // -// MuseRecord::getAttributes -- +// MuseRecord::getEditorialLevels -- returns a string containing the +// edit levels given in the additional notation fields // -string MuseRecord::getAttributes(void) { +string MuseRecord::getEditorialLevels(void) { string output; - switch (getType()) { - case E_muserec_musical_attributes: - break; - default: - cerr << "Error: cannot use getAttributes function on line: " - << getLine() << endl; - return ""; - } - - int ending = 0; - int tempcol; - for (int column=4; column <= getLength(); column++) { - if (getColumn(column) == ':') { - tempcol = column - 1; - while (tempcol > 0 && getColumn(tempcol) != ' ') { - tempcol--; - } - tempcol++; - while (tempcol <= column) { - output += getColumn(tempcol); - if (output.back() == 'D') { - ending = 1; - } - tempcol++; - } - } - if (ending) { - break; + string addString = getAdditionalNotationsField(); + for (int index = 0; index < 12-1; index++) { + if (addString[index] == '&' && isalnum(addString[index+1])) { + output += addString[index+1]; } } return output; @@ -42537,38 +42834,16 @@ string MuseRecord::getAttributes(void) { ////////////////////////////// // -// MuseRecord::attributeQ -- +// MuseRecord::addEditorialLevelQ -- returns true if there are any editorial +// levels present in the additional notations fields // -int MuseRecord::attributeQ(const string& attribute) { - switch (getType()) { - case E_muserec_musical_attributes: - break; - default: - cerr << "Error: cannot use getAttributes function on line: " - << getLine() << endl; - return 0; - } - - - string attributelist = getAttributes(); - +int MuseRecord::addEditorialLevelQ(void) { + string addString = getAdditionalNotationsField(); int output = 0; - int attstrlength = (int)attributelist.size(); - int attlength = (int)attribute.size(); - - for (int i=0; i 0 && getColumn(tempcol) != ' ') { - tempcol--; - } - tempcol++; - while (tempcol <= column) { - if (getColumn(tempcol) == attribute) { - ending = 2; - } else if (getColumn(tempcol) == 'D') { - ending = 1; +int MuseRecord::findField(const string& key) { + int len = (int)key.size(); + string notations = getAdditionalNotationsField(); + int output = 0; + for (int i=0; i<12-len; i++) { + if (notations[i] == key[0]) { + output = 1; + for (int j=0; j stop) { + return -1; } - int returnValue = 0; - int ending = 0; - // int index = 0; - int tempcol; - int column; - for (column=4; column <= getLength(); column++) { - if (getColumn(column) == ':') { - tempcol = column - 1; - while (tempcol > 0 && getColumn(tempcol) != ' ') { - tempcol--; - } - tempcol++; - while (tempcol <= column) { - if (getColumn(tempcol) == key[0]) { - ending = 2; - } else if (getColumn(tempcol) == 'D') { - ending = 1; - } - tempcol++; - // index++; - } - } - if (ending) { - break; - } + if (maxcol < stop) { + stop = maxcol; } - value.clear(); - if (ending == 0 || ending == 1) { - return returnValue; - } else { - returnValue = 1; - column++; - while (getColumn(column) != ' ') { - value += getColumn(column++); + int i; + for (i=start; i<=stop; i++) { + if (m_recordString[i-1] == key) { + return i; // return the column which is offset from 1 } - return returnValue; } + + return -1; } -/////////////////////////////////////////////////////////////////////////// +////////////////////////////// // -// functions that work with basso continuo figuration records (f): +// MuseRecord::getSlurParameterRegion -- // +string MuseRecord::getSlurParameterRegion(void) { + return getColumns(31, 43); +} + + ////////////////////////////// // -// MuseRecord::getFigureCountField -- column 2. +// MuseRecord::getSlurStartColumn -- search column 32 to 43 for a slur +// marker. Returns the first one found from left to right. +// returns -1 if a slur character was not found. // -string MuseRecord::getFigureCountField(void) { - allowFigurationOnly("getFigureCountField"); - return extract(2, 2); +int MuseRecord::getSlurStartColumn(void) { + int start = 31; + int stop = getLength() - 1; + if (stop >= 43) { + stop = 42; + } + int i; + for (i=start; i<=stop; i++) { + switch (m_recordString[i]) { + case '(': // slur level 1 + case '[': // slur level 2 + case '{': // slur level 3 + case 'z': // slur level 4 + return i+1; // column is offset from 1 + } + } + + return -1; } ////////////////////////////// // -// MuseRecord::getFigurationCountString -- +// MuseRecord::getTextUnderlayField -- returns the contents +// of columns 44-80. // -string MuseRecord::getFigureCountString(void) { - allowFigurationOnly("getFigureCount"); - string output = extract(2, 2); - if (output[0] == ' ') { - output = ""; - } - return output; +string MuseRecord::getTextUnderlayField(void) { + allowNotesOnly("getTextUnderlayField"); + return extract(44, 80); } ////////////////////////////// // -// MuseRecord::getFigurationCount -- +// MuseRecord::textUnderlayQ -- // -int MuseRecord::getFigureCount(void) { - allowFigurationOnly("getFigureCount"); - string temp = getFigureCountString(); - int output = (int)strtol(temp.c_str(), NULL, 36); +int MuseRecord::textUnderlayQ(void) { + int output = 0; + if (getLength() < 44) { + output = 0; + } else { + for (int i=44; i<=80; i++) { + if (getColumn(i) != ' ') { + output = 1; + break; + } + } + } return output; } @@ -42740,29 +42990,73 @@ int MuseRecord::getFigureCount(void) { ////////////////////////////// // -// getFigurePointerField -- columns 6 -- 8. +// MuseRecord::getVerseCount -- // -string MuseRecord::getFigurePointerField(void) { - allowFigurationOnly("getFigurePointerField"); - return extract(6, 8); +int MuseRecord::getVerseCount(void) { + if (!textUnderlayQ()) { + return 0; + } + + int count = 1; + for (int i=44; i<=getLength() && i <= 80; i++) { + if (getColumn(i) == '|') { + count++; + } + } + + return count; } + ////////////////////////////// // -// figurePointerQ -- +// MuseRecord::getVerse -- // -int MuseRecord::figurePointerQ(void) { - allowFigurationOnly("figurePointerQ"); - int output = 0; - for (int i=6; i<=8; i++) { - if (getColumn(i) != ' ') { - output = 1; - break; +string MuseRecord::getVerse(int index) { + string output; + if (!textUnderlayQ()) { + return output; + } + int verseCount = getVerseCount(); + if (index >= verseCount) { + return output; + } + + int tindex = 44; + int c = 0; + while (c < index && tindex < 80) { + if (getColumn(tindex) == '|') { + c++; } + tindex++; + } + + while (tindex <= 80 && getColumn(tindex) != '|') { + output += getColumn(tindex++); + } + + // remove trailing spaces + int zindex = (int)output.size() - 1; + while (output[zindex] == ' ') { + zindex--; + } + zindex++; + output.resize(zindex); + + // remove leading spaces + int spacecount = 0; + while (output[spacecount] == ' ') { + spacecount++; + } + + // problem here? + for (int rr = 0; rr <= zindex-spacecount; rr++) { + output[rr] = output[rr+spacecount]; } + return output; } @@ -42770,79 +43064,97 @@ int MuseRecord::figurePointerQ(void) { ////////////////////////////// // -// MuseRecord::getFigureString -- +// MuseRecord::getVerseUtf8 -- // -string MuseRecord::getFigureString(void) { - string output = getFigureFields(); - for (int i=(int)output.size()-1; i>= 0; i--) { - if (isspace(output[i])) { - output.resize((int)output.size() - 1); - } else { - break; +string MuseRecord::getVerseUtf8(int index) { + string tverse = getVerse(index); + return MuseRecord::musedataToUtf8(tverse); +} + + + +////////////////////////////// +// +// MuseRecord::getSlurInfo -- +// +// ( ) = regular slur +// [ ] = second levels slur, convert to &( and &) +// { } = third level slur, convert to &&( and &&) +// Z = fourth level slur (how to close?) +// + +void MuseRecord::getSlurInfo(string& slurstarts, string& slurends) { + slurstarts.clear(); + slurends.clear(); + + string data = getSlurParameterRegion(); + for (int i=0; i<(int)data.size(); i++) { + if (data[i] == '(') { + slurstarts += '('; + } else if (data[i] == ')') { + slurends += ')'; + } else if (data[i] == '[') { + slurstarts += "&{"; + } else if (data[i] == ']') { + slurends += "&)"; + } else if (data[i] == '{') { + slurstarts += "&&("; + } else if (data[i] == '}') { + slurends += "&&)"; } } - return output; } + ////////////////////////////// // -// MuseRecord::getFigureFields -- columns 17 -- 80 +// MuseRecord::MuseRecord -- // -string MuseRecord::getFigureFields(void) { - allowFigurationOnly("getFigureFields"); - return extract(17, 80); -} +MuseRecord::MuseRecord(void) : MuseRecordBasic() { } +MuseRecord::MuseRecord(const string& aLine) : MuseRecordBasic(aLine) { } +MuseRecord::MuseRecord(MuseRecord& aRecord) : MuseRecordBasic(aRecord) { } + ////////////////////////////// // -// MuseRecord::figureFieldsQ -- +// MuseRecord::~MuseRecord -- // -int MuseRecord::figureFieldsQ(void) { - allowFigurationOnly("figureFieldsQ"); - int output = 0; - if (getLength() < 17) { - output = 0; - } else { - for (int i=17; i<=80; i++) { - if (getColumn(i) != ' ') { - output = 1; - break; - } - } - } - return output; +MuseRecord::~MuseRecord() { + // do nothing } ////////////////////////////// // -// getFigure -- +// MuseRecord::operator= -- // -string MuseRecord::getFigure(int index) { - string output; - allowFigurationOnly("getFigure"); - if (index >= getFigureCount()) { - return output; - } - string temp = getFigureString(); - if (index == 0) { - return temp; - } - HumRegex hre; - vector pieces; - hre.split(pieces, temp, " +"); - if (index < (int)pieces.size()) { - output = pieces[index]; +MuseRecord& MuseRecord::operator=(MuseRecord& aRecord) { + // don't copy onto self + if (&aRecord == this) { + return *this; } - return output; + + setLine(aRecord.getLine()); + setType(aRecord.getType()); + m_lineindex = aRecord.m_lineindex; + + m_absbeat = aRecord.m_absbeat; + m_lineduration = aRecord.m_lineduration; + m_noteduration = aRecord.m_noteduration; + + m_b40pitch = aRecord.m_b40pitch; + m_nexttiednote = aRecord.m_nexttiednote; + m_lasttiednote = aRecord.m_lasttiednote; + + return *this; } @@ -43406,121 +43718,558 @@ bool MuseRecord::isOctaveStop(void) { ////////////////////////////// // -// MuseRecord::getDirectionText -- Return the text starting in column 25. +// MuseRecord::getDirectionText -- Return the text starting in column 25. +// + +std::string MuseRecord::getDirectionText(void) { + int length = (int)m_recordString.size(); + if (length < 25) { + // no text + return ""; + } + return trimSpaces(m_recordString.substr(24)); +} + + + +////////////////////////////// +// +// MuseRecord::hasPrintSuggestions -- +// + +bool MuseRecord::hasPrintSuggestions(void) { + MuseData* md = getOwner(); + if (md == NULL) { + return false; + } + if (m_lineindex < 0) { + return false; + } + if (m_lineindex >= md->getLineCount() - 1) { + return false; + } + if (md->getRecord(m_lineindex).isPrintSuggestion()) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MuseRecord::getPrintSuggestions -- Return any print suggestions +// for the given column number +// + +void MuseRecord::getPrintSuggestions(vector& suggestions, int column) { + suggestions.clear(); + + MuseData* md = getOwner(); + if (md == NULL) { + return; + } + if (m_lineindex < 0) { + return; + } + if (m_lineindex >= md->getLineCount() - 1) { + return; + } + if (!md->getRecord(m_lineindex+1).isPrintSuggestion()) { + return; + } + + string pline = md->getLine(m_lineindex+1); + HumRegex hre; + vector entries; + hre.split(entries, pline, "\\s+"); + for (int i=0; i<(int)entries.size(); i++) { + if (entries[i][0] != 'C') { + continue; + } + if (hre.search(entries[i], "C(\\d+):([^\\s]+)")) { + int value = hre.getMatchInt(1); + if (value == column) { + suggestions.push_back(hre.getMatch(2)); + } + } + } +} + + + +////////////////////////////// +// +// MuseRecord::getAllPrintSuggestions -- Return all print suggestions. +// + +void MuseRecord::getAllPrintSuggestions(vector& suggestions) { + suggestions.clear(); + + MuseData* md = getOwner(); + if (md == NULL) { + return; + } + if (m_lineindex < 0) { + return; + } + if (m_lineindex >= md->getLineCount() - 1) { + return; + } + if (!md->getRecord(m_lineindex+1).isPrintSuggestion()) { + return; + } + + string pline = md->getLine(m_lineindex+1); + HumRegex hre; + vector entries; + hre.split(entries, pline, " "); + for (int i=0; i<(int)entries.size(); i++) { + if (entries[i][0] != 'C') { + continue; + } + if (hre.search(entries[i], "C(\\d+):([^\\s]+)")) { + suggestions.push_back(entries[i]); + } + } +} + + + + + + + + +////////////////////////////// +// +// MuseRecordBasic::isPartName -- +// + +bool MuseRecordBasic::isPartName(void) { + return m_type == E_muserec_header_part_name; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isAttributes -- +// + +bool MuseRecordBasic::isAttributes(void) { + return m_type == E_muserec_musical_attributes; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isSource -- +// + +bool MuseRecordBasic::isSource(void) { + return m_type == E_muserec_source; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isEncoder -- +// + +bool MuseRecordBasic::isEncoder(void) { + return m_type == E_muserec_encoder; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isId -- +// + +bool MuseRecordBasic::isId(void) { + return m_type == E_muserec_id; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isBarline -- +// + +bool MuseRecordBasic::isBarline(void) { + return m_type == E_muserec_measure; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isBackup -- +// + +bool MuseRecordBasic::isBackup(void) { + return m_type == E_muserec_back; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isAnyComment -- +// + +bool MuseRecordBasic::isAnyComment(void) { + return isLineComment() || isBlockComment(); +} + + + +////////////////////////////// +// +// MuseRecordBasic::isLineComment -- +// + +bool MuseRecordBasic::isLineComment(void) { + return m_type == E_muserec_comment_line; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isBlockComment -- +// + +bool MuseRecordBasic::isBlockComment(void) { + return m_type == E_muserec_comment_toggle; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isChordNote -- Is a regular note that is a seoncdary +// note in a chord (not the first note in the chord). +// + +bool MuseRecordBasic::isChordNote(void) { + return m_type == E_muserec_note_chord; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isDirection -- Is a musical direction (text) +// instruction. +// + +bool MuseRecordBasic::isDirection(void) { + return m_type == E_muserec_musical_directions; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isMusicalDirection -- Is a musical direction (text) +// instruction. +// + +bool MuseRecordBasic::isMusicalDirection(void) { + return isDirection(); +} + + + +////////////////////////////// +// +// MuseRecordBasic::isGraceNote -- A grace note, either a single note or +// the first note in a gracenote chord. +// + +bool MuseRecordBasic::isGraceNote(void) { + return m_type == E_muserec_note_grace; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isCueNote -- +// + +bool MuseRecordBasic::isCueNote(void) { + return m_type == E_muserec_note_cue; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isChordNote -- +// + +bool MuseRecordBasic::isChordGraceNote(void) { + return m_type == E_muserec_note_grace_chord; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isFiguredHarmony -- +// + +bool MuseRecordBasic::isFiguredHarmony(void) { + return m_type == E_muserec_figured_harmony; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isPrintSuggestion -- +// + +bool MuseRecordBasic::isPrintSuggestion(void) { + switch (m_type) { + case E_muserec_print_suggestion: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isRegularNote -- +// + +bool MuseRecordBasic::isRegularNote(void) { + switch (m_type) { + case E_muserec_note_regular: + return true; + } + return false; +} + + +////////////////////////////// +// +// MuseRecordBasic::isAnyNote -- +// + +bool MuseRecordBasic::isAnyNote(void) { + switch (m_type) { + case E_muserec_note_regular: + case E_muserec_note_chord: + case E_muserec_note_cue: + case E_muserec_note_grace: + case E_muserec_note_grace_chord: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isAnyNoteOrRest -- +// + +bool MuseRecordBasic::isAnyNoteOrRest(void) { + switch (m_type) { + case E_muserec_note_regular: + case E_muserec_note_chord: + case E_muserec_note_cue: + case E_muserec_note_grace: + case E_muserec_note_grace_chord: + case E_muserec_rest_invisible: + case E_muserec_rest: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isInvisibleRest -- +// + +bool MuseRecordBasic::isInvisibleRest(void) { + switch (m_type) { + case E_muserec_rest_invisible: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isRegularRest -- +// + +bool MuseRecordBasic::isRegularRest(void) { + switch (m_type) { + case E_muserec_rest: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isAnyRest -- Also cue-sized rests? +// + +bool MuseRecordBasic::isAnyRest(void) { + switch (m_type) { + case E_muserec_rest_invisible: + case E_muserec_rest: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isCopyright -- +// + +bool MuseRecordBasic::isCopyright(void) { + switch (m_type) { + case E_muserec_copyright: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isWorkInfo -- +// + +bool MuseRecordBasic::isWorkInfo(void) { + switch (m_type) { + case E_muserec_work_info: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isWorkTitle -- +// + +bool MuseRecordBasic::isWorkTitle(void) { + switch (m_type) { + case E_muserec_work_title: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isMovementTitle -- +// + +bool MuseRecordBasic::isMovementTitle(void) { + switch (m_type) { + case E_muserec_movement_title: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isGroup -- +// + +bool MuseRecordBasic::isGroup(void) { + switch (m_type) { + case E_muserec_group: + return true; + } + return false; +} + + + +////////////////////////////// +// +// MuseRecordBasic::isGroupMembership -- // -std::string MuseRecord::getDirectionText(void) { - int length = (int)m_recordString.size(); - if (length < 25) { - // no text - return ""; +bool MuseRecordBasic::isGroupMembership(void) { + switch (m_type) { + case E_muserec_group_memberships: + return true; } - return trimSpaces(m_recordString.substr(24)); + return false; } ////////////////////////////// // -// MuseRecord::hasPrintSuggestions -- +// MuseRecordBasic::isHeaderRecord -- True if a header, or a comment +// occurring before the first non-header record. // -bool MuseRecord::hasPrintSuggestions(void) { - MuseData* md = getOwner(); - if (md == NULL) { - return false; - } - if (m_lineindex < 0) { - return false; - } - if (m_lineindex >= md->getLineCount() - 1) { - return false; - } - if (md->getRecord(m_lineindex).isPrintSuggestion()) { - return true; - } else { - return false; - } +bool MuseRecordBasic::isHeaderRecord(void) { + return m_header > 0; } ////////////////////////////// // -// MuseRecord::getPrintSuggestions -- Return any print suggestions -// for the given column number +// MuseRecordBasic::isBodyRecord -- True if not a header record. // -void MuseRecord::getPrintSuggestions(vector& suggestions, int column) { - suggestions.clear(); - - MuseData* md = getOwner(); - if (md == NULL) { - return; - } - if (m_lineindex < 0) { - return; - } - if (m_lineindex >= md->getLineCount() - 1) { - return; - } - if (!md->getRecord(m_lineindex+1).isPrintSuggestion()) { - return; - } - - string pline = md->getLine(m_lineindex+1); - HumRegex hre; - vector entries; - hre.split(entries, pline, "\\s+"); - for (int i=0; i<(int)entries.size(); i++) { - if (entries[i][0] != 'C') { - continue; - } - if (hre.search(entries[i], "C(\\d+):([^\\s]+)")) { - int value = hre.getMatchInt(1); - if (value == column) { - suggestions.push_back(hre.getMatch(2)); - } - } - } +bool MuseRecordBasic::isBodyRecord(void) { + return m_header == 0; } + ////////////////////////////// // -// MuseRecord::getAllPrintSuggestions -- Return all print suggestions. +// MuseRecord::addPrintSuggestion -- add a delta index for associated +// print suggestion. // -void MuseRecord::getAllPrintSuggestions(vector& suggestions) { - suggestions.clear(); - - MuseData* md = getOwner(); - if (md == NULL) { - return; - } - if (m_lineindex < 0) { - return; - } - if (m_lineindex >= md->getLineCount() - 1) { - return; - } - if (!md->getRecord(m_lineindex+1).isPrintSuggestion()) { - return; - } - - string pline = md->getLine(m_lineindex+1); - HumRegex hre; - vector entries; - hre.split(entries, pline, " "); - for (int i=0; i<(int)entries.size(); i++) { - if (entries[i][0] != 'C') { - continue; - } - if (hre.search(entries[i], "C(\\d+):([^\\s]+)")) { - suggestions.push_back(entries[i]); - } - } +void MuseRecord::addPrintSuggestion(int deltaIndex) { + m_printSuggestions.push_back(deltaIndex); } @@ -43528,9 +44277,6 @@ void MuseRecord::getAllPrintSuggestions(vector& suggestions) { - - - ////////////////////////////// // // MuseRecordBasic::MuseRecordBasic -- @@ -43965,7 +44711,7 @@ void MuseRecordBasic::appendString(const string& astring) { ////////////////////////////// // -// MuseRecord::appendInteger -- Insert an integer after the last character +// MuseRecordBasic::appendInteger -- Insert an integer after the last character // in the current line. // @@ -43978,7 +44724,7 @@ void MuseRecordBasic::appendInteger(int value) { ////////////////////////////// // -// MuseRecord::appendRational -- Insert a rational after the last character +// MuseRecordBasic::appendRational -- Insert a rational after the last character // in the current line. // @@ -43993,7 +44739,7 @@ void MuseRecordBasic::appendRational(HumNum& value) { ////////////////////////////// // -// MuseRecord::append -- append multiple objects in sequence +// MuseRecordBasic::append -- append multiple objects in sequence // from left to right onto the record. The format contains // characters with two possibilities at the moment: // "i": integer value @@ -44287,413 +45033,6 @@ void MuseRecordBasic::cleanLineEnding(void) { -////////////////////////////// -// -// MuseRecordBasic::isPartName -- -// - -bool MuseRecordBasic::isPartName(void) { - return m_type == E_muserec_header_part_name; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isAttributes -- -// - -bool MuseRecordBasic::isAttributes(void) { - return m_type == E_muserec_musical_attributes; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isSource -- -// - -bool MuseRecordBasic::isSource(void) { - return m_type == E_muserec_source; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isEncoder -- -// - -bool MuseRecordBasic::isEncoder(void) { - return m_type == E_muserec_encoder; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isId -- -// - -bool MuseRecordBasic::isId(void) { - return m_type == E_muserec_id; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isBarline -- -// - -bool MuseRecordBasic::isBarline(void) { - return m_type == E_muserec_measure; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isBackup -- -// - -bool MuseRecordBasic::isBackup(void) { - return m_type == E_muserec_back; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isAnyComment -- -// - -bool MuseRecordBasic::isAnyComment(void) { - return isLineComment() || isBlockComment(); -} - - - -////////////////////////////// -// -// MuseRecordBasic::isLineComment -- -// - -bool MuseRecordBasic::isLineComment(void) { - return m_type == E_muserec_comment_line; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isBlockComment -- -// - -bool MuseRecordBasic::isBlockComment(void) { - return m_type == E_muserec_comment_toggle; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isChordNote -- Is a regular note that is a seoncdary -// note in a chord (not the first note in the chord). -// - -bool MuseRecordBasic::isChordNote(void) { - return m_type == E_muserec_note_chord; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isDirection -- Is a musical direction (text) -// instruction. -// - -bool MuseRecordBasic::isDirection(void) { - return m_type == E_muserec_musical_directions; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isGraceNote -- A grace note, either a single note or -// the first note in a gracenote chord. -// - -bool MuseRecordBasic::isGraceNote(void) { - return m_type == E_muserec_note_grace; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isCueNote -- -// - -bool MuseRecordBasic::isCueNote(void) { - return m_type == E_muserec_note_cue; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isChordNote -- -// - -bool MuseRecordBasic::isChordGraceNote(void) { - return m_type == E_muserec_note_grace_chord; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isFiguredHarmony -- -// - -bool MuseRecordBasic::isFiguredHarmony(void) { - return m_type == E_muserec_figured_harmony; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isPrintSuggestion -- -// - -bool MuseRecordBasic::isPrintSuggestion(void) { - switch (m_type) { - case E_muserec_print_suggestion: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isRegularNote -- -// - -bool MuseRecordBasic::isRegularNote(void) { - switch (m_type) { - case E_muserec_note_regular: - return true; - } - return false; -} - - -////////////////////////////// -// -// MuseRecordBasic::isAnyNote -- -// - -bool MuseRecordBasic::isAnyNote(void) { - switch (m_type) { - case E_muserec_note_regular: - case E_muserec_note_chord: - case E_muserec_note_cue: - case E_muserec_note_grace: - case E_muserec_note_grace_chord: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isAnyNoteOrRest -- -// - -bool MuseRecordBasic::isAnyNoteOrRest(void) { - switch (m_type) { - case E_muserec_note_regular: - case E_muserec_note_chord: - case E_muserec_note_cue: - case E_muserec_note_grace: - case E_muserec_note_grace_chord: - case E_muserec_rest_invisible: - case E_muserec_rest: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isInvisibleRest -- -// - -bool MuseRecordBasic::isInvisibleRest(void) { - switch (m_type) { - case E_muserec_rest_invisible: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isRegularRest -- -// - -bool MuseRecordBasic::isRegularRest(void) { - switch (m_type) { - case E_muserec_rest: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isAnyRest -- Also cue-sized rests? -// - -bool MuseRecordBasic::isAnyRest(void) { - switch (m_type) { - case E_muserec_rest_invisible: - case E_muserec_rest: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isCopyright -- -// - -bool MuseRecordBasic::isCopyright(void) { - switch (m_type) { - case E_muserec_copyright: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isWorkInfo -- -// - -bool MuseRecordBasic::isWorkInfo(void) { - switch (m_type) { - case E_muserec_work_info: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isWorkTitle -- -// - -bool MuseRecordBasic::isWorkTitle(void) { - switch (m_type) { - case E_muserec_work_title: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isMovementTitle -- -// - -bool MuseRecordBasic::isMovementTitle(void) { - switch (m_type) { - case E_muserec_movement_title: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isGroup -- -// - -bool MuseRecordBasic::isGroup(void) { - switch (m_type) { - case E_muserec_group: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isGroupMembership -- -// - -bool MuseRecordBasic::isGroupMembership(void) { - switch (m_type) { - case E_muserec_group_memberships: - return true; - } - return false; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isHeaderRecord -- True if a header, or a comment -// occurring before the first non-header record. -// - -bool MuseRecordBasic::isHeaderRecord(void) { - return m_header > 0; -} - - - -////////////////////////////// -// -// MuseRecordBasic::isBodyRecord -- True if not a header record. -// - -bool MuseRecordBasic::isBodyRecord(void) { - return m_header == 0; -} - - - ////////////////////////////// // // MuseRecordBasic::trimSpaces -- @@ -51955,386 +52294,1371 @@ PixelColor PixelColor::getColor(const string& colorstring) { if (cs == "yellowgreen" ) { output.setColor("#9acd32"); return output; } } - // References: - // http://netdancer.com/rgbblk.htm - // http://www.htmlhelp.com/cgi-bin/color.cgi?rgb=FFFFFF - // http://www.brobstsystems.com/colors1.htm + // References: + // http://netdancer.com/rgbblk.htm + // http://www.htmlhelp.com/cgi-bin/color.cgi?rgb=FFFFFF + // http://www.brobstsystems.com/colors1.htm + + return output; +} + + + +////////////////////////////// +// +// PixelColor::writePpm6 -- write the pixel in PPM 6 format. +// + +void PixelColor::writePpm6(ostream& out) { + out << (unsigned char)getRed() << (unsigned char)getGreen() << (unsigned char)getBlue(); +} + +void PixelColor::writePpm3(ostream& out) { + out << (int)getRed() << " " + << (int)getGreen() << " " + << (int)getBlue() << " "; +} + + + +////////////////////////////// +// +// PixelColor::setHue -- +// + +PixelColor& PixelColor::setHue(float value) { + double fraction = value - (int)value; + if (fraction < 0) { + fraction = fraction + 1.0; + } + + if (fraction < 1.0/6.0) { + Red = 255; + Green = (unsigned char)limit(floatToChar(6.0 * fraction), 0, 255); + Blue = 0; + } else if (fraction < 2.0/6.0) { + Red = (unsigned char)limit(255 - floatToChar(6.0 * (fraction - 1.0/6.0)), 0,255); + Green = 255; + Blue = 0; + } else if (fraction < 3.0/6.0) { + Red = 0; + Green = 255; + Blue = (unsigned char)limit(floatToChar(6.0 * (fraction - 2.0/6.0)), 0,255); + } else if (fraction < 4.0/6.0) { + Red = 0; + Blue = 255; + Green = (unsigned char)limit(255 - floatToChar(6.0 * (fraction - 3.0/6.0)), 0,255); + } else if (fraction < 5.0/6.0) { + Red = (unsigned char)limit(floatToChar(6.0 * (fraction - 4.0/6.0)), 0,255); + Green = 0; + Blue = 255; + } else if (fraction <= 6.0/6.0) { + Red = 255; + Green = 0; + Blue = (unsigned char)limit(255 - floatToChar(6.0 * (fraction - 5.0/6.0)), 0,255); + } else { + Red = 0; + Green = 0; + Blue = 0; + } + + return *this; +} + + + +////////////////////////////// +// +// PixelColor::setTriHue -- Red, Green, Blue with a little overlap +// + +PixelColor& PixelColor::setTriHue(float value) { + double fraction = value - (int)value; + if (fraction < 0) { + fraction = fraction + 1.0; + } + if (fraction < 1.0/3.0) { + Green = (unsigned char)limit(floatToChar(3.0 * fraction), 0, 255); + Red = (unsigned char)limit(255 - Green, 0, 255); + Blue = 0; + } else if (fraction < 2.0/3.0) { + setBlue(floatToChar(3.0 * (fraction - 1.0/3.0))); + setGreen(255 - getBlue()); + setRed(0); + } else { + setRed(floatToChar(3.0 * (fraction - 2.0/3.0))); + setBlue(255 - Red); + setGreen(0); + } + + return *this; +} + + + + +////////////////////////////////////////////////////////////////////////// +// +// private functions: +// + + +////////////////////////////// +// +// PixelColor::charToFloat -- +// + +float PixelColor::charToFloat(int value) { + return value / 255.0; +} + + +////////////////////////////// +// +// PixelColor::floatToChar -- +// + +int PixelColor::floatToChar(float value) { + return limit((int)(value * 255.0 + 0.5), 0, 255); +} + + +////////////////////////////// +// +// limit -- +// + +int PixelColor::limit(int value, int min, int max) { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + return value; +} + + + +////////////////////////////// +// +// PixelColor:mix -- mix two colors together. +// + +PixelColor PixelColor::mix(PixelColor& color1, PixelColor& color2) { + + PixelColor p1 = color1.getHsi(); + PixelColor p2 = color2.getHsi(); + + PixelColor output; + unsigned int r = ((unsigned int)color1.Red + (unsigned int)color2.Red)/2; + unsigned int g = ((unsigned int)color1.Green + (unsigned int)color2.Green)/2; + unsigned int b = ((unsigned int)color1.Blue + (unsigned int)color2.Blue)/2; + + output.setRed(r); + output.setGreen(g); + output.setBlue(b); + + return output; +} + + + +////////////////////////////// +// +// PixelColor::rgb2hsi -- convert from RGB color space to HSI color space. +// You have to keep track of color space used by pixel since RGB/HSI +// state is not stored in pixel. +// + +PixelColor& PixelColor::rgb2hsi(void) { + + // Convert RGB into range from 0 to 255.0: + double R = Red / 255.0; + double G = Green / 255.0; + double B = Blue / 255.0; + + // HSI will be in the range from 0.0 to 1.0; + double H = 0.0; // will be stored in Red parameter + double S = 0.0; // will be stored in Green parameter + double I = 0.0; // will be stored in Blue parameter + + double min = R; + if (G < min) min = G; + if (B < min) min = B; + + I = (R+G+B)/3.0; + S = 1 - min/I; + if (S == 0.0) { + H = 0.0; + } else { + H = ((R-G)+(R-B))/2.0; + H = H/sqrt((R-G)*(R-G) + (R-B)*(G-B)); + H = acos(H); + if (B > G) { + H = 2*M_PI - H; + } + H = H/(2*M_PI); + } + + // Adjust output range from 0 to 255: + int h = (int)(H * 255.0 + 0.5); + if (h < 0) { h = 0; } + if (h > 255) { h = 255; } + + int s = (int)(S * 255.0 + 0.5); + if (s < 0) { s = 0; } + if (s > 255) { s = 255; } + + int i = (int)(I * 255.0 + 0.5); + if (i < 0) { i = 0; } + if (i > 255) { i = 255; } + + Red = h; + Green = s; + Blue = i; + + return *this; +} + + + +////////////////////////////// +// +// PixelColor::hsi2rgb -- convert from HSI color space to RGB color space. +// + +PixelColor& PixelColor::hsi2rgb(void) { + + // Scale input HSI into the range from 0.0 to 1.0: + double H = Red / 255.0; + double S = Green / 255.0; + double I = Blue / 255.0; + + double R = 0.0; + double G = 0.0; + double B = 0.0; + + if (H < 1.0/3.0) { + B = (1-S)/3; + R = (1+S*cos(2*M_PI*H)/cos(M_PI/3-2*M_PI*H))/3.0; + G = 1 - (B + R); + } else if (H < 2.0/3.0) { + H = H - 1.0/3.0; + R = (1-S)/3; + G = (1+S*cos(2*M_PI*H)/cos(M_PI/3-2*M_PI*H))/3.0; + B = 1 - (R+G); + } else { + H = H - 2.0/3.0; + G = (1-S)/3; + B = (1+S*cos(2*M_PI*H)/cos(M_PI/3-2*M_PI*H))/3.0; + R = 1 - (G+B); + } + + // Adjust output range from 0 to 255: + int r = (int)(I * R * 3.0 * 255.0 + 0.5); + if (r < 0) { r = 0; } + if (r > 255) { r = 255; } + + int g = (int)(I * G * 3.0 * 255.0 + 0.5); + if (g < 0) { g = 0; } + if (g > 255) { g = 255; } + + int b = (int)(I * B * 3.0 * 255.0 + 0.5); + if (b < 0) { b = 0; } + if (b > 255) { b = 255; } + + Red = r; + Green = g; + Blue = b; + + return *this; +} + + + +////////////////////////////// +// +// PixelColor::getHsi -- convert from RGB color space to HSI color space. +// You have to keep track of color space used by pixel since RGB/HSI +// state is not stored in pixel. +// + +PixelColor PixelColor::getHsi(void) { + PixelColor tempColor = *this; + tempColor.rgb2hsi(); + return tempColor; +} + + + +////////////////////////////// +// +// PixelColor::getRgb -- convert from HSI color space to RGB color space. +// You have to keep track of color space used by pixel since RGB/HSI +// state is not stored in pixel. +// + +PixelColor PixelColor::getRgb(void) { + PixelColor tempColor = *this; + tempColor.hsi2rgb(); + return tempColor; +} + + +////////////////////////////// +// +// PixelColor::getHexColor -- +// + +string PixelColor::getHexColor(void) { + string output = "#"; + unsigned char redA = (Red & 0xF0) >> 4; + unsigned char redB = (Red & 0x0F); + unsigned char greenA = (Green & 0xF0) >> 4; + unsigned char greenB = (Green & 0x0F); + unsigned char blueA = (Blue & 0xF0) >> 4; + unsigned char blueB = (Blue & 0x0F); + + if (redA < 10) { + output += '0' + redA; + } else { + output += 'A' + redA - 10; + } + if (redB < 10) { + output += '0' + redB; + } else { + output += 'A' + redB - 10; + } + + if (greenA < 10) { + output += '0' + greenA; + } else { + output += 'A' + greenA - 10; + } + if (greenB < 10) { + output += '0' + greenB; + } else { + output += 'A' + greenB - 10; + } + + if (blueA < 10) { + output += '0' + blueA; + } else { + output += 'A' + blueA - 10; + } + if (blueB < 10) { + output += '0' + blueB; + } else { + output += 'A' + blueB - 10; + } + + return output; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// other functions +// + + +////////////////////////////// +// +// operator<< -- +// +// for use with P3 ASCII pnm images: print red green blue triplet. +// + +ostream& operator<<(ostream& out, PixelColor apixel) { + out << apixel.getRed() << ' '; + out << apixel.getGreen() << ' '; + out << apixel.getBlue(); + return out; +} + + + + +///////////////////////////////// +// +// Tool_addic::Tool_addic -- Set the recognized options for the tool. +// + +Tool_addic::Tool_addic(void) { + define("f|fix=b", "Fix instrument class values if different from expected for instrument code."); +} + + + +///////////////////////////////// +// +// Tool_addic::run -- Do the main work of the tool. +// + +bool Tool_addic::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; isetText(text); + } + infile[classIndex].createLineFromTokens(); } ////////////////////////////// // -// PixelColor::writePpm6 -- write the pixel in PPM 6 format. +// Tool_addic::getInstrumentCodeIndex -- // -void PixelColor::writePpm6(ostream& out) { - out << (unsigned char)getRed() << (unsigned char)getGreen() << (unsigned char)getBlue(); +int Tool_addic::getInstrumentCodeIndex(HumdrumFile& infile) { + HumRegex hre; + for (int i=0; isetText(text); + } + + HumRegex hre; + string keyDesig = "*" + m_key; + if (!hre.search(m_key, ":")) { + keyDesig += ":"; + } + insertKeyDesig(infile, keyDesig); + + // Update the reference key record if -K option is used: + if (m_addKeyRefQ) { + if (m_refKeyIndex != -1) { + string text = "!!!key: " + m_key; + infile[m_refKeyIndex].setText(text); + } + // Or print just before exinterp line later if not found, + // but needs to be created. + } } + ////////////////////////////// // -// limit -- +// Tool_addkey::insertKeyDesig -- // -int PixelColor::limit(int value, int min, int max) { - if (value < min) { - value = min; - } else if (value > max) { - value = max; +void Tool_addkey::insertKeyDesig(HumdrumFile& infile, const string& keyDesig) { + // Replace the key designation if any are found in the header. + // If not found, then store in key signature for printing later. + for (int i=0; i= m_dataStartIndex) { + break; + } + if (!infile[i].isInterpretation()) { + continue; + } + for (int j=0; jisKeyDesignation()) { + token->setText(keyDesig); + } else if ((m_keyDesigIndex == -1) && (token->isKeySignature())) { + // Store keyDesig later to print: + token->setValue("auto", "keyDesig", keyDesig); + } + } } - return value; } ////////////////////////////// // -// PixelColor:mix -- mix two colors together. +// Tool_addkey::insertReferenceKey -- Take the !!!key: value and insert +// into key designations in the header. Add key designation line if not +// present already in header. // -PixelColor PixelColor::mix(PixelColor& color1, PixelColor& color2) { - - PixelColor p1 = color1.getHsi(); - PixelColor p2 = color2.getHsi(); - - PixelColor output; - unsigned int r = ((unsigned int)color1.Red + (unsigned int)color2.Red)/2; - unsigned int g = ((unsigned int)color1.Green + (unsigned int)color2.Green)/2; - unsigned int b = ((unsigned int)color1.Blue + (unsigned int)color2.Blue)/2; +void Tool_addkey::insertReferenceKey(HumdrumFile& infile) { + getLineIndexes(infile); - output.setRed(r); - output.setGreen(g); - output.setBlue(b); + if (m_refKeyIndex == -1) { + // Nothing to do, add later before exinterp line. + return; + } - return output; + HumRegex hre; + string keyValue = infile[m_refKeyIndex].getReferenceValue(); + if (!hre.search(keyValue, ":")) { + keyValue += ":"; + } + if (!hre.search(keyValue, "^\\*")) { + hre.replaceDestructive(keyValue, "*", "^"); + } + if (m_keyDesigIndex > 0) { + for (int i=m_exinterpIndex+1; i<=m_keyDesigIndex; i++) { + if (!infile[i].isInterpretation()) { + continue; + } + for (int j=0; jisKeyDesignation()) { + continue; + } + string text = "*" + keyValue; + token->setText(text); + } + } + infile.generateLinesFromTokens(); + m_humdrum_text << infile; + } else if (m_keySigIndex > 0) { + printKeyDesig(infile, m_keySigIndex, keyValue, +1); + } else if (m_dataStartIndex > 0) { + printKeyDesig(infile, m_dataStartIndex, keyValue, -1); + } } ////////////////////////////// // -// PixelColor::rgb2hsi -- convert from RGB color space to HSI color space. -// You have to keep track of color space used by pixel since RGB/HSI -// state is not stored in pixel. +// Tool_addkey::printKeyDesig -- // -PixelColor& PixelColor::rgb2hsi(void) { +void Tool_addkey::printKeyDesig(HumdrumFile& infile, int index, const string& desig, int direction) { + int index2 = index + direction; + for (int i=0; i index2) { + m_humdrum_text << infile[i] << endl; + } + for (int j=0; j 0) { + m_humdrum_text << "\t"; + } + if (token->isKern()) { + m_humdrum_text << desig; + } else { + m_humdrum_text << "*"; + } + } + m_humdrum_text << endl; + if (index < index2) { + m_humdrum_text << infile[i] << endl; + } + } + } +} - // Convert RGB into range from 0 to 255.0: - double R = Red / 255.0; - double G = Green / 255.0; - double B = Blue / 255.0; - // HSI will be in the range from 0.0 to 1.0; - double H = 0.0; // will be stored in Red parameter - double S = 0.0; // will be stored in Green parameter - double I = 0.0; // will be stored in Blue parameter - double min = R; - if (G < min) min = G; - if (B < min) min = B; +////////////////////////////// +// +// Tool_addkey::getLineIndexes -- +// - I = (R+G+B)/3.0; - S = 1 - min/I; - if (S == 0.0) { - H = 0.0; - } else { - H = ((R-G)+(R-B))/2.0; - H = H/sqrt((R-G)*(R-G) + (R-B)*(G-B)); - H = acos(H); - if (B > G) { - H = 2*M_PI - H; +void Tool_addkey::getLineIndexes(HumdrumFile& infile) { + m_refKeyIndex = -1; + m_keyDesigIndex = -1; + m_keySigIndex = -1; + m_dataStartIndex = -1; + + for (int i=0; icompare(0, 7, "!!!key:") == 0) { + m_refKeyIndex = i; + } + if (!infile[i].isInterpretation()) { + continue; + } + for (int j=0; jisKeySignature()) { + m_keySigIndex = i; + } else if (token->isKeyDesignation()) { + m_keyDesigIndex = i; + } } - H = H/(2*M_PI); } - // Adjust output range from 0 to 255: - int h = (int)(H * 255.0 + 0.5); - if (h < 0) { h = 0; } - if (h > 255) { h = 255; } + if (m_refKeyIndex == -1) { + // !!!key: could be at bottom, so search backwards in file. + for (int i=infile.getLineCount() - 1; i>=0; i--) { + if (!infile[i].isReference()) { + continue; + } + string key = infile[i].getReferenceKey(); + if (key == "key") { + m_refKeyIndex = i; + break; + } + } + } +} - int s = (int)(S * 255.0 + 0.5); - if (s < 0) { s = 0; } - if (s > 255) { s = 255; } - int i = (int)(I * 255.0 + 0.5); - if (i < 0) { i = 0; } - if (i > 255) { i = 255; } - Red = h; - Green = s; - Blue = i; - return *this; +///////////////////////////////// +// +// Tool_addlabels::Tool_addlabels -- Set the recognized options for the tool. +// + +Tool_addlabels::Tool_addlabels(void) { + define("d|default=s:", "Default expansion list"); + define("r|norep=s:", "norep expansion list"); + define("l|labels=s:", "List of labels to insert"); } -////////////////////////////// +///////////////////////////////// // -// PixelColor::hsi2rgb -- convert from HSI color space to RGB color space. +// Tool_addlabels::run -- Do the main work of the tool. // -PixelColor& PixelColor::hsi2rgb(void) { +bool Tool_addlabels::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i 255) { r = 255; } - int g = (int)(I * G * 3.0 * 255.0 + 0.5); - if (g < 0) { g = 0; } - if (g > 255) { g = 255; } +bool Tool_addlabels::run(HumdrumFile& infile) { + processFile(infile); + return true; +} - int b = (int)(I * B * 3.0 * 255.0 + 0.5); - if (b < 0) { b = 0; } - if (b > 255) { b = 255; } - Red = r; - Green = g; - Blue = b; - return *this; +////////////////////////////// +// +// Tool_addlabels::initialize -- Process input options. +// + +void Tool_addlabels::initialize(void) { + m_default = getString("default"); + m_norep = getString("norep"); + m_zeroth.clear(); + + HumRegex hre; + if (!m_default.empty()) { + if (!hre.search(m_default, "^\\[")) { + m_default = "[" + m_default; + } + if (!hre.search(m_default, "\\]$")) { + m_default += "]"; + } + } + if (!m_norep.empty()) { + if (!hre.search(m_norep, "^\\[")) { + m_norep = "[" + m_norep; + } + if (!hre.search(m_norep, "\\]$")) { + m_norep += "]"; + } + } + + string value = getString("labels"); + hre.replaceDestructive(value, "", "^[\\s;,]+"); + hre.replaceDestructive(value, "", "[\\s;,]+$"); + + vector pieces; + hre.split(pieces, value, "\\s*[;,]\\s*"); + for (int i=0; i<(int)pieces.size(); i++) { + if (hre.search(pieces[i], "^\\s$")) { + continue; + } + if (hre.search(pieces[i], "^\\s*m?\\s*(\\d+)([a-z]?)\\s*:\\s*(.+)\\s*$")) { + int barnum = hre.getMatchInt(1); + string sub = hre.getMatch(2); + int subbar = 0; + if (!sub.empty()) { + subbar = sub[0] - 'a'; + } + string label = hre.getMatch(3); + + if ((barnum <= 0) && (subbar <= 0)) { + m_zeroth = label; + continue; + } + m_barnums.push_back(barnum); + m_subbarnums.push_back(subbar); + m_labels.push_back(label); + } else { + cerr << "Error parsing label (ignoring): " << pieces[i] << endl; + } + } } ////////////////////////////// // -// PixelColor::getHsi -- convert from RGB color space to HSI color space. -// You have to keep track of color space used by pixel since RGB/HSI -// state is not stored in pixel. +// Tool_addlabels::processFile -- // -PixelColor PixelColor::getHsi(void) { - PixelColor tempColor = *this; - tempColor.rgb2hsi(); - return tempColor; +void Tool_addlabels::processFile(HumdrumFile& infile) { + initialize(); + + vector llist; + assignLabels(llist, infile); + + m_defaultIndex = getExpansionIndex(infile); + + for (int i=0; i" << llist.at(i); + if (j < infile[i].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; + } + } } ////////////////////////////// // -// PixelColor::getRgb -- convert from HSI color space to RGB color space. -// You have to keep track of color space used by pixel since RGB/HSI -// state is not stored in pixel. +// Tool_addlabels::getExpsnsionIndex -- Return index that is where the +// expansion labels and 0th label should be printed ABOVE. // -PixelColor PixelColor::getRgb(void) { - PixelColor tempColor = *this; - tempColor.hsi2rgb(); - return tempColor; +int Tool_addlabels::getExpansionIndex(HumdrumFile& infile) { + int staffIndex = -1; + int partIndex = -1; + int groupIndex = -1; + int instIndex = -1; + int abbrIndex = -1; + int clefIndex = -1; + int keySigIndex = -1; + int keyDesigIndex = -1; + int exIndex = -1; + + for (int i=0; iisClef()) { + clefIndex = i; + } + if ((instIndex < 0) && token->compare(0, 3, "*I\"") == 0) { + instIndex = i; + } + if ((abbrIndex < 0) && token->compare(0, 3, "*I\"") == 0) { + abbrIndex = i; + } + if ((keySigIndex < 0) && token->isKeySignature()) { + keySigIndex = i; + } + if ((keyDesigIndex < 0) && token->isKeyDesignation()) { + keyDesigIndex = i; + } + if ((staffIndex != i) && (token->compare(0, 6, "*staff") == 0)) { + staffIndex = i; + } + if ((partIndex != i) && (token->compare(0, 5, "*part") == 0)) { + partIndex = i; + } + if ((groupIndex != i) && (token->compare(0, 6, "*group") == 0)) { + groupIndex = i; + } + } + } + + int spigaIndex = staffIndex; + if (partIndex > spigaIndex) { + spigaIndex = partIndex; + } + if (groupIndex > spigaIndex) { + spigaIndex = groupIndex; + } + if (instIndex > spigaIndex) { + spigaIndex = instIndex; + } + if (abbrIndex > spigaIndex) { + spigaIndex = abbrIndex; + } + + if (spigaIndex > 0) { + return spigaIndex + 1; + } + + int tindex = -1; + + if ((clefIndex > 0) && (tindex > 0)) { + tindex = clefIndex; + } + + if ((keySigIndex > 0) && (tindex > 0)) { + if (keySigIndex < tindex) { + tindex = keySigIndex; + } + } + + if ((keyDesigIndex > 0) && (tindex > 0)) { + if (keyDesigIndex < tindex) { + tindex = keyDesigIndex; + } + } + if (tindex > 0) { + if (exIndex < tindex - 1) { + return tindex; + } + } + return exIndex + 1; } + ////////////////////////////// // -// PixelColor::getHexColor -- +// Tool_addlabels::printExpansionLists -- printing above given line index +// But use field count of next spined line in data if target line is +// unspined. // -string PixelColor::getHexColor(void) { - string output = "#"; - unsigned char redA = (Red & 0xF0) >> 4; - unsigned char redB = (Red & 0x0F); - unsigned char greenA = (Green & 0xF0) >> 4; - unsigned char greenB = (Green & 0x0F); - unsigned char blueA = (Blue & 0xF0) >> 4; - unsigned char blueB = (Blue & 0x0F); - - if (redA < 10) { - output += '0' + redA; - } else { - output += 'A' + redA - 10; +void Tool_addlabels::printExpansionLists(HumdrumFile& infile, int index) { + int ii = -1; + for (int i=index; i" << m_default; + if (j < infile[ii].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; } - if (blueA < 10) { - output += '0' + blueA; - } else { - output += 'A' + blueA - 10; - } - if (blueB < 10) { - output += '0' + blueB; - } else { - output += 'A' + blueB - 10; + if (!m_norep.empty()) { + for (int j=0; jnorep" << m_norep; + if (j < infile[ii].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; } - return output; + if (!m_zeroth.empty()) { + for (int j=0; j" << m_zeroth; + if (j < infile[ii].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; + } } -/////////////////////////////////////////////////////////////////////////// + +////////////////////////////// // -// other functions +// Tool_addlabels::assignLabels -- assign labels to specific lines. // +void Tool_addlabels::assignLabels(vector& llist, HumdrumFile& infile) { + llist.resize(infile.getLineCount()); + for (int i=0; i<(int)m_barnums.size(); i++) { + addLabel(llist, infile, m_barnums.at(i), m_subbarnums.at(i), m_labels.at(i)); + } +} + + ////////////////////////////// // -// operator<< -- -// -// for use with P3 ASCII pnm images: print red green blue triplet. +// Tool_addlabels::addLabel -- Add specified tempo to list. // -ostream& operator<<(ostream& out, PixelColor apixel) { - out << apixel.getRed() << ' '; - out << apixel.getGreen() << ' '; - out << apixel.getBlue(); - return out; +void Tool_addlabels::addLabel(vector& llist, HumdrumFile& infile, + int barnum, int subbarnum, const string& label) { + + if (barnum <= 0) { + return; + } + + // find barnum index: + int barIndex = -1; + for (int i=0; i 0) { + for (int i=barIndex + 1; i= subbarnum) { + barIndex = i; + break; + } + } + } + + if (barIndex < 0) { + return; + } + + // insert at the next spined line (but figure that out later): + llist.at(barIndex) = label; } @@ -52342,21 +53666,21 @@ ostream& operator<<(ostream& out, PixelColor apixel) { ///////////////////////////////// // -// Tool_addic::Tool_addic -- Set the recognized options for the tool. +// Tool_addtempo::Tool_addtempo -- Set the recognized options for the tool. // -Tool_addic::Tool_addic(void) { - define("f|fix=b", "Fix instrument class values if different from expected for instrument code."); +Tool_addtempo::Tool_addtempo(void) { + define("q|quarter-notes-per-minute=s:120", "Quarter notes per minute (or list by measure)"); } ///////////////////////////////// // -// Tool_addic::run -- Do the main work of the tool. +// Tool_addtempo::run -- Do the main work of the tool. // -bool Tool_addic::run(HumdrumFileSet& infiles) { +bool Tool_addtempo::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i pieces; + hre.split(pieces, value, "\\s*;\\s*"); + for (int i=0; i<(int)pieces.size(); i++) { + if (hre.search(pieces[i], "^\\s$")) { + continue; + } + if (hre.search(pieces[i], "^\\s*m\\s*(\\d+)\\s*:\\s*([\\d.]+)\\s*$")) { + int measure = hre.getMatchInt(1); + double tempo = hre.getMatchDouble(2); + m_tempos.emplace_back(measure, tempo); + } else if (hre.search(pieces[i], "^\\s*([\\d.]+)\\s*$")) { + int measure = 0; + double tempo = hre.getMatchDouble(1); + m_tempos.emplace_back(measure, tempo); + } + } + + auto compareByFirst = [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }; + std::sort(m_tempos.begin(), m_tempos.end(), compareByFirst); } ////////////////////////////// // -// Tool_addic::processFile -- +// Tool_addtempo::processFile -- // -void Tool_addic::processFile(HumdrumFile& infile) { +void Tool_addtempo::processFile(HumdrumFile& infile) { + initialize(); - int codeIndex = getInstrumentCodeIndex(infile); - int classIndex = getInstrumentClassIndex(infile); + vector tlist; + assignTempoChanges(tlist, infile); - if (!codeIndex) { - // No code index, so nothing to do. - m_humdrum_text << infile; - } - if (classIndex) { - // Instrument class line already exists so adjust it: - updateInstrumentClassLine(infile, codeIndex, classIndex); - m_humdrum_text << infile; - } else { - string classLine = makeClassLine(infile, codeIndex); - for (int i=0; i 0.0) { + for (int j=0; jisKern()) { + m_humdrum_text << "*MM" << tlist.at(i); + } else { + m_humdrum_text << "*"; + } + if (j < infile[i].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } } - m_humdrum_text << infile[i] << endl; + m_humdrum_text << endl; } + m_humdrum_text << infile[i] << endl; } } - ////////////////////////////// // -// Tool_addic::makeClassLine -- +// Tool_addtempo::assignTempoChanges -- add non-zero +// tempo when it should change. // -string Tool_addic::makeClassLine(HumdrumFile& infile, int codeIndex) { - string output; - HumRegex hre; - int count = infile[codeIndex].getFieldCount(); - for (int i=0; i& tlist, HumdrumFile& infile) { + tlist.resize(infile.getLineCount()); + std::fill(tlist.begin(), tlist.end(), 0.0); + for (int i=0; i<(int)m_tempos.size(); i++) { + addTempo(tlist, infile, m_tempos[i].first, m_tempos[i].second); } - return output; } ////////////////////////////// // -// Tool_addic::updateInstrumentClassLine -- +// Tool_addtempo::addTempo -- Add specified tempo to list. // -void Tool_addic::updateInstrumentClassLine(HumdrumFile& infile, int codeIndex, - int classIndex) { +void Tool_addtempo::addTempo(vector& tlist, HumdrumFile& infile, + int measure, double tempo) { - int codeSize = infile[codeIndex].getFieldCount(); - int classSize = infile[classIndex].getFieldCount(); - if (codeSize != classSize) { - cerr << "Instrument code line length does not match that of class line" << endl; + if (measure == 0) { + addTempoToStart(tlist, infile, tempo); return; } - HumRegex hre; - for (int i=0; isetText(text); } - infile[classIndex].createLineFromTokens(); -} - - - -////////////////////////////// -// -// Tool_addic::getInstrumentCodeIndex -- -// - -int Tool_addic::getInstrumentCodeIndex(HumdrumFile& infile) { - HumRegex hre; - for (int i=0; iisKern()) { continue; } - if (hre.search(token, "^\\*I[a-z]")) { - return i; + if (token->isTimeSignature()) { + sigIndex = i; + } else if (token->isMetricSymbol()) { + symIndex = i; } } } - return 0; + + if (dataIndex < 0) { + return; + } + + if ((sigIndex >= 0) && (symIndex >= 0)) { + if (sigIndex > symIndex) { + tlist.at(sigIndex+1) = tempo; + } else { + tlist.at(symIndex+1) = tempo; + } + return; + } else if (sigIndex >= 0) { + tlist.at(sigIndex+1) = tempo; + return; + } else if (symIndex >= 0) { + return; + } else if (dataIndex >= 0) { + int localIndex = dataIndex - 1; + int lastSpineIndex = localIndex; + if (infile[dataIndex-1].isLocalComment()) { + while (infile[localIndex].isLocalComment() || !infile[localIndex].hasSpines()) { + if (!infile[localIndex].hasSpines()) { + localIndex--; + continue; + } else { + lastSpineIndex = localIndex; + } + if (infile[localIndex].isLocalComment()) { + lastSpineIndex = localIndex; + localIndex--; + continue; + } + } + tlist.at(lastSpineIndex) = tempo; + return; + } else { + tlist.at(dataIndex) = tempo; + } + } + } ////////////////////////////// // -// Tool_addic::getInstrumentClassIndex -- +// Tool_addtempo::addTempoToStart -- // -int Tool_addic::getInstrumentClassIndex(HumdrumFile& infile) { - HumRegex hre; +void Tool_addtempo::addTempoToStart(vector& tlist, + HumdrumFile& infile, double tempo) { + + // find first measure and data line indexes: + int barIndex = -1; + int dataIndex = -1; + int sigIndex = -1; + int symIndex = -1; + for (int i=0; iisKern()) { continue; } - if (hre.search(token, "^\\*IC[a-z]")) { - return i; + if (token->isTimeSignature()) { + sigIndex = i; + } else if (token->isMetricSymbol()) { + symIndex = i; } } } - return 0; -} - - - -////////////////////////////// -// -// Tool_addic::getInstrumentClass -- -// -string Tool_addic::getInstrumentClass(const string& code) { - HumRegex hre; - string code1 = code; - string code2; - string divider; - int count = 1; - if (hre.search(code, "([^I]+)([&|])I(.*)")) { - count = 2; - code1 = hre.getMatch(1); - divider = hre.getMatch(2); - code2 = hre.getMatch(3); + if (dataIndex < 0) { + return; } - string class1 = ""; - string class2 = ""; - - for (int i=0; i<(int)m_instrumentList.size(); i++) { - if (code1 == m_instrumentList[i].first) { - class1 = m_instrumentList[i].second; + if ((sigIndex >= 0) && (symIndex >= 0)) { + if (sigIndex > symIndex) { + tlist.at(sigIndex+1) = tempo; + } else { + tlist.at(symIndex+1) = tempo; } - if (count == 2) { - if (code2 == m_instrumentList[i].first) { - class2 = m_instrumentList[i].second; + return; + } else if (sigIndex >= 0) { + tlist.at(sigIndex+1) = tempo; + return; + } else if (symIndex >= 0) { + return; + } else if (dataIndex >= 0) { + if (infile[dataIndex-1].isLocalComment()) { + int localIndex = dataIndex - 1; + int lastSpineIndex = localIndex; + while (infile[localIndex].isLocalComment() || !infile[localIndex].hasSpines()) { + if (!infile[localIndex].hasSpines()) { + localIndex--; + continue; + } else { + lastSpineIndex = localIndex; + } + if (infile[localIndex].isLocalComment()) { + lastSpineIndex = localIndex; + localIndex--; + continue; + } } + tlist.at(lastSpineIndex) = tempo; + return; + } else { + tlist.at(dataIndex) = tempo; } } - if (count == 1) { - if (class1 == "") { - return "UNKNOWN" + code1; - } - } else { - if ((class1 == "") && (class2 == "")) { - return "UNKNOWN" + code1; - } - } - - if (count == 1) { - return class1; - } - - if (class1 == class2) { - return class1; - } - - // return two instrument classes: - return class1 + divider + "IC" + class2; } + ///////////////////////////////// // // Tool_autoaccid::Tool_autoaccid -- Set the recognized options for the tool. @@ -79855,6 +81200,12 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { for (int i=0; i<(int)commands.size(); i++) { if (commands[i].first == "addic") { RUNTOOL(addic, infile, commands[i].second, status); + } else if (commands[i].first == "addkey") { + RUNTOOL(addkey, infile, commands[i].second, status); + } else if (commands[i].first == "addlabels") { + RUNTOOL(addlabels, infile, commands[i].second, status); + } else if (commands[i].first == "addtempo") { + RUNTOOL(addtempo, infile, commands[i].second, status); } else if (commands[i].first == "autoaccid") { RUNTOOL(autoaccid, infile, commands[i].second, status); } else if (commands[i].first == "autobeam") { @@ -79884,8 +81235,6 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { } else if (commands[i].first == "filter") { RUNTOOL(filter, infile, commands[i].second, status); } else if (commands[i].first == "gasparize") { - RUNTOOL(grep, infile, commands[i].second, status); - } else if (commands[i].first == "grep") { RUNTOOL(gasparize, infile, commands[i].second, status); } else if (commands[i].first == "half") { RUNTOOL(half, infile, commands[i].second, status); @@ -80005,6 +81354,11 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { } else if (commands[i].first == "extractx") { // humlib cli name RUNTOOL(extract, infile, commands[i].second, status); + } else if (commands[i].first == "grep") { + RUNTOOL(grep, infile, commands[i].second, status); + } else if (commands[i].first == "humgrep") { + RUNTOOL(grep, infile, commands[i].second, status); + } else if (commands[i].first == "myank") { // humlib version of Humdrum Extras myank tool RUNTOOL(myank, infile, commands[i].second, status); } else if (commands[i].first == "myankx") { // humlib cli name @@ -80022,8 +81376,6 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { } else if (commands[i].first == "satb2gsx") { // humlib cli name RUNTOOL(satb2gs, infile, commands[i].second, status); - } else if (commands[i].first == "thru") { - RUNTOOL(thru, infile, commands[i].second, status); } else if (commands[i].first == "thru") { // humlib version of Humdrum Toolkit thru tool RUNTOOL(thru, infile, commands[i].second, status); } else if (commands[i].first == "thrux") { // Humdrum Extras cli name @@ -80035,9 +81387,10 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(timebase, infile, commands[i].second, status); } else if (commands[i].first == "timebasex") { // humlib cli name RUNTOOL(timebase, infile, commands[i].second, status); + } else { + cerr << "UNKNOWN FILTER: " << commands[i].first << " OPTIONS: " << commands[i].second << endl; } - } removeGlobalFilterLines(infile); @@ -82604,7 +83957,7 @@ bool Tool_grep::run(HumdrumFile& infile) { // void Tool_grep::initialize(void) { - m_negateQ = !getBoolean("remove-matching-lines"); + m_negateQ = getBoolean("remove-matching-lines"); m_regex = getString("regular-expression"); } @@ -98413,9 +99766,10 @@ Tool_musedata2hum::Tool_musedata2hum(void) { // Options& options = m_options; // options.define("k|kern=b","display corresponding **kern data"); - define("g|group=s:score", "the data group to process"); - define("r|recip=b", "output **recip spine"); - define("s|stems=b", "include stems in output"); + define("g|group=s:score", "The data group to process"); + define("r|recip=b", "Output **recip spine"); + define("s|stems=b", "Include stems in output"); + define("omv|no-omv=b", "Exclude extracted OMV record in output data"); } @@ -98429,6 +99783,7 @@ void Tool_musedata2hum::initialize(void) { m_stemsQ = getBoolean("stems"); m_recipQ = getBoolean("recip"); m_group = getString("group"); + m_noOmvQ = getBoolean("no-omv"); } @@ -98508,7 +99863,6 @@ bool Tool_musedata2hum::convert(ostream& out, MuseDataSet& mds) { initialize(); m_tempo = mds.getMidiTempo(); -cerr << "TEMPO " << m_tempo << endl; vector groupMemberIndex = mds.getGroupIndexList(m_group); if (groupMemberIndex.empty()) { @@ -98524,9 +99878,12 @@ cerr << "TEMPO " << m_tempo << endl; HumdrumFile outfile; outdata.transferTokens(outfile); + if (needsAboveBelowKernRdf()) { + outfile.appendLine("!!!RDF**kern: > = above"); + outfile.appendLine("!!!RDF**kern: < = above"); + } outfile.createLinesFromTokens(); - // Convert comments in header of first part: int ii = groupMemberIndex[0]; bool ending = false; @@ -98581,10 +99938,12 @@ cerr << "TEMPO " << m_tempo << endl; } } - if (!m_usedReferences["OMV"]) { - string movementtitle = mds[ii].getMovementTitle(); - if (!movementtitle.empty()) { - out << "!!!OMV: " << movementtitle << endl; + if (!m_noOmvQ) { + if (!m_usedReferences["OMV"]) { + string movementtitle = mds[ii].getMovementTitle(); + if (!movementtitle.empty()) { + out << "!!!OMV: " << movementtitle << endl; + } } } @@ -98608,7 +99967,41 @@ cerr << "TEMPO " << m_tempo << endl; } } - out << outfile; + bool foundDataQ = false; + for (int i=0; i lo(line.getFieldCount()); + int count = 0; + for (int i=0; igetValue("auto", "LO"); + if (!value.empty()) { + lo.at(i) = value; + count++; + } + } + if (count > 0) { + for (int i=0; i<(int)lo.size(); i++) { + if (lo[i].empty()) { + out << "!"; + } else { + out << lo[i]; + } + if (i < (int)lo.size() - 1) { + out << "\t"; + } + } + out << endl; + } + out << line << endl; +} + + + ////////////////////////////// // // Tool_musedata2hum::convertPart -- @@ -98735,10 +100165,6 @@ int Tool_musedata2hum::convertMeasure(HumGrid& outdata, MuseData& part, int part } GridMeasure* gm = getMeasure(outdata, starttime); - setMeasureNumber(outdata[(int)outdata.size() - 1], part[startindex]); - if (partindex == 0) { - gm->setBarStyle(MeasureStyle::Plain); - } int i = startindex; for (i=startindex; isetBarStyle(MeasureStyle::Plain); } } @@ -98798,6 +100226,7 @@ void Tool_musedata2hum::setMeasureNumber(GridMeasure* gm, MuseRecord& mr) { } } if (pos < 0) { + gm->setMeasureNumber(-1); return; } int num = stoi(line.substr(pos)); @@ -98817,9 +100246,8 @@ void Tool_musedata2hum::setMeasureNumber(GridMeasure* gm, MuseRecord& mr) { // void Tool_musedata2hum::setMeasureStyle(GridMeasure* gm, MuseRecord& mr) { - // Add bar numbers as well. string line = mr.getLine(); - string barstyle = mr.getMeasureFlagsString(); + string barstyle = mr.getMeasureFlags(); if (line.compare(0, 7, "mheavy2") == 0) { if (barstyle.find(":|") != string::npos) { gm->setStyle(MeasureStyle::RepeatBackward); @@ -98833,6 +100261,9 @@ void Tool_musedata2hum::setMeasureStyle(GridMeasure* gm, MuseRecord& mr) { } else if (line.compare(0, 7, "mheavy4") == 0) { if (barstyle.find(":|:") != string::npos) { gm->setStyle(MeasureStyle::RepeatBoth); + } else if (barstyle.find("|: :|") != string::npos) { + // Vivaldi op. 1, no. 1, mvmt. 1, m. 10: mheavy4 |: :| + gm->setStyle(MeasureStyle::RepeatBoth); } } else if (line.compare(0, 7, "mdouble") == 0) { gm->setStyle(MeasureStyle::Double); @@ -98855,13 +100286,18 @@ void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { layer = layer - 1; } + if (mr.isDirection()) { + return; + } + HumNum timestamp = mr.getAbsBeat(); // cerr << "CONVERTING LINE " << timestamp << "\t" << mr << endl; string tok; GridSlice* slice = NULL; if (mr.isBarline()) { - tok = mr.getKernMeasureStyle(); + // barline handled elsewhere + // tok = mr.getKernMeasure(); } else if (mr.isAttributes()) { map attributes; mr.getAttributeMap(attributes); @@ -98907,16 +100343,31 @@ void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { } } else if (mr.isRegularNote()) { tok = mr.getKernNoteStyle(1, 1); + string other = mr.getKernNoteOtherNotations(); + if (!needsAboveBelowKernRdf()) { + if (other.find("<") != string::npos) { + addAboveBelowKernRdf(); + } else if (other.find(">") != string::npos) { + addAboveBelowKernRdf(); + } + } + if (!other.empty()) { + tok += other; + } slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff); if (slice) { mr.setVoice(slice->at(part)->at(staff)->at(layer)); string gr = mr.getLayoutVis(); if (gr.size() > 0) { - cerr << "GRAPHIC VERSION OF NOTEA " << gr << endl; + // Visual and performance durations are not equal: + HTp token = slice->at(part)->at(staff)->at(layer)->getToken(); + string text = "!LO:N:vis=" + gr; + token->setValue("auto", "LO", text); } } m_lastnote = slice->at(part)->at(staff)->at(layer)->getToken(); addNoteDynamics(slice, part, mr); + addDirectionDynamics(slice, part, mr); addLyrics(slice, part, staff, mr); } else if (mr.isFiguredHarmony()) { addFiguredHarmony(mr, gm, timestamp, part, maxstaff); @@ -98947,7 +100398,6 @@ void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { } } } else if (mr.isDirection()) { - cerr << "PROCESS DIRECTION HERE: " << mr << endl; if (mr.isTextDirection()) { addTextDirection(gm, part, staff, mr, timestamp); } @@ -98955,6 +100405,64 @@ void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { } +////////////////////////////// +// +// Tool_musedata2hum::addDirectionDynamics -- search for a dynamic +// marking before the current line and after any previous note +// or similar line. These lines are store in "musical directions" +// which start the line with a "*" character. +// +// Example for "p" dyamic, with print suggesting. +// 1 2 +// 12345678901234567890123456789 +// * G p +// P C17:Y57 +// + +void Tool_musedata2hum::addDirectionDynamics(GridSlice* slice, int part, MuseRecord& mr) { + MuseRecord* direction = mr.getMusicalDirection(); + if (direction == NULL) { + return; + } + + if (direction->isDynamic()) { + string dynamicText = direction->getDynamicText(); + if (!dynamicText.empty()) { + slice->at(part)->setDynamics(dynamicText); + HumGrid* grid = slice->getOwner(); + if (grid) { + grid->setDynamicsPresent(part); + } + } + } +} + + + +////////////////////////////// +// +// Tool_musedata2hum::addAboveBelowKernRdf -- Save for later that +// !!!RDF**kern: > = above +// !!!RDF**kern: < = below +// in the output Humdrum data file. +// + +void Tool_musedata2hum::addAboveBelowKernRdf(void) { + m_aboveBelowKernRdf = true; +} + + + +////////////////////////////// +// +// Tool_musedata2hum::needsAboveBelowKernRdf -- Function name says it all. +// + +bool Tool_musedata2hum::needsAboveBelowKernRdf(void) { + return m_aboveBelowKernRdf; +} + + ////////////////////////////// // @@ -119686,6 +121194,13 @@ bool Tool_timebase::run(HumdrumFile& infile) { // void Tool_timebase::processFile(HumdrumFile& infile) { + // Test code: + #ifdef __EMSCRIPTEN__ + std::cout << "Compiled with Emscripten into WebAssembly or JavaScript." << std::endl; + #else + std::cout << "Compiled as a native OS executable." << std::endl; + #endif + m_grace = getBoolean("grace"); m_quiet = getBoolean("quiet"); if (!getBoolean("timebase")) { From 5b9fa14ceecb3b312ce9d6c83f6270c78b150f86 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 22 Apr 2024 00:19:12 -0700 Subject: [PATCH 070/383] Add default scoreDef@tempo.dist="3" to Humdrum-to-MEI converter. --- include/vrv/iohumdrum.h | 1 + src/iohumdrum.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index f9c31a1d954..42a17202173 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -927,6 +927,7 @@ class HumdrumInput : public vrv::Input { int getBestItem(const std::vector &items, const std::string &requiredLanguage); bool isStandardHumdrumKey(const std::string &key); void appendText(pugi::xml_node element, std::string text); + void addDefaultTempoDist(double distance); /// Templates /////////////////////////////////////////////////////////// template void verticalRest(ELEMENT rest, const std::string &token); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 2736714a063..35ed08d98f9 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -969,6 +969,8 @@ bool HumdrumInput::convertHumdrum() promoteInstrumentAbbreviationsToGroup(); promoteInstrumentNamesToGroup(); + addDefaultTempoDist(3); + processHangingTieEnds(); finalizeDocument(m_doc); @@ -987,6 +989,18 @@ bool HumdrumInput::convertHumdrum() return status; } +////////////////////////////// +// +// HumdrumInput::addDefaultTempoDist -- Add scoreDef@tempo.dist. +// + +void HumdrumInput::addDefaultTempoDist(double distance) +{ + data_MEASUREMENTSIGNED something; + something.SetVu(distance); + m_score->GetScoreDef()->SetTempoDist(something); +} + ////////////////////////////// // // HumdrumInput::checkIfReversedSpineOrder -- Return true of the lowest staff maps to the From 764d3d28233c4932c171a6d41776aa9f9bf4dcdc Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 22 Apr 2024 01:27:15 -0700 Subject: [PATCH 071/383] Add embedded option for controlling tempo height. --- include/vrv/iohumdrum.h | 1 + src/iohumdrum.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index 42a17202173..e90fbe07f90 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -894,6 +894,7 @@ class HumdrumInput : public vrv::Input { int getStaffNumForSpine(hum::HTp token); bool checkIfReversedSpineOrder(std::vector &staffstarts); bool hasOmdText(int startline, int endline); + void processMeiOptions(hum::HumdrumFile &infile); // header related functions: /////////////////////////////////////////// void createHeader(); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 35ed08d98f9..a8c7388c168 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -975,6 +975,8 @@ bool HumdrumInput::convertHumdrum() finalizeDocument(m_doc); + processMeiOptions(infile); + if (m_debug) { cout << GetMeiString(); } @@ -2715,6 +2717,42 @@ void HumdrumInput::parseEmbeddedOptions(Doc *doc) } } +////////////////////////////// +// +// HumdrumInput::processMeiOptions -- +// +// Known options: +// +// !!!mei: staffDef@tempo.dist="4" +// Set the minimum distance to the staff to 4 diatonic steps. +// + +void HumdrumInput::processMeiOptions(hum::HumdrumFile &infile) +{ + std::vector meiOptions; + for (int i = infile.getLineCount() - 1; i >= 0; i--) { + if (!infile[i].isComment()) { + continue; + } + if (!infile[i].isReference()) { + continue; + } + std::string key = infile[i].getReferenceKey(); + if (key == "mei") { + std::string value = infile[i].getReferenceValue(); + meiOptions.push_back(value); + } + } + + hum::HumRegex hre; + for (int i = 0; i < (int)meiOptions.size(); i++) { + if (hre.search(meiOptions[i], "\\bscoreDef@tempo.dist=\"([\\d.+-]+)\"")) { + double distance = hre.getMatchDouble(1); + addDefaultTempoDist(distance); + } + } +} + ////////////////////////////// // // HumdrumInput::parseMultiVerovioOptions -- From 1340dda5c99fba67a125b24f00584ce0fade7d8a Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 11 Mar 2024 22:59:11 +0100 Subject: [PATCH 072/383] Fix lyrics position in midi output for in notes <= quarter --- src/midifunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/midifunctor.cpp b/src/midifunctor.cpp index 5bf7f6246e8..8e19d336ed8 100644 --- a/src/midifunctor.cpp +++ b/src/midifunctor.cpp @@ -798,7 +798,7 @@ FunctorCode GenerateMIDIFunctor::VisitStaffDef(const StaffDef *staffDef) FunctorCode GenerateMIDIFunctor::VisitSyl(const Syl *syl) { - const int startTime = m_totalTime + m_lastNote->GetScoreTimeOnset(); + const double startTime = m_totalTime + m_lastNote->GetScoreTimeOnset(); const std::string sylText = UTF32to8(syl->GetText()); m_midiFile->addLyric(m_midiTrack, startTime * m_midiFile->getTPQ(), sylText); From 861cc4d8171cfdb408f35b28ff4e809322a4717a Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 13:09:05 -0700 Subject: [PATCH 073/383] Added ClearCoords call when editing a beam --- src/editortoolkit_cmn.cpp | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 6d8f85f49a8..3369563c761 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -213,7 +213,7 @@ bool EditorToolkitCMN::Delete(std::string &elementId) { Object *element = this->GetElement(elementId); if (!element) return false; - + if (element->Is(NOTE)) { return this->DeleteNote(vrv_cast(element)); } @@ -247,15 +247,15 @@ bool EditorToolkitCMN::KeyDown(std::string &elementId, int key, bool shiftKey, b // For elements whose y-position corresponds to a certain pitch if (element->HasInterface(INTERFACE_PITCH)) { - PitchInterface *interface = element->GetPitchInterface(); - assert(interface); + PitchInterface* pitch_interface = element->GetPitchInterface(); + assert(pitch_interface); int step; switch (key) { case KEY_UP: step = 1; break; case KEY_DOWN: step = -1; break; default: step = 0; } - interface->AdjustPitchByOffset(step); + pitch_interface->AdjustPitchByOffset(step); return true; } return false; @@ -301,11 +301,11 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); - assert(interface); + TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); + assert(timespan_interface); measure->AddChild(element); - interface->SetStartid("#" + startid); - interface->SetEndid("#" + endid); + timespan_interface->SetStartid("#" + startid); + timespan_interface->SetEndid("#" + endid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -512,7 +512,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) Chord *chord = note->IsChordTone(); Beam *beam = note->GetAncestorBeam(); - + if (chord) { if (chord->HasEditorialContent()) { LogInfo("Deleting a note in a chord that has editorial content is not possible"); @@ -560,6 +560,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } } else if (beam) { + // If the beam has exactly 2 notes (take apart and leave a single note and a rest) if ((int)beam->m_beamSegment.GetElementCoordRefs()->size() == 2) { bool insertBefore = true; LayerElement *otherElement = beam->m_beamSegment.GetElementCoordRefs()->back()->m_element; @@ -584,7 +585,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) m_chainedId = rest->GetID(); return true; } - if (beam->IsFirstIn(note)) { + // If the beam has more than 2 and this is first + else if (beam->IsFirstIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); Object *parent = beam->GetParent(); @@ -592,8 +594,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) parent->InsertBefore(beam, rest); beam->DeleteChild(note); m_chainedId = rest->GetID(); - return true; } + // If the beam has more than 2 and this is last else if (beam->IsLastIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); @@ -602,18 +604,26 @@ bool EditorToolkitCMN::DeleteNote(Note *note) parent->InsertAfter(beam, rest); beam->DeleteChild(note); m_chainedId = rest->GetID(); - return true; } + // If the beam has more than 2 and this in the middle else { - Rest *rest = new Rest(); + Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); beam->ReplaceChild(note, rest); delete note; m_chainedId = rest->GetID(); - return true; } + + // All but the first IF statement branches lead here + + // Clearing the coords here fixes an error where the children get updated, but the + // internal m_beamElementCoordRefs does not. By clearing it, the system is forced + // to update that structure to reflect the current children. + beam->ClearCoords(); + return true; } else { + // Deal with just a single note (Not in beam or chord) Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); Object *parent = note->GetParent(); From f83cb0ddffe6fc6a160260ff3946898b588ac8a5 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 15:47:57 -0700 Subject: [PATCH 074/383] Adjusted to meet some existing comments --- src/editortoolkit_cmn.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 3369563c761..0ab3d479c20 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -213,7 +213,6 @@ bool EditorToolkitCMN::Delete(std::string &elementId) { Object *element = this->GetElement(elementId); if (!element) return false; - if (element->Is(NOTE)) { return this->DeleteNote(vrv_cast(element)); } @@ -348,10 +347,10 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); - assert(interface); + TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); + assert(timespan_interface); measure->AddChild(element); - interface->SetStartid("#" + startid); + timespan_interface->SetStartid("#" + startid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -512,7 +511,6 @@ bool EditorToolkitCMN::DeleteNote(Note *note) Chord *chord = note->IsChordTone(); Beam *beam = note->GetAncestorBeam(); - if (chord) { if (chord->HasEditorialContent()) { LogInfo("Deleting a note in a chord that has editorial content is not possible"); @@ -581,6 +579,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } beam->DetachChild(otherElement->GetIdx()); parent->ReplaceChild(beam, otherElement); + otherElement-> + // Here delete beam; m_chainedId = rest->GetID(); return true; From a338499616765c381fbc227f6ffa199082d413dd Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 15:53:35 -0700 Subject: [PATCH 075/383] fixed indentation --- src/editortoolkit_cmn.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 0ab3d479c20..38b5f77eb74 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -558,7 +558,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } } else if (beam) { - // If the beam has exactly 2 notes (take apart and leave a single note and a rest) + // If the beam has exactly 2 notes (take apart and leave a single note and a rest) if ((int)beam->m_beamSegment.GetElementCoordRefs()->size() == 2) { bool insertBefore = true; LayerElement *otherElement = beam->m_beamSegment.GetElementCoordRefs()->back()->m_element; @@ -579,13 +579,11 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } beam->DetachChild(otherElement->GetIdx()); parent->ReplaceChild(beam, otherElement); - otherElement-> - // Here delete beam; m_chainedId = rest->GetID(); return true; } - // If the beam has more than 2 and this is first + // If the beam has more than 2 and this is first else if (beam->IsFirstIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); @@ -595,7 +593,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) beam->DeleteChild(note); m_chainedId = rest->GetID(); } - // If the beam has more than 2 and this is last + // If the beam has more than 2 and this is last else if (beam->IsLastIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); @@ -605,25 +603,25 @@ bool EditorToolkitCMN::DeleteNote(Note *note) beam->DeleteChild(note); m_chainedId = rest->GetID(); } - // If the beam has more than 2 and this in the middle + // If the beam has more than 2 and this in the middle else { - Rest *rest = new Rest(); + Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); beam->ReplaceChild(note, rest); delete note; m_chainedId = rest->GetID(); } - - // All but the first IF statement branches lead here - - // Clearing the coords here fixes an error where the children get updated, but the - // internal m_beamElementCoordRefs does not. By clearing it, the system is forced - // to update that structure to reflect the current children. - beam->ClearCoords(); - return true; + + // All but the first IF statement branches lead here + + // Clearing the coords here fixes an error where the children get updated, but the + // internal m_beamElementCoordRefs does not. By clearing it, the system is forced + // to update that structure to reflect the current children. + beam->ClearCoords(); + return true; } else { - // Deal with just a single note (Not in beam or chord) + // Deal with just a single note (Not in beam or chord) Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); Object *parent = note->GetParent(); From ab81f18ad80c74aeb8fcb5e359ad035a59dd327b Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 15:59:23 -0700 Subject: [PATCH 076/383] fixed known clang issues --- src/editortoolkit_cmn.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 38b5f77eb74..4fd5b498a38 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -246,7 +246,7 @@ bool EditorToolkitCMN::KeyDown(std::string &elementId, int key, bool shiftKey, b // For elements whose y-position corresponds to a certain pitch if (element->HasInterface(INTERFACE_PITCH)) { - PitchInterface* pitch_interface = element->GetPitchInterface(); + PitchInterface *pitch_interface = element->GetPitchInterface(); assert(pitch_interface); int step; switch (key) { @@ -613,10 +613,9 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } // All but the first IF statement branches lead here - - // Clearing the coords here fixes an error where the children get updated, but the - // internal m_beamElementCoordRefs does not. By clearing it, the system is forced - // to update that structure to reflect the current children. + /* Clearing the coords here fixes an error where the children get updated, but the + * internal m_beamElementCoordRefs does not. By clearing it, the system is forced + * to update that structure to reflect the current children. */ beam->ClearCoords(); return true; } From a09ffe13e526e797bcb640e34b270b7e62f99d85 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 16:08:08 -0700 Subject: [PATCH 077/383] Fixed clang problem --- src/editortoolkit_cmn.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 4fd5b498a38..ed7ebd5fa03 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -611,7 +611,6 @@ bool EditorToolkitCMN::DeleteNote(Note *note) delete note; m_chainedId = rest->GetID(); } - // All but the first IF statement branches lead here /* Clearing the coords here fixes an error where the children get updated, but the * internal m_beamElementCoordRefs does not. By clearing it, the system is forced From 63ea66f0e9c8dfd7c558515f988fdc205fff4c75 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Thu, 7 Mar 2024 09:19:27 -0700 Subject: [PATCH 078/383] Reset interface variable name --- src/editortoolkit_cmn.cpp | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index ed7ebd5fa03..18cbed036a5 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -246,15 +246,15 @@ bool EditorToolkitCMN::KeyDown(std::string &elementId, int key, bool shiftKey, b // For elements whose y-position corresponds to a certain pitch if (element->HasInterface(INTERFACE_PITCH)) { - PitchInterface *pitch_interface = element->GetPitchInterface(); - assert(pitch_interface); + PitchInterface *interface = element->GetPitchInterface(); + assert(interface); int step; switch (key) { case KEY_UP: step = 1; break; case KEY_DOWN: step = -1; break; default: step = 0; } - pitch_interface->AdjustPitchByOffset(step); + interface->AdjustPitchByOffset(step); return true; } return false; @@ -300,11 +300,11 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); - assert(timespan_interface); + TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); + assert(interface); measure->AddChild(element); - timespan_interface->SetStartid("#" + startid); - timespan_interface->SetEndid("#" + endid); + interface->SetStartid("#" + startid); + interface->SetEndid("#" + endid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -347,10 +347,10 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); - assert(timespan_interface); + TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); + assert(interface); measure->AddChild(element); - timespan_interface->SetStartid("#" + startid); + interface->SetStartid("#" + startid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -451,7 +451,8 @@ bool EditorToolkitCMN::InsertNote(Object *object) } if (currentNote->HasEditorialContent()) { - LogInfo("Inserting a note where a note has editorial content is not possible"); + LogInfo("Inserting a note where a note has editorial content is not " + "possible"); return false; } @@ -513,7 +514,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) Beam *beam = note->GetAncestorBeam(); if (chord) { if (chord->HasEditorialContent()) { - LogInfo("Deleting a note in a chord that has editorial content is not possible"); + LogInfo("Deleting a note in a chord that has editorial content is not " + "possible"); return false; } int count = chord->GetChildCount(NOTE, UNLIMITED_DEPTH); @@ -558,7 +560,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } } else if (beam) { - // If the beam has exactly 2 notes (take apart and leave a single note and a rest) + // If the beam has exactly 2 notes (take apart and leave a single note and a + // rest) if ((int)beam->m_beamSegment.GetElementCoordRefs()->size() == 2) { bool insertBefore = true; LayerElement *otherElement = beam->m_beamSegment.GetElementCoordRefs()->back()->m_element; @@ -612,9 +615,10 @@ bool EditorToolkitCMN::DeleteNote(Note *note) m_chainedId = rest->GetID(); } // All but the first IF statement branches lead here - /* Clearing the coords here fixes an error where the children get updated, but the - * internal m_beamElementCoordRefs does not. By clearing it, the system is forced - * to update that structure to reflect the current children. */ + /* Clearing the coords here fixes an error where the children get updated, + * but the internal m_beamElementCoordRefs does not. By clearing it, the + * system is forced to update that structure to reflect the current + * children. */ beam->ClearCoords(); return true; } From 817af7ffa9d6ce76a5ab06dc08b4e95a5a73abff Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Mon, 11 Mar 2024 17:36:34 +0100 Subject: [PATCH 079/383] Refactor PAE Duration handling This change extracts the PAE duration handling into a static method that can be used to convert durations to their PAE representations. It also adds a new feature to the feature extractor that includes the note duration on the PAE note. --- include/vrv/featureextractor.h | 1 + include/vrv/iopae.h | 4 +++ src/featureextractor.cpp | 13 +++++++- src/iopae.cpp | 57 ++++++++++++++++++++-------------- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/include/vrv/featureextractor.h b/include/vrv/featureextractor.h index 0c049af5ec3..21a74630489 100644 --- a/include/vrv/featureextractor.h +++ b/include/vrv/featureextractor.h @@ -55,6 +55,7 @@ class FeatureExtractor { std::list m_previousNotes; jsonxx::Array m_pitchesChromatic; + jsonxx::Array m_pitchesChromaticWithDuration; jsonxx::Array m_pitchesDiatonic; jsonxx::Array m_pitchesIds; diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index 7c1eb5c76d5..7d40d7db64e 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -91,6 +91,10 @@ class PAEOutput : public Output { */ bool WriteObjectEnd(Object *object) override; + /** + * Helper method to return a string representation of the PAE duration. + */ + static std::string GetPaeDur(data_DURATION dur, int ndots); private: /** * @name Methods for writing containers (measures, staff, etc) scoreDef and related. diff --git a/src/featureextractor.cpp b/src/featureextractor.cpp index bf9ccfc8cf7..cc0854ce3dd 100644 --- a/src/featureextractor.cpp +++ b/src/featureextractor.cpp @@ -17,6 +17,7 @@ #include "chord.h" #include "doc.h" #include "gracegrp.h" +#include "iopae.h" #include "layer.h" #include "mdiv.h" #include "measure.h" @@ -70,10 +71,16 @@ void FeatureExtractor::Extract(const Object *object) } std::stringstream pitch; + std::stringstream pitchWithDuration; + + pitchWithDuration << PAEOutput::GetPaeDur(note->GetDur(), note->GetDots()); + data_OCTAVE oct = note->GetOct(); char octSign = (oct > 3) ? '\'' : ','; int signCount = (oct > 3) ? (oct - 3) : (4 - oct); - pitch << std::string(signCount, octSign); + std::string octaves = std::string(signCount, octSign); + pitch << octaves; + pitchWithDuration << octaves; const Accid *accid = vrv_cast(note->FindDescendantByType(ACCID)); if (accid) { @@ -98,13 +105,16 @@ void FeatureExtractor::Extract(const Object *object) default: accidStr = accidStrWritten; } pitch << accidStr; + pitchWithDuration << accidStr; } std::string pname = note->AttPitch::PitchnameToStr(note->GetPname()); std::transform(pname.begin(), pname.end(), pname.begin(), ::toupper); pitch << pname; + pitchWithDuration << pname; m_pitchesChromatic << pitch.str(); + m_pitchesChromaticWithDuration << pitchWithDuration.str(); m_pitchesDiatonic << pname; jsonxx::Array pitchesIds; pitchesIds << note->GetID(); @@ -145,6 +155,7 @@ void FeatureExtractor::ToJson(std::string &output) { jsonxx::Object o; + o << "pitchesChromaticWithDuration" << m_pitchesChromaticWithDuration; o << "pitchesChromatic" << m_pitchesChromatic; o << "pitchesDiatonic" << m_pitchesDiatonic; o << "pitchesIds" << m_pitchesIds; diff --git a/src/iopae.cpp b/src/iopae.cpp index 725f9bf22fa..cd2978b670a 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -542,6 +542,38 @@ void PAEOutput::WriteTupletEnd(Tuplet *tuplet) m_streamStringOutput << ";" << tuplet->GetNum() << ")"; } +std::string PAEOutput::GetPaeDur(data_DURATION ndur, int ndots) +{ + std::string dur; + switch (ndur) { + case (DURATION_long): dur = "0"; break; + case (DURATION_breve): dur = "9"; break; + case (DURATION_1): dur = "1"; break; + case (DURATION_2): dur = "2"; break; + case (DURATION_4): dur = "4"; break; + case (DURATION_8): dur = "8"; break; + case (DURATION_16): dur = "6"; break; + case (DURATION_32): dur = "3"; break; + case (DURATION_64): dur = "5"; break; + case (DURATION_128): dur = "7"; break; + case (DURATION_maxima): dur = "0"; break; + case (DURATION_longa): dur = "0"; break; + case (DURATION_brevis): dur = "9"; break; + case (DURATION_semibrevis): dur = "1"; break; + case (DURATION_minima): dur = "2"; break; + case (DURATION_semiminima): dur = "4"; break; + case (DURATION_fusa): dur = "8"; break; + case (DURATION_semifusa): dur = "6"; break; + default: LogWarning("Unsupported duration"); dur = "4"; + } + + if (ndots > 0) { + dur += std::string(ndots, '.'); + } + + return dur; +} + void PAEOutput::WriteDur(DurationInterface *interface) { assert(interface); @@ -550,30 +582,7 @@ void PAEOutput::WriteDur(DurationInterface *interface) if ((interface->GetDur() != m_currentDur) || (ndots != m_currentDots)) { m_currentDur = interface->GetDur(); m_currentDots = ndots; - std::string dur; - switch (m_currentDur) { - case (DURATION_long): dur = "0"; break; - case (DURATION_breve): dur = "9"; break; - case (DURATION_1): dur = "1"; break; - case (DURATION_2): dur = "2"; break; - case (DURATION_4): dur = "4"; break; - case (DURATION_8): dur = "8"; break; - case (DURATION_16): dur = "6"; break; - case (DURATION_32): dur = "3"; break; - case (DURATION_64): dur = "5"; break; - case (DURATION_128): dur = "7"; break; - case (DURATION_maxima): dur = "0"; break; - case (DURATION_longa): dur = "0"; break; - case (DURATION_brevis): dur = "9"; break; - case (DURATION_semibrevis): dur = "1"; break; - case (DURATION_minima): dur = "2"; break; - case (DURATION_semiminima): dur = "4"; break; - case (DURATION_fusa): dur = "8"; break; - case (DURATION_semifusa): dur = "6"; break; - default: LogWarning("Unsupported duration"); dur = "4"; - } - m_streamStringOutput << dur; - m_streamStringOutput << std::string(m_currentDots, '.'); + m_streamStringOutput << GetPaeDur(interface->GetDur(), m_currentDots); } } From e13ea09917961d4fcf9e511574871408c391c5a1 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Mon, 11 Mar 2024 17:43:30 +0100 Subject: [PATCH 080/383] Formatting --- include/vrv/iopae.h | 1 + src/featureextractor.cpp | 4 ++-- src/iopae.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index 7d40d7db64e..77313365e15 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -95,6 +95,7 @@ class PAEOutput : public Output { * Helper method to return a string representation of the PAE duration. */ static std::string GetPaeDur(data_DURATION dur, int ndots); + private: /** * @name Methods for writing containers (measures, staff, etc) scoreDef and related. diff --git a/src/featureextractor.cpp b/src/featureextractor.cpp index cc0854ce3dd..fad1e07b47b 100644 --- a/src/featureextractor.cpp +++ b/src/featureextractor.cpp @@ -72,9 +72,9 @@ void FeatureExtractor::Extract(const Object *object) std::stringstream pitch; std::stringstream pitchWithDuration; - + pitchWithDuration << PAEOutput::GetPaeDur(note->GetDur(), note->GetDots()); - + data_OCTAVE oct = note->GetOct(); char octSign = (oct > 3) ? '\'' : ','; int signCount = (oct > 3) ? (oct - 3) : (4 - oct); diff --git a/src/iopae.cpp b/src/iopae.cpp index cd2978b670a..86259d0c392 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -566,7 +566,7 @@ std::string PAEOutput::GetPaeDur(data_DURATION ndur, int ndots) case (DURATION_semifusa): dur = "6"; break; default: LogWarning("Unsupported duration"); dur = "4"; } - + if (ndots > 0) { dur += std::string(ndots, '.'); } From 861270571ea4405e84899d452427c0badd6bcc3f Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Tue, 12 Mar 2024 12:00:13 +0100 Subject: [PATCH 081/383] Update src/iopae.cpp Co-authored-by: Laurent Pugin --- src/iopae.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iopae.cpp b/src/iopae.cpp index 86259d0c392..005f1ce0d3d 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -582,7 +582,7 @@ void PAEOutput::WriteDur(DurationInterface *interface) if ((interface->GetDur() != m_currentDur) || (ndots != m_currentDots)) { m_currentDur = interface->GetDur(); m_currentDots = ndots; - m_streamStringOutput << GetPaeDur(interface->GetDur(), m_currentDots); + m_streamStringOutput << PAEOutput::GetPaeDur(interface->GetDur(), m_currentDots); } } From 0ecd074063fffa86feee61922f7260de0079d563 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 17 Mar 2024 18:29:36 +0100 Subject: [PATCH 082/383] Update Verovio.podspec to c++20 [skip-ci] --- Verovio.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Verovio.podspec b/Verovio.podspec index e401987e0fa..93b69a5ae10 100644 --- a/Verovio.podspec +++ b/Verovio.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = '14.0' s.osx.deployment_target = '10.15' s.pod_target_xcconfig = { - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", "CLANG_CXX_LIBRARY" => "libc++", "GCC_C_LANGUAGE_STANDARD" => "gnu11", "GCC_DYNAMIC_NO_PIC" => "NO", From 7c535f239530eeaf1371c523b7a34ddd08fb6a03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:45:18 +0000 Subject: [PATCH 083/383] Bump black from 22.12.0 to 24.3.0 in /fonts Bumps [black](https://github.com/psf/black) from 22.12.0 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.12.0...24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- fonts/poetry.lock | 60 ++++++++++++++++++++++++++++++-------------- fonts/pyproject.toml | 2 +- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/fonts/poetry.lock b/fonts/poetry.lock index bd9153bf74d..53c2a8f35ed 100644 --- a/fonts/poetry.lock +++ b/fonts/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "astroid" @@ -39,36 +39,47 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "black" -version = "22.12.0" +version = "24.3.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" +packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -389,6 +400,17 @@ files = [ {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, ] +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + [[package]] name = "parso" version = "0.8.3" @@ -767,4 +789,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "8990a11839baa27daa52ec96424fdaf96f294140034d66d66d7fde50197b0749" +content-hash = "13dc88eb6e57ab4192765ec4d46f48aa0091403e19806d5c3ed58ffe35a76464" diff --git a/fonts/pyproject.toml b/fonts/pyproject.toml index 3bd27e61a62..6c68d206a6d 100644 --- a/fonts/pyproject.toml +++ b/fonts/pyproject.toml @@ -12,7 +12,7 @@ svgpathtools = "^1.6.0" [tool.poetry.group.dev.dependencies] ipython = "^8.10.0" -black = "^22.8.0" +black = "^24.3.0" mypy = "^0.971" pylint = "^2.15.3" From 3636fa791998419151a22e8cd4bb8dd1b2b187bb Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 21 Mar 2024 14:51:17 +0100 Subject: [PATCH 084/383] Set locale in toolkit --- include/vrv/toolkit.h | 2 ++ src/toolkit.cpp | 5 +++++ src/vrv.cpp | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 14a5499baeb..e607b0d4ba6 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -790,6 +790,8 @@ class Toolkit { Options *m_options; + std::locale m_previousLocale; + /** * The C buffer string. */ diff --git a/src/toolkit.cpp b/src/toolkit.cpp index a2d44009785..d1f31872b91 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -69,6 +69,9 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; + // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) + m_previousLocale = std::locale::global(std::locale::classic()); + if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); resources.InitFonts(); @@ -85,6 +88,8 @@ Toolkit::Toolkit(bool initFont) Toolkit::~Toolkit() { + std::locale::global(m_previousLocale); + if (m_humdrumBuffer) { free(m_humdrumBuffer); m_humdrumBuffer = NULL; diff --git a/src/vrv.cpp b/src/vrv.cpp index f4ac75c2d33..8e4353b086c 100644 --- a/src/vrv.cpp +++ b/src/vrv.cpp @@ -211,14 +211,12 @@ void EnableLogToBuffer(bool value) std::string StringFormat(const char *fmt, ...) { - std::locale previousLocale = std::locale::global(std::locale("C")); std::string str(STRING_FORMAT_MAX_LEN, 0); va_list args; va_start(args, fmt); vsnprintf(&str[0], STRING_FORMAT_MAX_LEN, fmt, args); va_end(args); str.resize(strlen(str.data())); - std::locale::global(previousLocale); return str; } From 8e10a91878497cdf98f0b4d88c20d3f266f7ab73 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 21 Mar 2024 16:30:44 +0100 Subject: [PATCH 085/383] Add option --set-locale --- include/vrv/options.h | 1 + include/vrv/toolkit.h | 2 +- src/options.cpp | 4 ++++ src/toolkit.cpp | 12 ++++++++---- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/vrv/options.h b/include/vrv/options.h index 98a66d74f36..0e9160240a7 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -652,6 +652,7 @@ class Options { OptionBool m_preserveAnalyticalMarkup; OptionBool m_removeIds; OptionBool m_scaleToPageSize; + OptionBool m_setLocale; OptionBool m_showRuntime; OptionBool m_shrinkToFit; OptionIntMap m_smuflTextFont; diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index e607b0d4ba6..718df09219a 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -790,7 +790,7 @@ class Toolkit { Options *m_options; - std::locale m_previousLocale; + std::optional m_previousLocale; /** * The C buffer string. diff --git a/src/options.cpp b/src/options.cpp index 0a24f1c16bd..a873af78cd4 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1135,6 +1135,10 @@ Options::Options() m_scaleToPageSize.Init(false); this->Register(&m_scaleToPageSize, "scaleToPageSize", &m_general); + m_setLocale.SetInfo("Set the global locale", "Changes the global locale to C (this is not thread-safe)"); + m_setLocale.Init(false); + this->Register(&m_setLocale, "setLocale", &m_general); + m_showRuntime.SetInfo("Show runtime on CLI", "Display the total runtime on command-line"); m_showRuntime.Init(false); this->Register(&m_showRuntime, "showRuntime", &m_general); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index d1f31872b91..ffeb59035fa 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -69,9 +69,6 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; - // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) - m_previousLocale = std::locale::global(std::locale::classic()); - if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); resources.InitFonts(); @@ -88,7 +85,9 @@ Toolkit::Toolkit(bool initFont) Toolkit::~Toolkit() { - std::locale::global(m_previousLocale); + if (m_previousLocale) { + std::locale::global(*m_previousLocale); + } if (m_humdrumBuffer) { free(m_humdrumBuffer); @@ -1158,6 +1157,11 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) resources.LoadAll(); } + if (m_options->m_setLocale.GetValue() && !m_previousLocale) { + // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) + m_previousLocale = std::locale::global(std::locale::classic()); + } + return true; } From 01c00d9e348ec53a78d658bad31c5180105bdc23 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 21 Mar 2024 18:24:35 +0100 Subject: [PATCH 086/383] Add proper methods and apply option in CLI --- include/vrv/toolkit.h | 8 ++++++++ src/toolkit.cpp | 26 ++++++++++++++++++-------- tools/main.cpp | 2 ++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 718df09219a..8ec0376e71d 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -732,6 +732,14 @@ class Toolkit { */ int GetOutputTo() { return m_outputTo; } + /** + * Setting the global locale. + */ + ///@{ + void SetLocale(); + void ResetLocale(); + ///@} + /** * Measuring runtime. * diff --git a/src/toolkit.cpp b/src/toolkit.cpp index ffeb59035fa..d79ed929038 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -85,9 +85,7 @@ Toolkit::Toolkit(bool initFont) Toolkit::~Toolkit() { - if (m_previousLocale) { - std::locale::global(*m_previousLocale); - } + this->ResetLocale(); if (m_humdrumBuffer) { free(m_humdrumBuffer); @@ -1140,6 +1138,8 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) m_options->Sync(); + this->SetLocale(); + // Forcing font resource to be reset if the font is given in the options if (json.has("fontAddCustom")) { Resources &resources = m_doc.GetResourcesForModification(); @@ -1157,11 +1157,6 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) resources.LoadAll(); } - if (m_options->m_setLocale.GetValue() && !m_previousLocale) { - // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) - m_previousLocale = std::locale::global(std::locale::classic()); - } - return true; } @@ -2161,6 +2156,21 @@ std::string Toolkit::ConvertHumdrumToMIDI(const std::string &humdrumData) #endif } +void Toolkit::SetLocale() +{ + if (m_options->m_setLocale.GetValue() && !m_previousLocale) { + // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) + m_previousLocale = std::locale::global(std::locale::classic()); + } +} + +void Toolkit::ResetLocale() +{ + if (m_previousLocale) { + std::locale::global(*m_previousLocale); + } +} + void Toolkit::InitClock() { #ifndef NO_RUNTIME diff --git a/tools/main.cpp b/tools/main.cpp index d30a41fca43..a57880eccb3 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -259,6 +259,8 @@ int main(int argc, char **argv) } options->Sync(); + toolkit.SetLocale(); + if (show_version) { display_version(); exit(0); From 4e86b525f7a859c3b424dc5229b1f487ad599886 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Tue, 19 Mar 2024 18:23:52 +0100 Subject: [PATCH 087/383] fix ties from Dorico --- src/iomusxml.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index cab02b71ddf..2117e0e7510 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -3148,7 +3148,7 @@ void MusicXmlInput::ReadMusicXmlNote( } // ties - ReadMusicXmlTies(notations.node(), layer, note, measureNum); + ReadMusicXmlTies(node, layer, note, measureNum); // articulation std::vector artics; @@ -3834,7 +3834,8 @@ void MusicXmlInput::ReadMusicXmlBeamStart(const pugi::xml_node &node, const pugi void MusicXmlInput::ReadMusicXmlTies( const pugi::xml_node &node, Layer *layer, Note *note, const std::string &measureNum) { - for (pugi::xml_node xmlTie : node.children("tied")) { + pugi::xpath_node ties = node.select_node("notations[tied]"); + for (pugi::xml_node xmlTie : ties.node().children("tied")) { std::string tieType = xmlTie.attribute("type").as_string(); if (tieType.empty()) { From b4fd9d97e3adf515d4d2d932bd2651cd30e0e749 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Wed, 20 Mar 2024 10:32:01 +0100 Subject: [PATCH 088/383] iterate over node set --- src/iomusxml.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index 2117e0e7510..667e4d99a68 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -3834,8 +3834,9 @@ void MusicXmlInput::ReadMusicXmlBeamStart(const pugi::xml_node &node, const pugi void MusicXmlInput::ReadMusicXmlTies( const pugi::xml_node &node, Layer *layer, Note *note, const std::string &measureNum) { - pugi::xpath_node ties = node.select_node("notations[tied]"); - for (pugi::xml_node xmlTie : ties.node().children("tied")) { + pugi::xpath_node_set xmlTies = node.select_nodes("notations/tied"); + for (pugi::xpath_node_set::const_iterator it = xmlTies.begin(); it != xmlTies.end(); ++it) { + pugi::xml_node xmlTie = (*it).node(); std::string tieType = xmlTie.attribute("type").as_string(); if (tieType.empty()) { From 32a418ff8a8664fb4c60edeb921bbf2dfc14db80 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 22 Mar 2024 10:30:37 +0100 Subject: [PATCH 089/383] Update clang-format-check.yml --- .github/workflows/clang-format-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index e00c94d00fa..f79013e8515 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -18,8 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@v4.9.0 + uses: jidicula/clang-format-action@v4.11.0 with: - clang-format-version: "15" + clang-format-version: "18" check-path: ${{ matrix.path['check'] }} exclude-regex: ${{ matrix.path['exclude'] }} From ab7f8201bfc0897004383c6d2441c08e4812e115 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 22 Mar 2024 10:34:33 +0100 Subject: [PATCH 090/383] formatting --- include/vrv/bboxdevicecontext.h | 6 +++--- include/vrv/devicecontext.h | 10 +++++----- include/vrv/layerelement.h | 2 +- include/vrv/lb.h | 2 +- include/vrv/view.h | 2 +- src/beam.cpp | 10 ++++------ src/options.cpp | 18 ++++++------------ 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/include/vrv/bboxdevicecontext.h b/include/vrv/bboxdevicecontext.h index 05b63b990ba..1c6ee4f9274 100644 --- a/include/vrv/bboxdevicecontext.h +++ b/include/vrv/bboxdevicecontext.h @@ -50,7 +50,7 @@ class BBoxDeviceContext : public DeviceContext { */ ///@{ void SetBackground(int color, int style = AxSOLID) override; - void SetBackgroundImage(void *image, double opacity = 1.0) override{}; + void SetBackgroundImage(void *image, double opacity = 1.0) override {}; void SetBackgroundMode(int mode) override; void SetTextForeground(int color) override; void SetTextBackground(int color) override; @@ -87,7 +87,7 @@ class BBoxDeviceContext : public DeviceContext { void DrawSpline(int n, Point points[]) override; void DrawGraphicUri(int x, int y, int width, int height, const std::string &uri) override; void DrawSvgShape(int x, int y, int width, int height, double scale, pugi::xml_node svg) override; - void DrawBackgroundImage(int x = 0, int y = 0) override{}; + void DrawBackgroundImage(int x = 0, int y = 0) override {}; ///@} /** @@ -150,7 +150,7 @@ class BBoxDeviceContext : public DeviceContext { * @name Method for adding description element */ ///@{ - void AddDescription(const std::string &text) override{}; + void AddDescription(const std::string &text) override {}; ///@} private: diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index fde5c18875d..95c62a81208 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -211,7 +211,7 @@ class DeviceContext { * Special method for forcing bounding boxes to be updated * Used for invisible elements (e.g., ) that needs to be take into account in spacing */ - virtual void DrawPlaceholder(int x, int y){}; + virtual void DrawPlaceholder(int x, int y) {}; /** * @name Method for starting and ending a text @@ -262,14 +262,14 @@ class DeviceContext { * For example, the method can be used for grouping shapes in in SVG */ ///@{ - virtual void StartCustomGraphic(const std::string &name, std::string gClass = "", std::string gId = ""){}; - virtual void EndCustomGraphic(){}; + virtual void StartCustomGraphic(const std::string &name, std::string gClass = "", std::string gId = "") {}; + virtual void EndCustomGraphic() {}; ///@} /** * Method for changing the color of a custom graphic */ - virtual void SetCustomGraphicColor(const std::string &color){}; + virtual void SetCustomGraphicColor(const std::string &color) {}; /** * @name Methods for re-starting and ending a graphic for objects drawn in separate steps @@ -312,7 +312,7 @@ class DeviceContext { * @name Method for adding description element */ ///@{ - virtual void AddDescription(const std::string &text){}; + virtual void AddDescription(const std::string &text) {}; ///@} /** diff --git a/include/vrv/layerelement.h b/include/vrv/layerelement.h index 248554b5a1d..ea8307a9da1 100644 --- a/include/vrv/layerelement.h +++ b/include/vrv/layerelement.h @@ -296,7 +296,7 @@ class LayerElement : public Object, /** * Helper function to set shortening for elements with beam interface */ - virtual void SetElementShortening(int shortening){}; + virtual void SetElementShortening(int shortening) {}; /** * Get the stem mod for the element (if any) diff --git a/include/vrv/lb.h b/include/vrv/lb.h index 9b044f6d25b..52c97896da4 100644 --- a/include/vrv/lb.h +++ b/include/vrv/lb.h @@ -37,7 +37,7 @@ class Lb : public TextElement { /** * Lb is an empty element */ - void AddChild(Object *object) override{}; + void AddChild(Object *object) override {}; /** * Interface for class functor visitation diff --git a/include/vrv/view.h b/include/vrv/view.h index fffae2d7348..485d1d9b1a9 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -122,7 +122,7 @@ class View { virtual void DoRefresh() {} virtual void DoResize() {} virtual void DoReset() {} - virtual void OnPageChange(){}; + virtual void OnPageChange() {}; ///@} /** diff --git a/src/beam.cpp b/src/beam.cpp index 775ccb9f55a..5e30dfde1c5 100644 --- a/src/beam.cpp +++ b/src/beam.cpp @@ -333,9 +333,8 @@ std::pair BeamSegment::GetMinimalStemLength(const BeamDrawingInterface const auto [topOffset, bottomOffset] = this->GetVerticalOffset(beamInterface); // lambda check whether coord has element set and whether that element is CHORD or NOTE - const auto isNoteOrChord = [](BeamElementCoord *coord) { - return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); - }; + const auto isNoteOrChord + = [](BeamElementCoord *coord) { return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); }; using CoordIt = ArrayOfBeamElementCoords::const_iterator; for (CoordIt it = m_beamElementCoordRefs.begin(); it != m_beamElementCoordRefs.end(); ++it) { @@ -460,9 +459,8 @@ void BeamSegment::AdjustBeamToFrenchStyle(const BeamDrawingInterface *beamInterf // set to store durations of relevant notes (it's ordered, so min duration is going to be first) std::set noteDurations; // lambda check whether coord has element set and whether that element is CHORD or NOTE - const auto isNoteOrChord = [](BeamElementCoord *coord) { - return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); - }; + const auto isNoteOrChord + = [](BeamElementCoord *coord) { return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); }; // iterators using CoordIt = ArrayOfBeamElementCoords::iterator; using CoordReverseIt = ArrayOfBeamElementCoords::reverse_iterator; diff --git a/src/options.cpp b/src/options.cpp index a873af78cd4..86fd84f5c47 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -111,13 +111,11 @@ jsonxx::Object Option::ToJson() const const OptionBool *optBool = dynamic_cast(this); if (optBool) { - opt << "type" - << "bool"; + opt << "type" << "bool"; opt << "default" << optBool->GetDefault(); } else if (optDbl) { - opt << "type" - << "double"; + opt << "type" << "double"; jsonxx::Value value(optDbl->GetDefault()); value.precision_ = 2; opt << "default" << value; @@ -129,20 +127,17 @@ jsonxx::Object Option::ToJson() const opt << "max" << value; } else if (optInt) { - opt << "type" - << "int"; + opt << "type" << "int"; opt << "default" << optInt->GetDefault(); opt << "min" << optInt->GetMin(); opt << "max" << optInt->GetMax(); } else if (optString) { - opt << "type" - << "std::string"; + opt << "type" << "std::string"; opt << "default" << optString->GetDefault(); } else if (optArray) { - opt << "type" - << "array"; + opt << "type" << "array"; std::vector strValues = optArray->GetDefault(); std::vector::iterator strIter; jsonxx::Array values; @@ -152,8 +147,7 @@ jsonxx::Object Option::ToJson() const opt << "default" << values; } else if (optIntMap) { - opt << "type" - << "std::string-list"; + opt << "type" << "std::string-list"; opt << "default" << optIntMap->GetDefaultStrValue(); std::vector strValues = optIntMap->GetStrValues(false); std::vector::iterator strIter; From 29381da87a0712a776351e4c552c01f68374375c Mon Sep 17 00:00:00 2001 From: David Bauer Date: Mon, 8 Apr 2024 21:15:06 +0200 Subject: [PATCH 091/383] Fix HasFile implementation --- src/filereader.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/filereader.cpp b/src/filereader.cpp index 4703dd56a44..2ae6dfc9659 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -58,7 +58,7 @@ bool ZipFileReader::Load(const std::string &filename) #else std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); if (!fin.is_open()) { - LogError("File archive '%s' could not be open.", filename.c_str()); + LogError("File archive '%s' could not be opened.", filename.c_str()); return false; } @@ -92,7 +92,7 @@ std::list ZipFileReader::GetFileList() const assert(m_file); std::list list; - for (miniz_cpp::zip_info &member : m_file->infolist()) { + for (const miniz_cpp::zip_info &member : m_file->infolist()) { list.push_back(member.filename); } return list; @@ -103,13 +103,9 @@ bool ZipFileReader::HasFile(const std::string &filename) assert(m_file); // Look for the file in the zip - for (miniz_cpp::zip_info &member : m_file->infolist()) { - if (member.filename == filename) { - return true; - } - } - - return true; + const std::vector &fileInfoList = m_file->infolist(); + return std::any_of(fileInfoList.cbegin(), fileInfoList.cend(), + [&filename](const auto &info) { return info.filename == filename; }); } std::string ZipFileReader::ReadTextFile(const std::string &filename) @@ -117,7 +113,7 @@ std::string ZipFileReader::ReadTextFile(const std::string &filename) assert(m_file); // Look for the meta file in the zip - for (miniz_cpp::zip_info &member : m_file->infolist()) { + for (const miniz_cpp::zip_info &member : m_file->infolist()) { if (member.filename == filename) { return m_file->read(member.filename); } From 6bc88cd2221d80fb6216ba92d2611143f53b2074 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 15 Apr 2024 09:45:15 +0200 Subject: [PATCH 092/383] only draw extender if set explicitly --- src/system.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/system.cpp b/src/system.cpp index 2a070dba54f..cbf3e6748f3 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -341,7 +341,7 @@ void System::AddToDrawingListIfNecessary(Object *object) else if (object->Is(DYNAM)) { Dynam *dynam = vrv_cast(object); assert(dynam); - if (dynam->GetEnd() || (dynam->GetNextLink() && (dynam->GetExtender() == BOOLEAN_true))) { + if ((dynam->GetEnd() || dynam->GetNextLink()) && (dynam->GetExtender() == BOOLEAN_true)) { this->AddToDrawingList(dynam); } } From db55d11b530912a5dec5d419c774d5c98d5fa9ca Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 7 Jan 2024 22:28:25 +0100 Subject: [PATCH 093/383] Handle rotation offset when rendering layer elements --- src/view_element.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/view_element.cpp b/src/view_element.cpp index 9b2cc39a933..3fc6cf08ad8 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -775,6 +775,9 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St if (staff->HasDrawingRotation()) { y -= staff->GetDrawingRotationOffsetFor(x); } + else if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); + } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); From 8f09c5f1d3046a34da52ceb62393a4e41fe02933 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 14:39:46 -0500 Subject: [PATCH 094/383] Adjust offset when insert new neume component --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2b1d30f6dc8..6a8cf8e89a4 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -881,11 +881,12 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int noteWidthOffset = (int)(noteWidth / 2); // Set up facsimile - zone->SetUlx(ulx); + zone->SetUlx(ulx - noteWidthOffset); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidthOffset); zone->SetLry(uly + noteHeight); // add syl bounding box if Facs From 9f54057db2b29238d32af2fbd987b19cdc5db02c Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 14:43:15 -0500 Subject: [PATCH 095/383] Adjust offset when insert new neume grouping --- src/editortoolkit_neume.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 6a8cf8e89a4..2564eb70c2f 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -996,9 +996,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in // Apply offset due to rotate newUly += (newUlx - ulx) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); - newZone->SetUlx(newUlx); + newZone->SetUlx(newUlx - noteWidthOffset); newZone->SetUly(newUly); - newZone->SetLrx(newUlx + noteWidth); + newZone->SetLrx(newUlx + noteWidthOffset); newZone->SetLry(newUly + noteHeight); newNc->AttachZone(newZone); From 20a9144e0e48d6dd0a88e16cb83bca5fe18645f3 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:00:05 -0500 Subject: [PATCH 096/383] Adjust offset when insert new clef --- src/editortoolkit_neume.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2564eb70c2f..92989b866e6 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1028,14 +1028,21 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Clef *clef = new Clef(); data_CLEFSHAPE clefShape = CLEFSHAPE_NONE; + const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int noteWidthOffsetR, noteWidthOffsetL; + for (auto it = attributes.begin(); it != attributes.end(); ++it) { if (it->first == "shape") { if (it->second == "C") { clefShape = CLEFSHAPE_C; + noteWidthOffsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + noteWidthOffsetL = noteWidthOffsetR; break; } else if (it->second == "F") { clefShape = CLEFSHAPE_F; + noteWidthOffsetR = 0; + noteWidthOffsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); break; } } @@ -1049,7 +1056,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } clef->SetShape(clefShape); - const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); int yDiff = -staff->GetDrawingY() + uly; yDiff += ((ulx - staff->GetZone()->GetUlx())) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); // Subtract distance due to rotate. @@ -1057,9 +1063,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in clef->SetLine(clefLine); Zone *zone = new Zone(); - zone->SetUlx(ulx); + zone->SetUlx(ulx - noteWidthOffsetR); zone->SetUly(uly); - zone->SetLrx(ulx + staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + zone->SetLrx(ulx + noteWidthOffsetL); zone->SetLry(uly + staffSize / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); clef->AttachZone(zone); Surface *surface = dynamic_cast(facsimile->FindDescendantByType(SURFACE)); From 22fec97e1b92d3c591b5aba574980759c106878f Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:15:21 -0500 Subject: [PATCH 097/383] Adjust offset when insert new custos --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 92989b866e6..2ce57934ac8 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1111,13 +1111,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 4); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); if (!AdjustPitchFromPosition(custos)) { From 9ac5268aad9d9e24fcf877740b8bfafc7df1c9e2 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:19:08 -0500 Subject: [PATCH 098/383] Rename offset for position adjustment --- src/editortoolkit_neume.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2ce57934ac8..bf2925fb352 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -881,12 +881,12 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - const int noteWidthOffset = (int)(noteWidth / 2); + const int offsetX = (int)(noteWidth / 2); // Set up facsimile - zone->SetUlx(ulx - noteWidthOffset); + zone->SetUlx(ulx - offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidthOffset); + zone->SetLrx(ulx + offsetX); zone->SetLry(uly + noteHeight); // add syl bounding box if Facs @@ -996,9 +996,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in // Apply offset due to rotate newUly += (newUlx - ulx) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); - newZone->SetUlx(newUlx - noteWidthOffset); + newZone->SetUlx(newUlx - offsetX); newZone->SetUly(newUly); - newZone->SetLrx(newUlx + noteWidthOffset); + newZone->SetLrx(newUlx + offsetX); newZone->SetLry(newUly + noteHeight); newNc->AttachZone(newZone); @@ -1029,20 +1029,20 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in data_CLEFSHAPE clefShape = CLEFSHAPE_NONE; const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int noteWidthOffsetR, noteWidthOffsetL; + int offsetR, offsetL; for (auto it = attributes.begin(); it != attributes.end(); ++it) { if (it->first == "shape") { if (it->second == "C") { clefShape = CLEFSHAPE_C; - noteWidthOffsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); - noteWidthOffsetL = noteWidthOffsetR; + offsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + offsetL = offsetR; break; } else if (it->second == "F") { clefShape = CLEFSHAPE_F; - noteWidthOffsetR = 0; - noteWidthOffsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + offsetR = 0; + offsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); break; } } @@ -1063,9 +1063,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in clef->SetLine(clefLine); Zone *zone = new Zone(); - zone->SetUlx(ulx - noteWidthOffsetR); + zone->SetUlx(ulx - offsetR); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidthOffsetL); + zone->SetLrx(ulx + offsetL); zone->SetLry(uly + staffSize / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); clef->AttachZone(zone); Surface *surface = dynamic_cast(facsimile->FindDescendantByType(SURFACE)); From 8afcd53e9405021fee6c8a6a27bfea604a1d57fe Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:52:52 -0500 Subject: [PATCH 099/383] Adjust offset when insert new accid --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index bf2925fb352..d6c97ef532d 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1165,13 +1165,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); From f96bd3fdf8054ae5c08631a3ef4d04b212865cde Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 2 Feb 2024 15:57:01 -0500 Subject: [PATCH 100/383] Adjust offset when insert new divLine --- src/editortoolkit_neume.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index d6c97ef532d..ac01f9f0fde 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1230,13 +1230,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); From 6c3fb3a494ead95ab201de74910e856788540386 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 6 Feb 2024 17:04:50 -0500 Subject: [PATCH 101/383] Remove redundant zone of syl when merging syllables --- src/editortoolkit_neume.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index ac01f9f0fde..0e9ec5e8fae 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2728,6 +2728,10 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e + obj->GetChildCount(CLEF))) { Object *leftover; while ((leftover = obj->FindDescendantByType(SYL)) != NULL) { + Zone *zone = dynamic_cast(leftover->GetFacsimileInterface()->GetZone()); + if (zone != NULL) { + m_doc->GetFacsimile()->FindDescendantByType(SURFACE)->DeleteChild(zone); + } obj->DeleteChild(leftover); } while ((leftover = obj->FindDescendantByType(DIVLINE)) != NULL) { From fa29c16b9e5dc78fc3c135c1d4e93c3b91073347 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 4 Jan 2024 15:17:38 -0500 Subject: [PATCH 102/383] Add this-> to DrawDivLine --- src/view_element.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 4bbd8ec48a8..51ed9b647ad 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -113,7 +113,7 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay this->DrawCustos(dc, element, layer, staff, measure); } else if (element->Is(DIVLINE)) { - DrawDivLine(dc, element, layer, staff, measure); + this->DrawDivLine(dc, element, layer, staff, measure); } else if (element->Is(DOT)) { this->DrawDot(dc, element, layer, staff, measure); From e39330f9fca88e5f7cf935c19987f6ccbfd2e978 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 4 Jan 2024 16:40:31 -0500 Subject: [PATCH 103/383] Write liquescent via nc@curve --- src/editortoolkit_neume.cpp | 2 -- src/iomei.cpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 0e9ec5e8fae..5739ee1b417 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -948,7 +948,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in contour = it->second; } else if (it->first == "curve") { - Liquescent *liquescent = new Liquescent(); curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; if (it->second == "a") { curve = curvatureDirection_CURVE_a; @@ -958,7 +957,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in curve = curvatureDirection_CURVE_c; nc->SetCurve(curve); } - nc->AddChild(liquescent); } } diff --git a/src/iomei.cpp b/src/iomei.cpp index badac886885..410fb82e4bb 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2710,6 +2710,7 @@ void MEIOutput::WriteNc(pugi::xml_node currentNode, Nc *nc) this->WritePitchInterface(currentNode, nc); this->WritePositionInterface(currentNode, nc); nc->WriteColor(currentNode); + nc->WriteCurvatureDirection(currentNode); nc->WriteIntervalMelodic(currentNode); nc->WriteNcForm(currentNode); } From 3c2b3cb848f1539204c9c43d0cf8aa40340f739e Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Sun, 7 Jan 2024 15:05:56 -0500 Subject: [PATCH 104/383] Add reading @curve for nc --- src/iomei.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iomei.cpp b/src/iomei.cpp index 410fb82e4bb..20d79472273 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -6833,6 +6833,7 @@ bool MEIInput::ReadNc(Object *parent, pugi::xml_node nc) this->ReadPitchInterface(nc, vrvNc); this->ReadPositionInterface(nc, vrvNc); vrvNc->ReadColor(nc); + vrvNc->ReadCurvatureDirection(nc); vrvNc->ReadIntervalMelodic(nc); vrvNc->ReadNcForm(nc); From ed56c1cd9c7519da23229abb822355fdbc576860 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Sun, 7 Jan 2024 16:00:33 -0500 Subject: [PATCH 105/383] Add liquescent element to insertion --- src/editortoolkit_neume.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 5739ee1b417..ec7230e6081 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -957,6 +957,8 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in curve = curvatureDirection_CURVE_c; nc->SetCurve(curve); } + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); } } From 74b88f31fb8f7536fd049b94b4e94274476a61ad Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 8 Jan 2024 11:51:09 -0500 Subject: [PATCH 106/383] Add SetLiquescent() --- include/vrv/editortoolkit_neume.h | 2 + src/editortoolkit_neume.cpp | 66 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index 11828f083cb..5fcd380f836 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -50,6 +50,7 @@ class EditorToolkitNeume : public EditorToolkit { bool Set(std::string elementId, std::string attrType, std::string attrValue); bool SetText(std::string elementId, const std::string &text); bool SetClef(std::string elementId, std::string shape); + bool SetLiquescent(std::string elementId, std::string shape); bool Split(std::string elementId, int x); bool SplitNeume(std::string elementId, std::string ncId); bool Remove(std::string elementId); @@ -80,6 +81,7 @@ class EditorToolkitNeume : public EditorToolkit { bool ParseSetAction(jsonxx::Object param, std::string *elementId, std::string *attrType, std::string *attrValue); bool ParseSetTextAction(jsonxx::Object param, std::string *elementId, std::string *text); bool ParseSetClefAction(jsonxx::Object param, std::string *elementId, std::string *shape); + bool ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *shape); bool ParseSplitAction(jsonxx::Object param, std::string *elementId, int *x); bool ParseSplitNeumeAction(jsonxx::Object param, std::string *elementId, std::string *ncId); bool ParseRemoveAction(jsonxx::Object param, std::string *elementId); diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index ec7230e6081..1fe87d1b6bd 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -136,6 +136,13 @@ bool EditorToolkitNeume::ParseEditorAction(const std::string &json_editorAction) } LogWarning("Could not parse the set clef action"); } + else if (action == "setLiquescent") { + std::string elementId, curve; + if (this->ParseSetLiquescentAction(json.get("param"), &elementId, &curve)) { + return this->SetLiquescent(elementId, curve); + } + LogWarning("Could not parse the set liquescent action"); + } else if (action == "remove") { std::string elementId; if (this->ParseRemoveAction(json.get("param"), &elementId)) { @@ -1996,6 +2003,50 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) return true; } +bool EditorToolkitNeume::SetLiquescent(std::string elementId, std::string curve) +{ + if (!m_doc->GetDrawingPage()) { + LogError("Could not get the drawing page."); + m_editInfo.import("status", "FAILURE"); + m_editInfo.import("message", "Could not get the drawing page."); + return false; + } + + Nc *nc = vrv_cast(m_doc->GetDrawingPage()->FindDescendantByID(elementId)); + assert(nc); + bool hasLiquscent = nc->GetChildCount(); + + if (curve == "a") { + curvatureDirection_CURVE curve = curvatureDirection_CURVE_a; + nc->SetCurve(curve); + if (!hasLiquscent) { + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); + } + } + else if (curve == "c") { + curvatureDirection_CURVE curve = curvatureDirection_CURVE_c; + nc->SetCurve(curve); + if (!hasLiquscent) { + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); + } + } + else { + // For unset curve + curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; + nc->SetCurve(curve); + if (hasLiquscent) { + Liquescent *liquescent = vrv_cast(nc->FindDescendantByType(LIQUESCENT)); + nc->DeleteChild(liquescent); + } + } + + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); + return true; +} + bool EditorToolkitNeume::Split(std::string elementId, int x) { if (!m_doc->GetDrawingPage()) { @@ -3859,6 +3910,21 @@ bool EditorToolkitNeume::ParseSetClefAction(jsonxx::Object param, std::string *e return true; } +bool EditorToolkitNeume::ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *curve) +{ + if (!param.has("elementId")) { + LogWarning("Could not parse 'elementId'"); + return false; + } + *elementId = param.get("elementId"); + if (!param.has("curve")) { + LogWarning("Could not parse 'curve'"); + return false; + } + *curve = param.get("curve"); + return true; +} + bool EditorToolkitNeume::ParseRemoveAction(jsonxx::Object param, std::string *elementId) { if (!param.has("elementId")) return false; From 66a7bb3d57be0942a0dd1531bd313b0a8d2ca8cf Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Jan 2024 14:08:48 -0500 Subject: [PATCH 107/383] Handle liquescent in DrawNc() --- src/view_neume.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 1b972bab007..e62afa9787c 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -177,7 +177,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; } - else if (nc->GetCurve() == curvatureDirection_CURVE_c) { + else if (nc->GetCurve() == curvatureDirection_CURVE_c && nc->FindDescendantByType(LIQUESCENT)) { params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; @@ -187,7 +187,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).yOffsetLiq[0] = -1.5; params.at(0).yOffsetLiq[4] = -1.75; } - else if (nc->GetCurve() == curvatureDirection_CURVE_a) { + else if (nc->GetCurve() == curvatureDirection_CURVE_a && nc->FindDescendantByType(LIQUESCENT)) { params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; @@ -274,7 +274,9 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - this->DrawLayerChildren(dc, nc, layer, staff, measure); + if (!nc->FindDescendantByType(LIQUESCENT)) { + this->DrawLayerChildren(dc, nc, layer, staff, measure); + } dc->EndGraphic(element, this); } From bfbf863e42ac0f5a2798f7bc5fb4cb8a4f95ef77 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Feb 2024 17:09:58 -0500 Subject: [PATCH 108/383] Add DrawLiquescent() --- include/vrv/view.h | 1 + src/view_element.cpp | 3 + src/view_neume.cpp | 145 ++++++++++++++++++++++++++++++++----------- 3 files changed, 113 insertions(+), 36 deletions(-) diff --git a/include/vrv/view.h b/include/vrv/view.h index 2f59e4c6657..485d1d9b1a9 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -416,6 +416,7 @@ class View { ///@{ void DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); + void DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNeume(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); ///@} diff --git a/src/view_element.cpp b/src/view_element.cpp index 51ed9b647ad..abad3b08e88 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -139,6 +139,9 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay else if (element->Is(LIGATURE)) { this->DrawLigature(dc, element, layer, staff, measure); } + else if (element->Is(LIQUESCENT)) { + this->DrawLiquescent(dc, element, layer, staff, measure); + } else if (element->Is(MENSUR)) { this->DrawMensur(dc, element, layer, staff, measure); } diff --git a/src/view_neume.cpp b/src/view_neume.cpp index e62afa9787c..c593605b805 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -56,6 +56,110 @@ void View::DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, dc->EndGraphic(element, this); } +void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) +{ + assert(dc); + assert(layer); + assert(staff); + assert(measure); + + Liquescent *liquescent = dynamic_cast(element); + assert(liquescent); + + struct drawingParams { + wchar_t fontNo = SMUFL_E990_chantPunctum; + wchar_t fontNoLiq[5] = {}; + float xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + float yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + }; + std::vector params; + params.push_back(drawingParams()); + + dc->StartGraphic(element, "", element->GetID()); + + Clef *clef = layer->GetClef(element); + int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int staffLineNumber = staff->m_drawingLines; + int clefLine = clef->GetLine(); + + Nc *nc = dynamic_cast(element->GetParent()); + assert(liquescent); + + if (nc->GetCurve() == curvatureDirection_CURVE_c) { + params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; + params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; + params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; + params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).xOffsetLiq[4] = 0.8; + params.at(0).yOffsetLiq[0] = -1.5; + params.at(0).yOffsetLiq[4] = -1.75; + } + else if (nc->GetCurve() == curvatureDirection_CURVE_a) { + params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; + params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; + params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; + params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).xOffsetLiq[4] = 0.8; + params.at(0).yOffsetLiq[0] = 0.5; + params.at(0).yOffsetLiq[4] = 0.75; + } + + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + int noteY, noteX; + int yValue; + if (nc->HasFacs() && m_doc->IsFacs()) { + noteY = ToLogicalY(staff->GetDrawingY()); + noteX = nc->GetDrawingX(); + } + else { + noteX = element->GetDrawingX(); + noteY = element->GetDrawingY(); + } + + // Calculating proper y offset based on pname, clef, staff, and staff rotate + int clefYPosition = noteY - (staffSize * (staffLineNumber - clefLine)); + int pitchOffset = 0; + + // The default octave = 3, but the actual octave is calculated by + // taking into account the displacement of the clef + int clefOctave = 3; + if (clef->GetDis() && clef->GetDisPlace()) { + clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); + } + int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); + int rotateOffset; + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = noteX - staff->GetDrawingX(); + rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + } + else { + rotateOffset = 0; + } + + if (clef->GetShape() == CLEFSHAPE_C) { + pitchOffset = (nc->GetPname() - 1) * (staffSize / 2); + } + else if (clef->GetShape() == CLEFSHAPE_F) { + pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); + } + yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; + + for (auto it = params.begin(); it != params.end(); it++) { + for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { + DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, + it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); + } + } + + dc->EndGraphic(element, this); +} + void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) { assert(dc); @@ -74,10 +178,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff struct drawingParams { wchar_t fontNo = SMUFL_E990_chantPunctum; wchar_t fontNoLiq[5] = {}; - double xOffset = 0; - double yOffset = 0; - double xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - double yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + float xOffset = 0; + float yOffset = 0; }; std::vector params; params.push_back(drawingParams()); @@ -177,27 +279,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; } - else if (nc->GetCurve() == curvatureDirection_CURVE_c && nc->FindDescendantByType(LIQUESCENT)) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; - params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; - params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = -1.5; - params.at(0).yOffsetLiq[4] = -1.75; - } - else if (nc->GetCurve() == curvatureDirection_CURVE_a && nc->FindDescendantByType(LIQUESCENT)) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; - params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; - params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = 0.5; - params.at(0).yOffsetLiq[4] = 0.75; - } - const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth @@ -251,14 +332,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; } - for (auto it = params.begin(); it != params.end(); it++) { - if (nc->GetCurve() == curvatureDirection_CURVE_a || nc->GetCurve() == curvatureDirection_CURVE_c) { - for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { - DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, - it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); - } - } - else { + if (!nc->HasCurve()) { + for (auto it = params.begin(); it != params.end(); it++) { DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, yValue + it->yOffset * noteHeight, it->fontNo, staff->m_drawingStaffSize, false, true); } @@ -274,9 +349,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - if (!nc->FindDescendantByType(LIQUESCENT)) { - this->DrawLayerChildren(dc, nc, layer, staff, measure); - } + this->DrawLayerChildren(dc, nc, layer, staff, measure); dc->EndGraphic(element, this); } From 2cb1b02c52c52eefaa872eac26e68e07211f14a2 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Feb 2024 17:20:14 -0500 Subject: [PATCH 109/383] Skip liquescents for pitch adjustment --- src/editortoolkit_neume.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 1fe87d1b6bd..0118c09f510 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -4154,6 +4154,8 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); for (auto it = pitchedChildren.begin(); it != pitchedChildren.end(); ++it) { + if ((*it)->Is(LIQUESCENT)) continue; + FacsimileInterface *fi = (*it)->GetFacsimileInterface(); if (fi == NULL || !fi->HasFacs()) { LogError("Could not adjust pitch: child %s does not have facsimile data", (*it)->GetID().c_str()); From 85aafe1dee4a9f9e1265d37c6f58d8a11dfb80ec Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 9 Jan 2024 14:08:48 -0500 Subject: [PATCH 110/383] Handle liquescent in DrawNc() --- src/view_neume.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index c593605b805..63840290216 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -349,7 +349,9 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - this->DrawLayerChildren(dc, nc, layer, staff, measure); + if (!nc->FindDescendantByType(LIQUESCENT)) { + this->DrawLayerChildren(dc, nc, layer, staff, measure); + } dc->EndGraphic(element, this); } From f9a0e6c36a9a774d90ea40672db0923d6275fab2 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 12 Feb 2024 17:09:58 -0500 Subject: [PATCH 111/383] Add DrawLiquescent() --- src/view_neume.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 63840290216..c593605b805 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -349,9 +349,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } // Draw the children - if (!nc->FindDescendantByType(LIQUESCENT)) { - this->DrawLayerChildren(dc, nc, layer, staff, measure); - } + this->DrawLayerChildren(dc, nc, layer, staff, measure); dc->EndGraphic(element, this); } From 703377b6803b26271ca6e175bc2df8f3e211f656 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 14 Feb 2024 15:51:56 -0500 Subject: [PATCH 112/383] Use while loop for consecutive layer elements inside syllable --- src/editortoolkit_neume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 0118c09f510..a2b5ff7460e 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2910,7 +2910,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } } - if (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { + while (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { fparent = el->GetFirstAncestor(SYLLABLE); sparent = el->GetFirstAncestor(LAYER); if (fparent && sparent) { From 25308061ad2d0975887e743ba527f8ddc5f97fd7 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 16 Feb 2024 16:36:14 -0500 Subject: [PATCH 113/383] Break loop when reach end of syllable --- src/editortoolkit_neume.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index a2b5ff7460e..8f8885c8a33 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2836,6 +2836,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector ListOfObjects syllables; // List of syllables used. groupType=neume only. jsonxx::Array uuidArray; + bool breakOnEnd = false; // Check if you can get drawing page if (!m_doc->GetDrawingPage()) { @@ -2920,10 +2921,15 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector fparent->ReorderByXPos(); uuidArray << (*it); it = elementIds.erase(it); - if (it == elementIds.end()) break; + if (it == elementIds.end()) { + breakOnEnd = true; + break; + } el = m_doc->GetDrawingPage()->FindDescendantByID(*it); } } + if (breakOnEnd) break; + if (elementIds.begin() == it || firstIsSyl) { // if the element is a syl we want it to stay attached to the first element // we'll still need to initialize all the parents, thus the bool From 86f7143f4a02d0f2cd214594a9d1643714c5f76d Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 16 Feb 2024 17:21:53 -0500 Subject: [PATCH 114/383] Remove redundant text for new syl in SetText() --- src/editortoolkit_neume.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 8f8885c8a33..0aa9f3fe447 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1895,11 +1895,7 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) std::u32string str = U""; text->SetText(str); syl->AddChild(text); - syllable->AddChild(syl); - Text *textChild = new Text(); - textChild->SetText(wtext); - syl->AddChild(textChild); if (m_doc->GetType() == Facs) { // Create a default bounding box Zone *zone = new Zone(); From 65b4ccbaf3cde6317323ec6e76461b4e0522ac98 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 22 Apr 2024 20:16:05 -0400 Subject: [PATCH 115/383] Fix DrawStaffLines() for neume lines --- src/staff.cpp | 2 +- src/view_page.cpp | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/staff.cpp b/src/staff.cpp index 5ba6a4a3dad..fc45d680088 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -173,7 +173,7 @@ void Staff::AdjustDrawingStaffSize() if (this->HasFacs()) { Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); assert(doc); - if (doc->IsFacs()) { + if (doc->IsFacs() || doc->IsNeumeLines()) { double rotate = this->GetDrawingRotate(); Zone *zone = this->GetZone(); assert(zone); diff --git a/src/view_page.cpp b/src/view_page.cpp index 3a13c9d1cb9..d150f693ed4 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1283,24 +1283,14 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys int j, x1, x2, y1, y2; - if (staff->HasFacs() && m_doc->IsFacs()) { - double d = staff->GetDrawingRotate(); - x1 = staff->GetDrawingX(); - x2 = x1 + staff->GetWidth(); - y1 = ToLogicalY(staff->GetDrawingY()); - staff->AdjustDrawingStaffSize(); - y2 = y1 - staff->GetWidth() * tan(d * M_PI / 180.0); + x1 = measure->GetDrawingX(); + x2 = x1 + measure->GetWidth(); + y1 = staff->GetDrawingY(); + if (!staff->HasDrawingRotation()) { + y2 = y1; } else { - x1 = measure->GetDrawingX(); - x2 = x1 + measure->GetWidth(); - y1 = staff->GetDrawingY(); - if (!staff->HasDrawingRotation()) { - y2 = y1; - } - else { - y2 = y1 - measure->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); - } + y2 = y1 - measure->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); } const int lineWidth = m_doc->GetDrawingStaffLineWidth(staff->m_drawingStaffSize); From b31408d24e2b242e52abb9a70b58d6101e95aa99 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 22 Apr 2024 20:17:54 -0400 Subject: [PATCH 116/383] Call SyncFromFacsimileDoc when zone changes --- src/editortoolkit_neume.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 3d5e409f257..d421f0fc48c 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -674,11 +674,11 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) if (fi->GetZone() != NULL) zones.insert(fi->GetZone()); } for (auto it = zones.begin(); it != zones.end(); ++it) { - // Transform y to device context (*it)->ShiftByXY(x, -y); } staff->GetParent()->StableSort(StaffSort()); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers } @@ -1259,6 +1259,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } layer->ReorderByXPos(); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", status); m_editInfo.import("message", message); return true; @@ -1700,6 +1703,8 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) zone->SetLry(uly + offsetY + height); } + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -1791,6 +1796,8 @@ bool EditorToolkitNeume::Merge(std::vector elementIds) m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + // TODO change zones for staff children return true; @@ -1934,6 +1941,9 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) m_editInfo.import("message", "Element type '" + element->GetClassName() + "' is unsupported for SetText."); return false; } + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", success ? status : "FAILURE"); m_editInfo.import("message", success ? message : "SetText method failed."); return success; @@ -2132,6 +2142,9 @@ bool EditorToolkitNeume::Split(std::string elementId, int x) } } layer->ClearRelinquishedChildren(); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); m_editInfo.import("uuid", splitStaff->GetID()); @@ -2185,6 +2198,8 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) FacsimileInterface *fi = syl->GetFacsimileInterface(); assert(fi); fi->AttachZone(zone); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); } } } @@ -2411,6 +2426,9 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx m_editInfo.import("message", "Element of type '" + obj->GetClassName() + "' is unsupported."); return false; } + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -2800,6 +2818,8 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e secondParent->ReorderByXPos(); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("uuid", parent->GetID()); m_editInfo.import("status", status); m_editInfo.import("message", message); @@ -3082,6 +3102,8 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); m_editInfo.import("uuid", uuidArray); @@ -3249,7 +3271,6 @@ bool EditorToolkitNeume::ChangeGroup(std::string elementId, std::string contour) } zone->SetUlx(newUlx); zone->SetUly(newUly); - ; zone->SetLrx(newLrx); zone->SetLry(newLry); @@ -3267,6 +3288,9 @@ bool EditorToolkitNeume::ChangeGroup(std::string elementId, std::string contour) initialLry = newLry; prevNc = newNc; } + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("uuid", el->GetID()); m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); @@ -3378,6 +3402,8 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) } surface->AddChild(zone); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + return success1 && success2; } From a1224a60b01c1f4500d4cca3550fc030ff3ee373 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 25 Apr 2024 09:25:51 +0200 Subject: [PATCH 117/383] Add an empty measure object only when none exist * This will still break with facsimile since the measure has no corresponding zone --- src/iomei.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/iomei.cpp b/src/iomei.cpp index badac886885..6cd054a24ec 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -4501,7 +4501,8 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) } // New for blank files in neume notation - if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume)) { + if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume) + && !parent->FindDescendantByType(MEASURE)) { if (m_doc->IsNeumeLines()) { unmeasured = new Measure(NEUMELINE); } From 8294255f0f6f1e0d4476d1bb818944cfb15ee9e7 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 25 Apr 2024 11:58:32 -0400 Subject: [PATCH 118/383] Fix StaffSort() for neme lines --- include/vrv/editortoolkit_neume.h | 22 +++++++++++++++++++--- src/editortoolkit_neume.cpp | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index 5fcd380f836..ea09e699823 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -16,6 +16,7 @@ #include "doc.h" #include "editortoolkit.h" +#include "measure.h" #include "view.h" #include "vrv.h" #include "zone.h" @@ -179,11 +180,26 @@ struct ClosestNeume { struct StaffSort { // Sort staves left-to-right and top-to-bottom // Sort by y if there is no intersection, by x if there is x intersection is smaller than half length of staff line + + // Update 2024-04: + // Used only in neume lines, + // System->(Measure->Staff) + // Need to sort Measure to sort staff bool operator()(Object *a, Object *b) { - if (!a->GetFacsimileInterface() || !b->GetFacsimileInterface()) return true; - Zone *zoneA = a->GetFacsimileInterface()->GetZone(); - Zone *zoneB = b->GetFacsimileInterface()->GetZone(); + if (!a->Is(MEASURE) || !b->Is(MEASURE)) return false; + Measure *measureA = dynamic_cast(a); + Measure *measureB = dynamic_cast(b); + + if (!measureA->IsNeumeLine() || !measureB->IsNeumeLine()) return true; + Object *staffA = a->FindDescendantByType(STAFF); + Object *staffB = b->FindDescendantByType(STAFF); + assert(staffA); + assert(staffB); + Zone *zoneA = staffA->GetFacsimileInterface()->GetZone(); + Zone *zoneB = staffB->GetFacsimileInterface()->GetZone(); + assert(zoneA); + assert(zoneB); int aLowest, bLowest, aHighest, bHighest; diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index d421f0fc48c..b57ef8c61e6 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -677,7 +677,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) (*it)->ShiftByXY(x, -y); } - staff->GetParent()->StableSort(StaffSort()); + staff->GetParent()->GetParent()->StableSort(StaffSort()); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers From df90106add227db89738f9ce45aec90e7f226f15 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 25 Apr 2024 15:42:03 -0400 Subject: [PATCH 119/383] Fix insert staff not showing & clean up --- src/editortoolkit_neume.cpp | 42 +++++++++++++------------------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index b57ef8c61e6..77c4a7d1d99 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -789,26 +789,28 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Zone *zone = new Zone(); if (elementType == "staff") { - Object *parent; + Object *grandParent; // System->(Measure->Staff) for neume lines + Measure *newMeasure = new Measure(NEUMELINE); Staff *newStaff; std::string columnValue; + // Use closest existing staff (if there is one) if (staff) { - parent = staff->GetParent(); - assert(parent); + grandParent = staff->GetParent()->GetParent(); + assert(grandParent); columnValue = staff->GetType(); - int n = parent->GetChildCount() + 1; + int n = grandParent->GetChildCount(MEASURE) + 1; newStaff = new Staff(n); newStaff->m_drawingStaffDef = staff->m_drawingStaffDef; newStaff->m_drawingNotationType = staff->m_drawingNotationType; newStaff->m_drawingLines = staff->m_drawingLines; } else { - parent = m_doc->GetDrawingPage()->FindDescendantByType(MEASURE); - assert(parent); + grandParent = m_doc->GetDrawingPage()->FindDescendantByType(SYSTEM); + assert(grandParent); newStaff = new Staff(1); newStaff->m_drawingStaffDef = vrv_cast( - m_doc->GetCorrespondingScore(parent)->GetScoreDef()->FindDescendantByType(STAFFDEF)); + m_doc->GetCorrespondingScore(grandParent)->GetScoreDef()->FindDescendantByType(STAFFDEF)); newStaff->m_drawingNotationType = NOTATIONTYPE_neume; newStaff->m_drawingLines = 4; } @@ -824,28 +826,12 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in if (columnValue.length()) newStaff->SetType(columnValue); Layer *newLayer = new Layer(); newStaff->AddChild(newLayer); + newMeasure->AddChild(newStaff); - if (staff) { - // Find index to insert new staff - ListOfObjects staves = parent->FindAllDescendantsByType(STAFF, false); - std::vector stavesVector(staves.begin(), staves.end()); - stavesVector.push_back(newStaff); - StaffSort staffSort; - std::stable_sort(stavesVector.begin(), stavesVector.end(), staffSort); - for (int i = 0; i < (int)staves.size(); ++i) { - if (stavesVector.at(i) == newStaff) { - parent->InsertChild(newStaff, i); - parent->Modify(); - - m_editInfo.import("uuid", newStaff->GetID()); - m_editInfo.import("status", status); - m_editInfo.import("message", message); - - return true; - } - } - } - parent->AddChild(newStaff); + grandParent->AddChild(newMeasure); + grandParent->StableSort(StaffSort()); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("uuid", newStaff->GetID()); m_editInfo.import("status", status); From fd2a4806d478397b892cb91c7b3a186a232f2903 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 25 Apr 2024 16:04:03 -0400 Subject: [PATCH 120/383] Fix inserted clef and syl not showing --- src/editortoolkit_neume.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 77c4a7d1d99..584a5a18422 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -884,12 +884,12 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in zone->SetLry(uly + noteHeight); // add syl bounding box if Facs - if (!m_doc->HasFacsimile()) { + if (m_doc->HasFacsimile()) { FacsimileInterface *fi = vrv_cast(syl->GetFacsimileInterface()); assert(fi); sylZone = new Zone(); - int staffLry = staff->GetFacsimileInterface()->GetZone()->GetLry(); + int staffLry = staff->GetZone()->GetLry(); // width height and offset can be adjusted int bboxHeight = 175; @@ -900,8 +900,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in int offsetY = 0; if (theta) { double factor = 1.3; - offsetY = (int)((ulx - staff->GetFacsimileInterface()->GetZone()->GetUlx()) * tan(theta * M_PI / 180.0) - / factor); + offsetY = (int)((ulx - staff->GetZone()->GetUlx()) * tan(theta * M_PI / 180.0) / factor); } sylZone->SetUlx(ulx); @@ -1050,7 +1049,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } clef->SetShape(clefShape); - int yDiff = -staff->GetDrawingY() + uly; + int yDiff = -staff->GetZone()->GetUly() + uly; yDiff += ((ulx - staff->GetZone()->GetUlx())) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); // Subtract distance due to rotate. int clefLine = staff->m_drawingLines - round((double)yDiff / (double)staffSize); From c3ba7a314f4ebe01d72121fc841125a1cb031f62 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 30 Apr 2024 08:28:14 -0700 Subject: [PATCH 121/383] Fix for issue https://github.com/rism-digital/verovio/issues/3661 --- src/iohumdrum.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index a8c7388c168..df48aca1729 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -26168,9 +26168,11 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta if (!editorialQ) { if (brackQ) { accid->SetEnclose(ENCLOSURE_brack); + cautionaryQ = true; } else if (parenQ) { accid->SetEnclose(ENCLOSURE_paren); + cautionaryQ = true; } if (showInAccid) { switch (accidCount) { From c0f0ba330f41fbcdde49d17d668a348d83dac0e7 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 30 Apr 2024 09:36:56 -0700 Subject: [PATCH 122/383] Refinement related to issue https://github.com/rism-digital/verovio/issues/3661 --- src/iohumdrum.cpp | 50 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index df48aca1729..57de1a318ee 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -26189,36 +26189,46 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta else if (!loaccid.empty()) { if (loaccid == "n#") { accid->SetAccid(ACCIDENTAL_WRITTEN_ns); + showInAccidGes = true; } else if (loaccid == "#") { accid->SetAccid(ACCIDENTAL_WRITTEN_s); + showInAccidGes = true; } else if (loaccid == "n") { accid->SetAccid(ACCIDENTAL_WRITTEN_n); + showInAccidGes = true; } else if (loaccid == "##") { accid->SetAccid(ACCIDENTAL_WRITTEN_ss); + showInAccidGes = true; } else if (loaccid == "x") { accid->SetAccid(ACCIDENTAL_WRITTEN_x); + showInAccidGes = true; } else if (loaccid == "-") { accid->SetAccid(ACCIDENTAL_WRITTEN_f); + showInAccidGes = true; } else if (loaccid == "--") { accid->SetAccid(ACCIDENTAL_WRITTEN_ff); + showInAccidGes = true; } else if (loaccid == "#x") { accid->SetAccid(ACCIDENTAL_WRITTEN_sx); } else if (loaccid == "###") { accid->SetAccid(ACCIDENTAL_WRITTEN_ts); + showInAccidGes = true; } else if (loaccid == "n-") { accid->SetAccid(ACCIDENTAL_WRITTEN_nf); + showInAccidGes = true; } else if (loaccid == "---") { accid->SetAccid(ACCIDENTAL_WRITTEN_tf); + showInAccidGes = true; } else { std::cerr << "Warning: unknown accidental type " << std::endl; @@ -26227,6 +26237,18 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta // which are not dealt with directly in **kern data: su, sd, fu, fd, nu, // nd, 1qf, 3qf, 1qs, 3qs // http://music-encoding.org/guidelines/v3/data-types/data.accidental.explicit.html + + if (showInAccidGes) { + switch (accidCount) { + // case +3: note->SetAccidGes(ACCIDENTAL_GESTURAL_ts); break; + // case -3: note->SetAccidGes(ACCIDENTAL_GESTURAL_tf); break; + case +2: accid->SetAccidGes(ACCIDENTAL_GESTURAL_ss); break; + case +1: accid->SetAccidGes(ACCIDENTAL_GESTURAL_s); break; + case 0: accid->SetAccidGes(ACCIDENTAL_GESTURAL_n); break; + case -1: accid->SetAccidGes(ACCIDENTAL_GESTURAL_f); break; + case -2: accid->SetAccidGes(ACCIDENTAL_GESTURAL_ff); break; + } + } } } else { @@ -26243,27 +26265,41 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta accid->SetFunc(accidLog_FUNC_edit); } if (edittype.find("brack") != std::string::npos) { - // enclose="brack" cannot be present with func="edit" at the moment... accid->SetEnclose(ENCLOSURE_brack); + accid->SetType("edit"); } else if (edittype.find("brac") != std::string::npos) { - // enclose="brac" cannot be present with func="edit" at the moment... accid->SetEnclose(ENCLOSURE_brack); + accid->SetType("edit"); } if (edittype.find("paren") != std::string::npos) { - // enclose="paren" cannot be present with func="edit" at the moment... accid->SetEnclose(ENCLOSURE_paren); + accid->SetType("edit"); } else if (edittype.find("none") != std::string::npos) { // display as a regular accidental + accid->SetType("edit"); } + if (loaccid.empty()) { switch (accidCount) { case +2: accid->SetAccid(ACCIDENTAL_WRITTEN_x); break; - case +1: accid->SetAccid(ACCIDENTAL_WRITTEN_s); break; - case 0: accid->SetAccid(ACCIDENTAL_WRITTEN_n); break; - case -1: accid->SetAccid(ACCIDENTAL_WRITTEN_f); break; - case -2: accid->SetAccid(ACCIDENTAL_WRITTEN_ff); break; + case +1: + accid->SetAccid(ACCIDENTAL_WRITTEN_s); + showInAccidGes = false; + break; + case 0: + accid->SetAccid(ACCIDENTAL_WRITTEN_n); + showInAccidGes = false; + break; + case -1: + accid->SetAccid(ACCIDENTAL_WRITTEN_f); + showInAccidGes = false; + break; + case -2: + accid->SetAccid(ACCIDENTAL_WRITTEN_ff); + showInAccidGes = false; + break; } } else { From 39dac7ab988e06df72f776ea723d3b1b47e2da10 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 30 Apr 2024 18:57:25 -0400 Subject: [PATCH 123/383] Keep Pb & SystemMilestoneEnd order after staff sort --- include/vrv/editortoolkit_neume.h | 9 ++++--- src/editortoolkit_neume.cpp | 41 +++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index ea09e699823..85ec79836cb 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -52,6 +52,7 @@ class EditorToolkitNeume : public EditorToolkit { bool SetText(std::string elementId, const std::string &text); bool SetClef(std::string elementId, std::string shape); bool SetLiquescent(std::string elementId, std::string shape); + bool SortStaves(); bool Split(std::string elementId, int x); bool SplitNeume(std::string elementId, std::string ncId); bool Remove(std::string elementId); @@ -187,10 +188,10 @@ struct StaffSort { // Need to sort Measure to sort staff bool operator()(Object *a, Object *b) { - if (!a->Is(MEASURE) || !b->Is(MEASURE)) return false; - Measure *measureA = dynamic_cast(a); - Measure *measureB = dynamic_cast(b); - + if (!a->Is(SYSTEM) || !b->Is(SYSTEM)) return false; + if (!a->FindDescendantByType(MEASURE) || !b->FindDescendantByType(MEASURE)) return false; + Measure *measureA = dynamic_cast(a->FindDescendantByType(MEASURE)); + Measure *measureB = dynamic_cast(b->FindDescendantByType(MEASURE)); if (!measureA->IsNeumeLine() || !measureB->IsNeumeLine()) return true; Object *staffA = a->FindDescendantByType(STAFF); Object *staffB = b->FindDescendantByType(STAFF); diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 584a5a18422..711e7f99822 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -677,7 +677,8 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) (*it)->ShiftByXY(x, -y); } - staff->GetParent()->GetParent()->StableSort(StaffSort()); + SortStaves(); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers @@ -2039,6 +2040,42 @@ bool EditorToolkitNeume::SetLiquescent(std::string elementId, std::string curve) return true; } +bool EditorToolkitNeume::SortStaves() +{ + if (!m_doc->GetDrawingPage()) { + LogError("Could not get drawing page."); + m_editInfo.import("status", "FAILURE"); + m_editInfo.import("message", "Could not get drawing page."); + return false; + } + + Object *page = m_doc->GetDrawingPage(); + + page->StableSort(StaffSort()); + + Object *pb = page->FindDescendantByType(PB); + Object *milestoneEnd = page->FindDescendantByType(SYSTEM_MILESTONE_END); + assert(pb); + assert(milestoneEnd); + Object *pbParent = pb->GetParent(); + Object *milestoneEndParent = milestoneEnd->GetParent(); + int pbIdx = pbParent->GetChildIndex(pb); + int milestoneEndIdx = milestoneEndParent->GetChildIndex(milestoneEnd); + + pb = pbParent->DetachChild(pbIdx); + milestoneEnd = milestoneEndParent->DetachChild(milestoneEndIdx); + + Object *firstSystem = page->GetFirst(SYSTEM); + Object *lastSystem = page->GetLast(SYSTEM); + assert(firstSystem); + assert(lastSystem); + + firstSystem->InsertChild(pb, 0); + lastSystem->InsertChild(milestoneEnd, lastSystem->GetChildCount()); + + return true; +} + bool EditorToolkitNeume::Split(std::string elementId, int x) { if (!m_doc->GetDrawingPage()) { @@ -2369,7 +2406,7 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx zone->SetRotate(rotate); } zone->Modify(); - staff->GetParent()->StableSort(StaffSort()); + SortStaves(); } else if (obj->Is(SYL)) { Syl *syl = vrv_cast(obj); From ee1f6509a6d34a1df1883e5749275d65a46b9f89 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 2 May 2024 14:23:50 -0400 Subject: [PATCH 124/383] Fix Insert() for new neumelines structure --- src/editortoolkit_neume.cpp | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 711e7f99822..8c9488b970a 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -25,19 +25,21 @@ #include "divline.h" #include "layer.h" #include "liquescent.h" +#include "measure.h" #include "nc.h" #include "neume.h" #include "page.h" #include "rend.h" +#include "sb.h" #include "score.h" #include "staff.h" #include "staffdef.h" #include "surface.h" #include "syl.h" #include "syllable.h" +#include "system.h" #include "text.h" #include "vrv.h" - //-------------------------------------------------------------------------------- namespace vrv { @@ -790,28 +792,27 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Zone *zone = new Zone(); if (elementType == "staff") { - Object *grandParent; // System->(Measure->Staff) for neume lines + Object *page = m_doc->GetDrawingPage(); + System *newSystem = new System(); + Sb *newSb = new Sb(); Measure *newMeasure = new Measure(NEUMELINE); Staff *newStaff; + Layer *newLayer = new Layer(); std::string columnValue; // Use closest existing staff (if there is one) if (staff) { - grandParent = staff->GetParent()->GetParent(); - assert(grandParent); columnValue = staff->GetType(); - int n = grandParent->GetChildCount(MEASURE) + 1; + int n = page->GetChildCount(SYSTEM) + 1; newStaff = new Staff(n); newStaff->m_drawingStaffDef = staff->m_drawingStaffDef; newStaff->m_drawingNotationType = staff->m_drawingNotationType; newStaff->m_drawingLines = staff->m_drawingLines; } else { - grandParent = m_doc->GetDrawingPage()->FindDescendantByType(SYSTEM); - assert(grandParent); newStaff = new Staff(1); newStaff->m_drawingStaffDef = vrv_cast( - m_doc->GetCorrespondingScore(grandParent)->GetScoreDef()->FindDescendantByType(STAFFDEF)); + m_doc->GetCorrespondingScore(page)->GetScoreDef()->FindDescendantByType(STAFFDEF)); newStaff->m_drawingNotationType = NOTATIONTYPE_neume; newStaff->m_drawingLines = 4; } @@ -825,12 +826,16 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in surface->AddChild(zone); newStaff->AttachZone(zone); if (columnValue.length()) newStaff->SetType(columnValue); - Layer *newLayer = new Layer(); + newStaff->AddChild(newLayer); newMeasure->AddChild(newStaff); + newSystem->AddChild(newSb); + newSystem->AddChild(newMeasure); + newSystem->SetDrawingScoreDef(vrv_cast(m_doc->GetCorrespondingScore(page)->GetScoreDef())); - grandParent->AddChild(newMeasure); - grandParent->StableSort(StaffSort()); + page->InsertAfter(page->GetFirst(SCORE), newSystem); + + SortStaves(); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); @@ -2055,22 +2060,29 @@ bool EditorToolkitNeume::SortStaves() Object *pb = page->FindDescendantByType(PB); Object *milestoneEnd = page->FindDescendantByType(SYSTEM_MILESTONE_END); + Object *section = page->FindDescendantByType(SECTION); assert(pb); assert(milestoneEnd); + assert(section); + Object *pbParent = pb->GetParent(); Object *milestoneEndParent = milestoneEnd->GetParent(); + Object *sectionParent = section->GetParent(); int pbIdx = pbParent->GetChildIndex(pb); int milestoneEndIdx = milestoneEndParent->GetChildIndex(milestoneEnd); + int sectionIdx = sectionParent->GetChildIndex(section); pb = pbParent->DetachChild(pbIdx); milestoneEnd = milestoneEndParent->DetachChild(milestoneEndIdx); + section = sectionParent->DetachChild(sectionIdx); Object *firstSystem = page->GetFirst(SYSTEM); Object *lastSystem = page->GetLast(SYSTEM); assert(firstSystem); assert(lastSystem); - firstSystem->InsertChild(pb, 0); + firstSystem->InsertChild(section, 0); + firstSystem->InsertChild(pb, 1); lastSystem->InsertChild(milestoneEnd, lastSystem->GetChildCount()); return true; From 362c4aa352293721274f9e985187b3116209dda2 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 2 May 2024 16:41:39 -0400 Subject: [PATCH 125/383] Calculate staff rotation offset manually for accid --- src/editortoolkit_neume.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 8c9488b970a..c2c78147f4e 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2414,11 +2414,28 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx zone->SetUly(uly); zone->SetLrx(lrx); zone->SetLry(lry); + double orgRotate = staff->GetDrawingRotation(); if (!isnan(rotate)) { zone->SetRotate(rotate); } zone->Modify(); SortStaves(); + + if (staff->HasDrawingRotation()) { + ListOfObjects accids = staff->FindAllDescendantsByType(ACCID); + for (auto it = accids.begin(); it != accids.end(); ++it) { + Accid *accid = dynamic_cast(*it); + FacsimileInterface *fi = accid->GetFacsimileInterface(); + Zone *accidZone = fi->GetZone(); + double rotationOffset = (accid->GetDrawingX() - staff->GetDrawingX()) * tan(rotate * M_PI / 180.0); + if (orgRotate) { + double orgOffset = (accid->GetDrawingX() - staff->GetDrawingX()) * tan(orgRotate * M_PI / 180.0); + rotationOffset -= orgOffset; + } + accidZone->SetUly(accidZone->GetUly() + int(rotationOffset)); + accidZone->SetLry(accidZone->GetLry() + int(rotationOffset)); + } + } } else if (obj->Is(SYL)) { Syl *syl = vrv_cast(obj); From 08ed0b8f9de22982ceadc85929eab62f58a16bc7 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 2 May 2024 17:15:13 -0400 Subject: [PATCH 126/383] Add zone if has facimile --- src/editortoolkit_neume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index c2c78147f4e..d10349e1131 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1895,7 +1895,7 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) text->SetText(str); syl->AddChild(text); syllable->AddChild(syl); - if (!m_doc->HasFacsimile()) { + if (m_doc->HasFacsimile()) { // Create a default bounding box Zone *zone = new Zone(); int ulx, uly, lrx, lry; From 877731a45a07a9e408b0019827ddc80c92e881c9 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 2 May 2024 18:11:48 -0400 Subject: [PATCH 127/383] Fix if condition check for facsimile --- src/editortoolkit_neume.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index d10349e1131..0b9c7de6846 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1825,7 +1825,7 @@ bool EditorToolkitNeume::Set(std::string elementId, std::string attrType, std::s success = true; else if (AttModule::SetVisual(element, attrType, attrValue)) success = true; - if (success && !m_doc->HasFacsimile()) { + if (success && m_doc->HasFacsimile()) { m_doc->PrepareData(); m_doc->GetDrawingPage()->LayOut(true); } @@ -1992,7 +1992,7 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) pi->AdjustPitchByOffset(shift); } } - if (success && !m_doc->HasFacsimile()) { + if (success && m_doc->HasFacsimile()) { m_doc->PrepareData(); m_doc->GetDrawingPage()->LayOut(true); } @@ -2213,7 +2213,7 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) linkedSyllable->AddChild(syl); // Create default bounding box if facs - if (!m_doc->HasFacsimile()) { + if (m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx( @@ -2692,6 +2692,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e // if there are no full parents we need to make a new one to attach everything to if (fullParents.empty()) { + LogError("empty"); if (elementClass == NC) { parent = new Neume(); } @@ -2714,7 +2715,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e parent->AddChild(syl); // add a default bounding box if you need to - if (!m_doc->HasFacsimile()) { + if (m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx(parent->GetFirst(NEUME)->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -2772,7 +2773,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e std::u32string fullString = U""; for (auto it = fullParents.begin(); it != fullParents.end(); ++it) { Syl *syl = dynamic_cast((*it)->FindDescendantByType(SYL)); - if (syl != NULL && !m_doc->HasFacsimile()) { + if (syl != NULL && m_doc->HasFacsimile()) { Zone *zone = dynamic_cast(syl->GetFacsimileInterface()->GetZone()); if (fullSyl == NULL) { @@ -2806,7 +2807,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e fullText->SetText(fullString); parent->AddChild(fullSyl); - if (!m_doc->HasFacsimile()) { + if (m_doc->HasFacsimile()) { Zone *zone = dynamic_cast(fullSyl->GetFacsimileInterface()->GetZone()); zone->SetUlx(ulx); zone->SetUly(uly); @@ -2818,6 +2819,8 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e } } + LogError("2"); + // change the pitch of any pitched elements whose clef may have changed assert(newClef); ListOfObjects pitchedChildren; @@ -2832,6 +2835,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e } } } + LogError("3"); // Delete any empty parents for (auto it = parents.begin(); it != parents.end(); ++it) { @@ -3099,7 +3103,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector newParent->AddChild(syl); // Create default bounding box if facs - if (!m_doc->HasFacsimile()) { + if (m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx(el->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -3440,7 +3444,7 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) // m_editInfo.import("message", "isLigature value '" + isLigature + "' is invalid."); // return false; // } - if (success1 && success2 && !m_doc->HasFacsimile()) { + if (success1 && success2 && m_doc->HasFacsimile()) { m_doc->PrepareData(); m_doc->GetDrawingPage()->LayOut(true); } From bf899c4090118dc357bcc550e41729439bf74fe0 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sat, 4 May 2024 10:08:26 -0700 Subject: [PATCH 128/383] Humlib update --- include/hum/humlib.h | 1388 +++++++++++---------- src/hum/humlib.cpp | 2841 ++++++++++++++++++++++++++++++------------ 2 files changed, 2815 insertions(+), 1414 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 609ab1f6f65..fb231506a94 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Fri Apr 19 18:10:19 PDT 2024 +// Last Modified: Sat May 4 10:07:24 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -1638,6 +1638,9 @@ class HumdrumToken : public std::string, public HumHash { HumNum getBarlineDuration (void); HumNum getBarlineDuration (HumNum scale); + // metric-related functions: + HumNum getBeat (HumNum scale = 1); + HLp getOwner (void) const; HLp getLine (void) const { return getOwner(); } bool equalChar (int index, char ch) const; @@ -2138,8 +2141,6 @@ class HumdrumFileBase : public HumHash { HLp back (void); void makeBooleanTrackList (std::vector& spinelist, const std::string& spinestring); - bool analyzeBaseFromLines (void); - bool analyzeBaseFromTokens (void); std::vector getReferenceRecords(void); @@ -2180,6 +2181,15 @@ class HumdrumFileBase : public HumHash { static void readStringFromHttpUri (std::stringstream& inputdata, const std::string& webaddress); + bool analyzeBaseFromLines (void); + bool analyzeBaseFromTokens (void); + + bool analyzeTokens (void); + bool analyzeSpines (void); + bool analyzeLinks (void); + bool analyzeTracks (void); + bool analyzeLines (void); + protected: static int getChunk (int socket_id, std::stringstream& inputdata, @@ -2195,10 +2205,6 @@ class HumdrumFileBase : public HumHash { unsigned short int port); protected: - bool analyzeTokens (void); - bool analyzeSpines (void); - bool analyzeLinks (void); - bool analyzeTracks (void); bool adjustSpines (HumdrumLine& line, std::vector& datatype, std::vector& sinfo); @@ -2216,7 +2222,6 @@ class HumdrumFileBase : public HumHash { bool setParseError (std::stringstream& err); bool setParseError (const std::string& err); bool setParseError (const char* format, ...); - bool analyzeLines (void); // void fixMerges (int linei); protected: @@ -3378,16 +3383,17 @@ class MuseRecord : public MuseRecordBasic { // columns 20-22: time modification std::string getTimeModificationField (void); - std::string getTimeModification (void); + std::string getTimeModificationString (void); + HumNum getTimeModification (void); std::string getTimeModificationLeftField (void); std::string getTimeModificationLeftString(void); int getTimeModificationLeft (void); std::string getTimeModificationRightField(void); std::string getTimeModificationRightString(void); int getTimeModificationRight (void); - int timeModificationQ (void); - int timeModificationLeftQ (void); - int timeModificationRightQ (void); + bool timeModificationQ (void); + bool timeModificationLeftQ (void); + bool timeModificationRightQ (void); // column 23: stem direction std::string getStemDirectionField (void); @@ -3464,9 +3470,13 @@ class MuseRecord : public MuseRecordBasic { // columns 3-5: blank // columns 6-8: figure division pointer advancement (duration) + // this is the offset to the next figure and is not part + // of the note pointer advancement std::string getFigurePointerField (void); + std::string getFigurePointer (void); int figurePointerQ (void); - // same as note records: getDuration + int getFigureDuration (void); + // columns 9-12: blank @@ -3730,7 +3740,6 @@ class MuseData { std::string getError (void); bool hasError (void); - private: std::vector m_data; std::vector m_sequence; @@ -5137,39 +5146,39 @@ class MxmlMeasure { class Option_register { public: - Option_register (void); - Option_register (const string& aDefinition, char aType, - const string& aDefaultOption); - Option_register (const string& aDefinition, char aType, - const string& aDefaultOption, - const string& aModifiedOption); - Option_register (const Option_register& reg); - ~Option_register (); - - Option_register& operator=(const Option_register& reg); - void clearModified (void); - string getDefinition (void); - string getDefault (void); - string getOption (void); - string getModified (void); - string getDescription (void); - bool isModified (void); - char getType (void); - void reset (void); - void setDefault (const string& aString); - void setDefinition (const string& aString); - void setDescription (const string& aString); - void setModified (const string& aString); - void setType (char aType); - ostream& print (ostream& out); + Option_register (void); + Option_register (const std::string& aDefinition, char aType, + const std::string& aDefaultOption); + Option_register (const std::string& aDefinition, char aType, + const std::string& aDefaultOption, + const std::string& aModifiedOption); + Option_register (const Option_register& reg); + ~Option_register (); + + Option_register& operator= (const Option_register& reg); + void clearModified (void); + std::string getDefinition (void); + std::string getDefault (void); + std::string getOption (void); + std::string getModified (void); + std::string getDescription (void); + bool isModified (void); + char getType (void); + void reset (void); + void setDefault (const std::string& aString); + void setDefinition (const std::string& aString); + void setDescription (const std::string& aString); + void setModified (const std::string& aString); + void setType (char aType); + std::ostream& print (std::ostream& out); protected: - string m_definition; - string m_description; - string m_defaultOption; - string m_modifiedOption; - bool m_modifiedQ; - char m_type; + std::string m_definition; + std::string m_description; + std::string m_defaultOption; + std::string m_modifiedOption; + bool m_modifiedQ; + char m_type; }; @@ -5182,81 +5191,82 @@ class Options { Options& operator= (const Options& options); int argc (void) const; - const vector& argv (void) const; - int define (const string& aDefinition); - int define (const string& aDefinition, - const string& description); - string getArg (int index); - string getArgument (int index); + const std::vector& argv (void) const; + int define (const std::string& aDefinition); + int define (const std::string& aDefinition, + const std::string& description); + std::string getArg (int index); + std::string getArgument (int index); int getArgCount (void); int getArgumentCount (void); - vector& getArgList (vector& output); - vector& getArgumentList (vector& output); - bool getBoolean (const string& optionName); - string getCommand (void); - string getCommandLine (void); - string getDefinition (const string& optionName); - double getDouble (const string& optionName); + std::vector& getArgList (std::vector& output); + std::vector& getArgumentList (std::vector& output); + bool getBoolean (const std::string& optionName); + std::string getCommand (void); + std::string getCommandLine (void); + std::string getDefinition (const std::string& optionName); + double getDouble (const std::string& optionName); char getFlag (void); - char getChar (const string& optionName); - float getFloat (const string& optionName); - int getInt (const string& optionName); - int getInteger (const string& optionName); - string getString (const string& optionName); - char getType (const string& optionName); + char getChar (const std::string& optionName); + float getFloat (const std::string& optionName); + int getInt (const std::string& optionName); + int getInteger (const std::string& optionName); + std::string getString (const std::string& optionName); + char getType (const std::string& optionName); int optionsArg (void); - ostream& print (ostream& out); - ostream& printOptionList (ostream& out); - ostream& printOptionListBooleanState(ostream& out); + std::ostream& print (std::ostream& out); + std::ostream& printEmscripten (std::ostream& out); + std::ostream& printOptionList (std::ostream& out); + std::ostream& printOptionListBooleanState(std::ostream& out); bool process (int error_check = 1, int suppress = 0); bool process (int argc, char** argv, - int error_check = 1, - int suppress = 0); - bool process (const vector& argv, - int error_check = 1, - int suppress = 0); - bool process (const string& argv, int error_check = 1, - int suppress = 0); + int error_check = 1, + int suppress = 0); + bool process (const std::vector& argv, + int error_check = 1, + int suppress = 0); + bool process (const std::string& argv, int error_check = 1, + int suppress = 0); void reset (void); void xverify (int argc, char** argv, - int error_check = 1, - int suppress = 0); + int error_check = 1, + int suppress = 0); void xverify (int error_check = 1, - int suppress = 0); + int suppress = 0); void setFlag (char aFlag); - void setModified (const string& optionName, - const string& optionValue); + void setModified (const std::string& optionName, + const std::string& optionValue); void setOptions (int argc, char** argv); - void setOptions (const vector& argv); - void setOptions (const string& args); + void setOptions (const std::vector& argv); + void setOptions (const std::string& args); void appendOptions (int argc, char** argv); - void appendOptions (string& args); - void appendOptions (vector& argv); - ostream& printRegister (ostream& out); - int isDefined (const string& name); - static vector tokenizeCommandLine(const string& args); + void appendOptions (std::string& args); + void appendOptions (std::vector& argv); + std::ostream& printRegister (std::ostream& out); + int isDefined (const std::string& name); + static std::vector tokenizeCommandLine(const std::string& args); bool hasParseError (void); - string getParseError (void); - ostream& getParseError (ostream& out); + std::string getParseError (void); + std::ostream& getParseError (std::ostream& out); protected: // m_argv: the list of raw command line strings including // a mix of options and non-option argument. - vector m_argv; + std::vector m_argv; // m_arguments: list of parsed command-line arguments which // are not options, or the command (argv[0]); - vector m_arguments; + std::vector m_arguments; // m_optionRegister: store for the states/values of each option. - vector m_optionRegister; + std::vector m_optionRegister; // m_optionFlag: the character which indicates an option. // Generally a dash, but could be made a slash for Windows environments. char m_optionFlag = '-'; // m_optionList: - map m_optionList; + std::map m_optionList; // // boolern options for object: @@ -5278,12 +5288,13 @@ class Options { bool m_optionsArgQ = false; // m_error: used to store errors in parsing command-line options. - stringstream m_error; + std::stringstream m_error; - private: - int getRegIndex (const string& optionName); - bool isOption (const string& aString, int& argp); + protected: + int getRegIndex (const std::string& optionName); + bool isOption (const std::string& aString, int& argp); int storeOption (int gargp, int& position, int& running); + }; #define OPTION_BOOLEAN_TYPE 'b' @@ -5305,29 +5316,29 @@ class HumTool : public Options { bool hasAnyText (void); std::string getAllText (void); - ostream& getAllText (ostream& out); + std::ostream& getAllText (std::ostream& out); bool hasHumdrumText (void); std::string getHumdrumText (void); - ostream& getHumdrumText (ostream& out); + std::ostream& getHumdrumText (std::ostream& out); void suppressHumdrumFileOutput(void); bool hasJsonText (void); std::string getJsonText (void); - ostream& getJsonText (ostream& out); + std::ostream& getJsonText (std::ostream& out); bool hasFreeText (void); std::string getFreeText (void); - ostream& getFreeText (ostream& out); + std::ostream& getFreeText (std::ostream& out); bool hasWarning (void); std::string getWarning (void); - ostream& getWarning (ostream& out); + std::ostream& getWarning (std::ostream& out); bool hasError (void); std::string getError (void); - ostream& getError (ostream& out); - void setError (const string& message); + std::ostream& getError (std::ostream& out); + void setError (const std::string& message); virtual void finally (void) { }; @@ -5508,9 +5519,9 @@ class HumdrumFileStream { HumdrumFileStream (char** list); HumdrumFileStream (const std::vector& list); HumdrumFileStream (Options& options); - HumdrumFileStream (const string& datastream); + HumdrumFileStream (const std::string& datastream); - void loadString (const string& data); + void loadString (const std::string& data); int setFileList (char** list); int setFileList (const std::vector& list); @@ -5580,7 +5591,7 @@ class HumdrumFileSet { int appendHumdrumPointer(HumdrumFile* infile); protected: - vector m_data; + std::vector m_data; void appendHumdrumFileContent(const std::string& filename, std::stringstream& inbuffer); @@ -5595,14 +5606,14 @@ class Tool_addic : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); int getInstrumentCodeIndex(HumdrumFile& infile); int getInstrumentClassIndex(HumdrumFile& infile); void updateInstrumentClassLine(HumdrumFile& infile, int codeIndex, int classIndex); std::string makeClassLine(HumdrumFile& infile, int codeIndex); - std::string getInstrumentClass(const string& code); + std::string getInstrumentClass(const std::string& code); protected: @@ -5624,7 +5635,7 @@ class Tool_addkey : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -5633,8 +5644,8 @@ class Tool_addkey : public HumTool { void getLineIndexes (HumdrumFile& infile); void insertReferenceKey (HumdrumFile& infile); void addInputKey (HumdrumFile& infile); - void insertKeyDesig (HumdrumFile& infile, const string& keyDesig); - void printKeyDesig (HumdrumFile& infile, int index, const string& desig, int direction); + void insertKeyDesig (HumdrumFile& infile, const std::string& keyDesig); + void printKeyDesig (HumdrumFile& infile, int index, const std::string& desig, int direction); private: std::string m_key; @@ -5657,7 +5668,7 @@ class Tool_addlabels : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -5686,49 +5697,46 @@ class Tool_addtempo : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); void initialize (void); - void assignTempoChanges (std::vector& tlist, - HumdrumFile& infile); - void addTempo (vector& tlist, - HumdrumFile& infile, - int measure, double tempo); - void addTempoToStart (vector& tlist, - HumdrumFile& infile, double tempo); + void assignTempoChanges (std::vector& tlist, HumdrumFile& infile); + void addTempo (std::vector& tlist, HumdrumFile& infile, int measure, double tempo, int offset); + void addTempoToStart (std::vector& tlist, HumdrumFile& infile, double tempo); private: - std::vector> m_tempos; + // tuple + std::vector> m_tempos; }; class Tool_autoaccid : public HumTool { public: - Tool_autoaccid (void); - ~Tool_autoaccid () {}; + Tool_autoaccid (void); + ~Tool_autoaccid () {}; - bool run (HumdrumFileSet& infiles); - bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: - void processFile (HumdrumFile& infile); - void initialize (void); - void addAccidentalInfo (HTp token); - void removeAccidentalQualifications(HumdrumFile& infile); - void addAccidentalQualifications(HumdrumFile& infile); - string setVisualState (const string& input, bool state); + void processFile (HumdrumFile& infile); + void initialize (void); + void addAccidentalInfo (HTp token); + void removeAccidentalQualifications(HumdrumFile& infile); + void addAccidentalQualifications(HumdrumFile& infile); + std::string setVisualState (const std::string& input, bool state); private: - bool m_visualQ; - bool m_hiddenQ; - bool m_removeQ; - bool m_cautionQ; + bool m_visualQ; + bool m_hiddenQ; + bool m_removeQ; + bool m_cautionQ; }; @@ -5736,39 +5744,39 @@ class Tool_autoaccid : public HumTool { class Tool_autobeam : public HumTool { public: - Tool_autobeam (void); - ~Tool_autobeam () {}; + Tool_autobeam (void); + ~Tool_autobeam () {}; - bool run (HumdrumFile& infile); - bool run (HumdrumFileSet& infiles); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (HumdrumFile& infile); + bool run (HumdrumFileSet& infiles); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: - void initialize (HumdrumFile& infile); - void processStrand (HTp strandstart, HTp strandend); - void processMeasure (vector& measure); - void addBeam (HTp startnote, HTp endnote); - void addBeams (HumdrumFile& infile); - void beamGraceNotes (HumdrumFile& infile); - string getBeamFromDur (HTp token, const string& text); - void removeQqMarks (HTp stok, HTp etok); - void removeQqMarks (HTp tok); - void removeBeams (HumdrumFile& infile); - void removeEdgeRests (HTp& startnote, HTp& endnote); - void breakBeamsByLyrics(HumdrumFile& infile); - void processStrandForLyrics(HTp stok, HTp etok); - bool hasSyllable (HTp token); - void splitBeam (HTp tok, HTp stok, HTp etok); - void splitBeam2 (vector& group, HTp tok); - void getBeamedNotes(vector& toks, HTp tok, HTp stok, HTp etok); - bool isLazy (vector& group); - void splitBeamLazy (vector& group, HTp tok); - void splitBeamNotLazy(vector& group, HTp tok); - void removeBeamCharacters(HTp token); + void initialize (HumdrumFile& infile); + void processStrand (HTp strandstart, HTp strandend); + void processMeasure (std::vector& measure); + void addBeam (HTp startnote, HTp endnote); + void addBeams (HumdrumFile& infile); + void beamGraceNotes (HumdrumFile& infile); + std::string getBeamFromDur (HTp token, const std::string& text); + void removeQqMarks (HTp stok, HTp etok); + void removeQqMarks (HTp tok); + void removeBeams (HumdrumFile& infile); + void removeEdgeRests (HTp& startnote, HTp& endnote); + void breakBeamsByLyrics (HumdrumFile& infile); + void processStrandForLyrics(HTp stok, HTp etok); + bool hasSyllable (HTp token); + void splitBeam (HTp tok, HTp stok, HTp etok); + void splitBeam2 (std::vector& group, HTp tok); + void getBeamedNotes (std::vector& toks, HTp tok, HTp stok, HTp etok); + bool isLazy (std::vector& group); + void splitBeamLazy (std::vector& group, HTp tok); + void splitBeamNotLazy (std::vector& group, HTp tok); + void removeBeamCharacters(HTp token); private: - std::vector > > m_timesigs; + std::vector > > m_timesigs; std::vector m_kernspines; bool m_overwriteQ = false; std::vector m_tracks; @@ -5795,61 +5803,61 @@ class Tool_autostem : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); void example (void); void usage (void); bool autostem (HumdrumFile& infile); - void getClefInfo (vector >& baseline, + void getClefInfo (std::vector>& baseline, HumdrumFile& infile); - void addStem (string& input, const string& piece); + void addStem (std::string& input, const std::string& piece); void processKernTokenStemsSimpleModel(HumdrumFile& infile, - vector >& baseline, + std::vector>& baseline, int row, int col); void removeStems (HumdrumFile& infile); void removeStem2 (HumdrumFile& infile, int row, int col); int getVoice (HumdrumFile& infile, int row, int col); - void getNotePositions (vector > >& notepos, - vector >& baseline, + void getNotePositions (std::vector>>& notepos, + std::vector>& baseline, HumdrumFile& infile); void printNotePositions (HumdrumFile& infile, - vector > >& notepos); - void getVoiceInfo (vector >& voice, HumdrumFile& infile); - void printVoiceInfo (HumdrumFile& infile, vector >& voice); + std::vector>>& notepos); + void getVoiceInfo (std::vector>& voice, HumdrumFile& infile); + void printVoiceInfo (HumdrumFile& infile, std::vector>& voice); void processKernTokenStems(HumdrumFile& infile, - vector >& baseline, int row, int col); - void getMaxLayers (vector& maxlayer, vector >& voice, + std::vector>& baseline, int row, int col); + void getMaxLayers (std::vector& maxlayer, std::vector>& voice, HumdrumFile& infile); - bool assignStemDirections (vector >& stemdir, - vector > & voice, - vector > >& notepos, + bool assignStemDirections (std::vector>& stemdir, + std::vector> & voice, + std::vector>>& notepos, HumdrumFile& infile); - void assignBasicStemDirections(vector >& stemdir, - vector >& voice, - vector > >& notepos, + void assignBasicStemDirections(std::vector>& stemdir, + std::vector>& voice, + std::vector>>& notepos, HumdrumFile& infile); - int determineChordStem (vector >& voice, - vector > >& notepos, + int determineChordStem (std::vector>& voice, + std::vector>>& notepos, HumdrumFile& infile, int row, int col); void insertStems (HumdrumFile& infile, - vector >& stemdir); + std::vector>& stemdir); void setStemDirection (HumdrumFile& infile, int row, int col, int direction); - bool getBeamState (vector >& beams, + bool getBeamState (std::vector>& beams, HumdrumFile& infile); - void countBeamStuff (const string& token, int& start, int& stop, + void countBeamStuff (const std::string& token, int& start, int& stop, int& flagr, int& flagl); - void getBeamSegments (vector >& beamednotes, - vector >& beamstates, - HumdrumFile& infile, vector maxlayer); - int getBeamDirection (vector& coords, - vector >& voice, - vector > >& notepos); - void setBeamDirection (vector >& stemdir, - vector& bnote, int direction); + void getBeamSegments (std::vector>& beamednotes, + std::vector>& beamstates, + HumdrumFile& infile, std::vector maxlayer); + int getBeamDirection (std::vector& coords, + std::vector>& voice, + std::vector>>& notepos); + void setBeamDirection (std::vector>& stemdir, + std::vector& bnote, int direction); private: int debugQ = 0; // used with --debug option @@ -5874,15 +5882,15 @@ class Tool_binroll : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); - void processStrand (vector>& roll, HTp starting, + void processStrand (std::vector>& roll, HTp starting, HTp ending); void printAnalysis (HumdrumFile& infile, - vector>& roll); + std::vector>& roll); private: HumNum m_duration; @@ -5892,35 +5900,94 @@ class Tool_binroll : public HumTool { class Tool_chantize : public HumTool { public: - Tool_chantize (void); - ~Tool_chantize () {}; + Tool_chantize (void); + ~Tool_chantize () {}; - bool run (HumdrumFileSet& infiles); - bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: - void initialize (HumdrumFile& infile); - void processFile (HumdrumFile& infile); - void outputFile (HumdrumFile& infile); - void updateKeySignatures(HumdrumFile& infile, int lineindex); - void checkDataLine (HumdrumFile& infile, int lineindex); - void clearStates (void); - void addBibliographicRecords(HumdrumFile& infile); - void deleteBreaks (HumdrumFile& infile); - void fixEditorialAccidentals(HumdrumFile& infile); - void fixInstrumentAbbreviations(HumdrumFile& infile); - void deleteDummyTranspositions(HumdrumFile& infile); - string getDate (void); - vector getTerminalRestStates(HumdrumFile& infile); - bool hasDiamondNotes (HumdrumFile& infile); + void initialize (HumdrumFile& infile); + void processFile (HumdrumFile& infile); + void outputFile (HumdrumFile& infile); + void updateKeySignatures (HumdrumFile& infile, int lineindex); + void checkDataLine (HumdrumFile& infile, int lineindex); + void clearStates (void); + void addBibliographicRecords (HumdrumFile& infile); + void deleteBreaks (HumdrumFile& infile); + void fixEditorialAccidentals (HumdrumFile& infile); + void fixInstrumentAbbreviations(HumdrumFile& infile); + void deleteDummyTranspositions (HumdrumFile& infile); + std::string getDate (void); + std::vector getTerminalRestStates (HumdrumFile& infile); + bool hasDiamondNotes (HumdrumFile& infile); private: - vector> m_pstates; - vector> m_kstates; - vector> m_estates; + std::vector> m_pstates; + std::vector> m_kstates; + std::vector> m_estates; bool m_diamondQ = false; +}; + + +class Tool_chint : public HumTool { + public: + Tool_chint (void); + ~Tool_chint () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void fillIntervalNames (void); + void fillIntervalNamesDiatonic(void); + void chromaticColoring (void); + void dissonanceColoring (void); + void getPartIntervals (std::vector& topInterval, + std::vector& botInterval, + HTp botSpine, HTp topSpine, HumdrumFile& infile); + void insertPartColors (HumdrumFile& infile, std::vector& botInterval, + std::vector& topInterval, int botTrack, int topTrack); + std::string getColorToken (int interval, HumdrumFile& infile, int line, HTp token); + std::string getIntervalToken (int interval, HumdrumFile& infile, int line); + + private: + // m_color: Color mapping for notes, indexed by base-40: + std::vector m_color; + + // m_intervals: Names of intervals indexed by base-40: + std::vector m_intervals; + + // Used in particular to avoid adding interval when both + // staves have tied notes: + std::vector m_botPitch; + std::vector m_topPitch; + + // m_intervalQ: Show interval numbers + bool m_intervalQ = false; + + // m_octaveQ: Do not collapse octaves to unisons. + bool m_octaveQ = false; + + // m_noColorBotQ: Do not colorize bottom analysis staff + bool m_noColorBotQ = false; + + // m_noColortopQ: Do not colorize top analysis staff + bool m_noColorTopQ = false; + + // m_negativeQ: Add minus sign to intervals + // when staff notes are crossed. + bool m_negativeQ = false; + + // m_middle: Add intervals between analysis staves, or actually + // below top staff. (Only effective in JavaScript compiled code.) + bool m_middleQ = true; }; @@ -5931,18 +5998,18 @@ class Tool_chooser : public HumTool { ~Tool_chooser () {}; bool run (HumdrumFileSet& infiles); - bool run (const string& indata); + bool run (const std::string& indata); bool run (HumdrumFileStream& instream); protected: void processFiles (HumdrumFileSet& infiles); void initialize (void); - void expandSegmentList (vector& field, string& fieldstring, + void expandSegmentList (std::vector& field, std::string& fieldstring, int maximum); - void processSegmentEntry(vector& field, - const string& astring, int maximum); - void removeDollarsFromString(string& buffer, int maximum); + void processSegmentEntry(std::vector& field, + const std::string& astring, int maximum); + void removeDollarsFromString(std::string& buffer, int maximum); private: @@ -5956,15 +6023,15 @@ class Tool_chord : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile, int direction); void processChord (HTp tok, int direction); void initialize (void); - void minimizeChordPitches(vector& notes, vector>& pitches); - void maximizeChordPitches(vector& notes, vector>& pitches); + void minimizeChordPitches(std::vector& notes, std::vector>& pitches); + void maximizeChordPitches(std::vector& notes, std::vector>& pitches); private: int m_direction = 0; @@ -6178,7 +6245,7 @@ class cmr_note_info { HumNum getStartTime (void); HumNum getEndTime (void); int getMidiPitch (void); - string getPitch (void); + std::string getPitch (void); HTp getToken (void); int getLineIndex (void); double getNoteStrength (void); @@ -6243,7 +6310,7 @@ class cmr_group_info { int getSyncopationCount(void); void makeInvalid (void); bool isValid (void); - string getPitch (void); + std::string getPitch (void); HumNum getEndTime (void); HumNum getGroupDuration (void); HumNum getStartTime (void); @@ -6312,7 +6379,7 @@ class Tool_cmr : public HumTool { int getGroupNoteCount (void); int getStrengthScore (void); void printStatistics (HumdrumFile& infile); - string getComposer (HumdrumFile& infile); + std::string getComposer (HumdrumFile& infile); void printSummaryStatistics (HumdrumFile& infile); void storeVegaData (HumdrumFile& infile); void printVegaPlot (void); @@ -6425,8 +6492,8 @@ class Tool_colorgroups : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -6442,8 +6509,8 @@ class Tool_colortriads : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -6485,7 +6552,7 @@ class Tool_composite : public HumTool { void assignGroups (HumdrumFile& infile); void analyzeLineGroups (HumdrumFile& infile); void analyzeLineGroup (HumdrumFile& infile, int line, - const string& target); + const std::string& target); void extractGroup (HumdrumFile& infile, const std::string &target); void getNumericGroupStates (std::vector& states, HumdrumFile& infile, const std::string& tgroup); int getGroupNoteType (HumdrumFile& infile, int line, const std::string& group); @@ -6516,8 +6583,8 @@ class Tool_composite : public HumTool { void getGroupRhythms (std::vector& rhythms, std::vector& durs, std::vector& states, HumdrumFile& infile); - int typeStringToInt (const string& value); - void addNumericAnalyses (ostream& output, HumdrumFile& infile, int line, + int typeStringToInt (const std::string& value); + void addNumericAnalyses (std::ostream& output, HumdrumFile& infile, int line, std::vector>& rhythmIndex); void analyzeOutputVariables(HumdrumFile& infile); std::string getTimeSignature (HumdrumFile& infile, int line, const std::string& group); @@ -6529,13 +6596,13 @@ class Tool_composite : public HumTool { void addTimeSignatureChanges (HumdrumFile& output, HumdrumFile& infile); void addMeterSignatureChanges (HumdrumFile& output, HumdrumFile& infile); void adjustBadCoincidenceRests (HumdrumFile& output, HumdrumFile& infile); - HTp fixBadRestRhythm (HTp token, string& rhythm, HumNum tstop, HumNum tsbot); + HTp fixBadRestRhythm (HTp token, std::string& rhythm, HumNum tstop, HumNum tsbot); std::string generateSizeLine (HumdrumFile& output, HumdrumFile& input, int line); void convertNotesToRhythms (HumdrumFile& infile); - int getEventCount (std::vector& data); - void fixTiedNotes (std::vector& data, HumdrumFile& infile); - void doOnsetAnalysisCoincidence(vector& output, - vector& inputA, vector& inputB); + int getEventCount (std::vector& data); + void fixTiedNotes (std::vector& data, HumdrumFile& infile); + void doOnsetAnalysisCoincidence(std::vector& output, + std::vector& inputA, std::vector& inputB); void checkForAutomaticGrouping (HumdrumFile& infile); // Numeric analysis functions: @@ -6543,7 +6610,7 @@ class Tool_composite : public HumTool { void doOnsetAnalyses (HumdrumFile& infile); void doOnsetAnalysis (std::vector& analysis, HumdrumFile& infile, - const string& targetGroup); + const std::string& targetGroup); void doAccentAnalyses (HumdrumFile& infile); @@ -6679,8 +6746,8 @@ class Tool_compositeold : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const std::string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -6714,13 +6781,13 @@ class Tool_compositeold : public HumTool { void getBeamedNotes (std::vector& notes, HTp starting); void getPitches (std::vector& pitches, HTp token); void addLabelsAndStria (HumdrumFile& infile); - void addLabels (HTp sstart, int labelIndex, const string& label, - int abbrIndex, const string& abbr); + void addLabels (HTp sstart, int labelIndex, const std::string& label, + int abbrIndex, const std::string& abbr); void addStria (HumdrumFile& infile, HTp spinestart); void addVerseLabels (HumdrumFile& infile, HTp spinestart); void addVerseLabels2 (HumdrumFile& infile, HTp spinestart); - bool pitchesEqual (vector& pitches1, vector& pitches2); - void mergeTremoloGroup (vector& notes, vector groups, int group); + bool pitchesEqual (std::vector& pitches1, std::vector& pitches2); + void mergeTremoloGroup (std::vector& notes, std::vector groups, int group); bool onlyAuxTremoloNotes (HumdrumFile& infile, int line); void removeAuxTremolosFromCompositeRhythm(HumdrumFile& infile); void markTogether (HumdrumFile& infile, int direction); @@ -6731,41 +6798,41 @@ class Tool_compositeold : public HumTool { void analyzeNestingDataGroups(HumdrumFile& infile, int direction); void analyzeNestingDataAll(HumdrumFile& infile, int direction); void getNestData (HTp spine, int& total, int& coincide); - void getCoincidenceRhythms(vector& rhythms, vector& coincidences, + void getCoincidenceRhythms(std::vector& rhythms, std::vector& coincidences, HumdrumFile& infile); - void fillInCoincidenceRhythm(vector& coincidences, + void fillInCoincidenceRhythm(std::vector& coincidences, HumdrumFile& infile, int direction); void processCoincidenceInterpretation(HumdrumFile& infile, HTp token); bool hasPipeRdf (HumdrumFile& infile); - void extractGroup (HumdrumFile& infile, const string &target); - void backfillGroup (vector>& curgroup, HumdrumFile& infile, - int line, int track, int subtrack, const string& group); + void extractGroup (HumdrumFile& infile, const std::string &target); + void backfillGroup (std::vector>& curgroup, HumdrumFile& infile, + int line, int track, int subtrack, const std::string& group); void analyzeComposite (HumdrumFile& infile); - void analyzeCompositeOnsets(HumdrumFile& infile, vector& groups, vector& tracks); - void analyzeCompositeAccents(HumdrumFile& infile, vector& groups, vector& tracks); - void analyzeCompositeOrnaments(HumdrumFile& infile, vector& groups, vector& tracks); - void analyzeCompositeSlurs(HumdrumFile& infile, vector& groups, vector& tracks); - void analyzeCompositeTotal(HumdrumFile& infile, vector& groups, vector& tracks); + void analyzeCompositeOnsets(HumdrumFile& infile, std::vector& groups, std::vector& tracks); + void analyzeCompositeAccents(HumdrumFile& infile, std::vector& groups, std::vector& tracks); + void analyzeCompositeOrnaments(HumdrumFile& infile, std::vector& groups, std::vector& tracks); + void analyzeCompositeSlurs(HumdrumFile& infile, std::vector& groups, std::vector& tracks); + void analyzeCompositeTotal(HumdrumFile& infile, std::vector& groups, std::vector& tracks); void getCompositeSpineStarts(std::vector& groups, HumdrumFile& infile); - std::vector getExpansionList(vector& tracks, int maxtrack, int count); - std::string makeExpansionString(vector& tracks); + std::vector getExpansionList(std::vector& tracks, int maxtrack, int count); + std::string makeExpansionString(std::vector& tracks); void doCoincidenceAnalysis(HumdrumFile& outfile, HumdrumFile& infile, int ctrack, HTp compositeoldStart); void doTotalAnalysis(HumdrumFile& outfile, HumdrumFile& infile, int ctrack); void doGroupAnalyses(HumdrumFile& outfile, HumdrumFile& infile); int countNoteOnsets(HTp token); - void doTotalOnsetAnalysis(vector& analysis, HumdrumFile& infile, - int track, vector& tracks); - void doGroupOnsetAnalyses(vector& analysisA, - vector& analysisB, + void doTotalOnsetAnalysis(std::vector& analysis, HumdrumFile& infile, + int track, std::vector& tracks); + void doGroupOnsetAnalyses(std::vector& analysisA, + std::vector& analysisB, HumdrumFile& infile); - void doCoincidenceOnsetAnalysis(vector>& analysis); - void insertAnalysesIntoFile(HumdrumFile& outfile, vector& spines, - vector& trackMap, vector& tracks); - void assignAnalysesToVdataTracks(vector*>& data, - vector& spines, HumdrumFile& outfile); + void doCoincidenceOnsetAnalysis(std::vector>& analysis); + void insertAnalysesIntoFile(HumdrumFile& outfile, std::vector& spines, + std::vector& trackMap, std::vector& tracks); + void assignAnalysesToVdataTracks(std::vector*>& data, + std::vector& spines, HumdrumFile& outfile); private: std::string m_pitch = "eR"; // pitch to display for compositeold rhythm @@ -6797,11 +6864,11 @@ class Tool_compositeold : public HumTool { bool m_analysisTotalQ = false; // used with -T option bool m_analysisQ = false; // union of -paost options bool m_nozerosQ = false; // used with -Z option - vector> m_analysisOnsets; // used with -P - vector> m_analysisAccents; // used with -A - vector> m_analysisOrnaments; // used with -O - vector> m_analysisSlurs; // used with -S - vector> m_analysisTotal; // used with -T + std::vector> m_analysisOnsets; // used with -P + std::vector> m_analysisAccents; // used with -A + std::vector> m_analysisOrnaments; // used with -O + std::vector> m_analysisSlurs; // used with -S + std::vector> m_analysisTotal; // used with -T }; @@ -6851,7 +6918,7 @@ class Tool_deg : public HumTool { static void setShowTies (bool state) { m_showTiesQ = state; } static void setShowZeros (bool state) { m_showZerosQ = state; } static void setShowOctaves (bool state) { m_octaveQ = state; } - static void setForcedKey (const string& key) { m_forcedKey = key; } + static void setForcedKey (const std::string& key) { m_forcedKey = key; } protected: // ScaleDegree class std::string generateDegDataToken (void) const; @@ -6955,16 +7022,16 @@ class Tool_deg : public HumTool { void initialize (void); bool setupSpineInfo (HumdrumFile& infile); - void prepareDegSpine (vector>& degspine, HTp kernstart, HumdrumFile& infil); + void prepareDegSpine (std::vector>& degspine, HTp kernstart, HumdrumFile& infil); void printDegScore (HumdrumFile& infile); void printDegScoreInterleavedWithInputScore(HumdrumFile& infile); std::string createOutputHumdrumLine (HumdrumFile& infile, int lineIndex); std::string prepareMergerLine (std::vector>& merge); void calculateManipulatorOutputForSpine(std::vector& lineout, std::vector& linein); std::string createRecipInterpretation(const std::string& starttok, int refLine); - std::string createDegInterpretation (const string& degtok, int refLine, bool addPreSpine); - std::string printDegInterpretation (const string& interp, HumdrumFile& infile, int lineIndex); - void getModeAndTonic (string& mode, int& b40tonic, const string& token); + std::string createDegInterpretation (const std::string& degtok, int refLine, bool addPreSpine); + std::string printDegInterpretation (const std::string& interp, HumdrumFile& infile, int lineIndex); + void getModeAndTonic (std::string& mode, int& b40tonic, const std::string& token); bool isDegAboveLine (HumdrumFile& infile, int lineIndex); bool isDegArrowLine (HumdrumFile& infile, int lineIndex); @@ -6975,15 +7042,15 @@ class Tool_deg : public HumTool { bool isDegSolfegeLine (HumdrumFile& infile, int lineIndex); bool isKeyDesignationLine (HumdrumFile& infile, int lineIndex); - void checkAboveStatus (string& value, bool arrowStatus); - void checkArrowStatus (string& value, bool arrowStatus); - void checkBoxStatus (string& value, bool arrowStatus); - void checkCircleStatus (string& value, bool arrowStatus); - void checkColorStatus (string& value, bool arrowStatus); - void checkHatStatus (string& value, bool arrowStatus); - void checkSolfegeStatus (string& value, bool arrowStatus); + void checkAboveStatus (std::string& value, bool arrowStatus); + void checkArrowStatus (std::string& value, bool arrowStatus); + void checkBoxStatus (std::string& value, bool arrowStatus); + void checkCircleStatus (std::string& value, bool arrowStatus); + void checkColorStatus (std::string& value, bool arrowStatus); + void checkHatStatus (std::string& value, bool arrowStatus); + void checkSolfegeStatus (std::string& value, bool arrowStatus); - void checkKeyDesignationStatus(string& value, int keyDesignationStatus); + void checkKeyDesignationStatus(std::string& value, int keyDesignationStatus); private: // Tool_deg class @@ -7227,15 +7294,15 @@ class Tool_double : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); void processFile (HumdrumFile& infile); void doubleRhythms (HumdrumFile& infile); void terminalBreveToTerminalLong(HumdrumFile& infile); - void processBeamsForMeasure(vector& notes); + void processBeamsForMeasure(std::vector& notes); void adjustBeams (HumdrumFile& infile); void adjustBeams (HTp sstart, HTp send); @@ -7268,7 +7335,7 @@ class NoteData { int phstart; int phend; int phnum; int slstart; int slend; int lyricnum; int tiestart; int tiecont; int tieend; - string text; + std::string text; }; @@ -7278,62 +7345,62 @@ class Tool_esac2hum : public HumTool { Tool_esac2hum (void); ~Tool_esac2hum () {}; - bool convertFile (ostream& out, const string& filename); - bool convert (ostream& out, const string& input); - bool convert (ostream& out, istream& input); + bool convertFile (std::ostream& out, const std::string& filename); + bool convert (std::ostream& out, const std::string& input); + bool convert (std::ostream& out, std::istream& input); protected: bool initialize (void); void checkOptions (Options& opts, int argc, char** argv); void example (void); - void usage (const string& command); - void convertEsacToHumdrum (ostream& out, istream& input); - bool getSong (vector& song, istream& infile, + void usage (const std::string& command); + void convertEsacToHumdrum (std::ostream& out, std::istream& input); + bool getSong (std::vector& song, std::istream& infile, int init); - void convertSong (vector& song, ostream& out); - bool getKeyInfo (vector& song, string& key, - double& mindur, int& tonic, string& meter, - ostream& out); - void printNoteData (NoteData& data, int textQ, ostream& out); - bool getNoteList (vector& song, - vector& songdata, double mindur, + void convertSong (std::vector& song, std::ostream& out); + bool getKeyInfo (std::vector& song, std::string& key, + double& mindur, int& tonic, std::string& meter, + std::ostream& out); + void printNoteData (NoteData& data, int textQ, std::ostream& out); + bool getNoteList (std::vector& song, + std::vector& songdata, double mindur, int tonic); - void getMeterInfo (string& meter, vector& numerator, - vector& denominator); - void postProcessSongData (vector& songdata, - vector& numerator,vector& denominator); - void printKeyInfo (vector& songdata, int tonic, - int textQ, ostream& out); + void getMeterInfo (std::string& meter, std::vector& numerator, + std::vector& denominator); + void postProcessSongData (std::vector& songdata, + std::vector& numerator,std::vector& denominator); + void printKeyInfo (std::vector& songdata, int tonic, + int textQ, std::ostream& out); int getAccidentalMax (int a, int b, int c); - bool printTitleInfo (vector& song, ostream& out); - void getLineRange (vector& song, const string& field, + bool printTitleInfo (std::vector& song, std::ostream& out); + void getLineRange (std::vector& song, const std::string& field, int& start, int& stop); - void printChar (unsigned char c, ostream& out); - void printBibInfo (vector& song, ostream& out); - void printString (const string& string, ostream& out); - void printSpecialChars (ostream& out); - bool placeLyrics (vector& song, - vector& songdata); - bool placeLyricPhrase (vector& songdata, - vector& lyrics, int line); - void getLyrics (vector& lyrics, const string& buffer); - void cleanupLyrics (vector& lyrics); - bool getFileContents (vector& array, const string& filename); - void chopExtraInfo (string& buffer); - void printHumdrumHeaderInfo(ostream& out, vector& song); - void printHumdrumFooterInfo(ostream& out, vector& song); + void printChar (unsigned char c, std::ostream& out); + void printBibInfo (std::vector& song, std::ostream& out); + void printString (const std::string& string, std::ostream& out); + void printSpecialChars (std::ostream& out); + bool placeLyrics (std::vector& song, + std::vector& songdata); + bool placeLyricPhrase (std::vector& songdata, + std::vector& lyrics, int line); + void getLyrics (std::vector& lyrics, const std::string& buffer); + void cleanupLyrics (std::vector& lyrics); + bool getFileContents (std::vector& array, const std::string& filename); + void chopExtraInfo (std::string& buffer); + void printHumdrumHeaderInfo(std::ostream& out, std::vector& song); + void printHumdrumFooterInfo(std::ostream& out, std::vector& song); private: int debugQ = 0; // used with --debug option int verboseQ = 0; // used with -v option int splitQ = 0; // used with -s option int firstfilenum = 1; // used with -f option - vector header; // used with -h option - vector trailer; // used with -t option - string fileextension; // used with -x option - string namebase; // used with -s option + std::vector header; // used with -h option + std::vector trailer; // used with -t option + std::string fileextension; // used with -x option + std::string namebase; // used with -s option - vector chartable; // used printChars() & printSpecialChars() + std::vector chartable; // used printChars() & printSpecialChars() int inputline = 0; }; @@ -7565,22 +7632,22 @@ class Tool_filter : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata); + bool run (const std::string& indata); bool runUniversal (HumdrumFileSet& infiles); protected: - void getCommandList (vector >& commands, + void getCommandList (std::vector >& commands, HumdrumFile& infile); void getUniversalCommandList(std::vector >& commands, HumdrumFileSet& infiles); void initialize (HumdrumFile& infile); void removeGlobalFilterLines (HumdrumFile& infile); void removeUniversalFilterLines (HumdrumFileSet& infiles); - void splitPipeline (vector& clist, const string& command); + void splitPipeline (std::vector& clist, const std::string& command); private: - string m_variant; // used with -v option. + std::string m_variant; // used with -v option. bool m_debugQ = false; // used with --debug option }; @@ -7593,17 +7660,17 @@ class Tool_fixps : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); void processFile (HumdrumFile& infile); void markEmptyVoices (HumdrumFile& infile); - void removeEmpties (vector>& newlist, HumdrumFile& infile); + void removeEmpties (std::vector>& newlist, HumdrumFile& infile); void removeDuplicateDynamics(HumdrumFile& infile); - void outputNewSpining (vector>& newlist, HumdrumFile& infile); - void printNewManipulator(HumdrumFile& infile, vector>& newlist, int line); + void outputNewSpining (std::vector>& newlist, HumdrumFile& infile); + void printNewManipulator(HumdrumFile& infile, std::vector>& newlist, int line); private: @@ -7617,8 +7684,8 @@ class Tool_flipper : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -7626,9 +7693,9 @@ class Tool_flipper : public HumTool { void processLine (HumdrumFile& infile, int index); void checkForFlipChanges(HumdrumFile& infile, int index); - bool flipSubspines (vector>& flipees); - void flipSpineTokens (vector& subtokens); - void extractFlipees (vector>& flipees, + bool flipSubspines (std::vector>& flipees); + void flipSpineTokens (std::vector& subtokens); + void extractFlipees (std::vector>& flipees, HumdrumFile& infile, int index); private: @@ -7651,8 +7718,8 @@ class Tool_gasparize : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -7663,7 +7730,7 @@ class Tool_gasparize : public HumTool { void fixInstrumentAbbreviations(HumdrumFile& infile); void addTerminalLongs (HumdrumFile& infile); void deleteDummyTranspositions(HumdrumFile& infile); - string getDate (void); + std::string getDate (void); void adjustSystemDecoration(HumdrumFile& infile); void deleteBreaks (HumdrumFile& infile); void updateKeySignatures(HumdrumFile& infile, int lineindex); @@ -7678,8 +7745,8 @@ class Tool_gasparize : public HumTool { void addMensuration (int top, HumdrumFile& infile, int i); void createEditText (HumdrumFile& infile); bool addEditStylingForText(HumdrumFile& infile, HTp sstart, HTp send); - string getEditLine (const string& text, int fieldindex, HLp line); - bool insertEditText (const string& text, HumdrumFile& infile, int line, int field); + std::string getEditLine (const std::string& text, int fieldindex, HLp line); + bool insertEditText (const std::string& text, HumdrumFile& infile, int line, int field); void adjustIntrumentNames(HumdrumFile& infile); void removeKeyDesignations(HumdrumFile& infile); void fixBarlines (HumdrumFile& infile); @@ -7692,9 +7759,9 @@ class Tool_gasparize : public HumTool { void fixTiesStartEnd(HTp starts, HTp ends); private: - vector> m_pstates; - vector> m_kstates; - vector> m_estates; + std::vector> m_pstates; + std::vector> m_kstates; + std::vector> m_estates; }; @@ -7706,8 +7773,8 @@ class Tool_grep : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -7727,8 +7794,8 @@ class Tool_half : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -7763,8 +7830,8 @@ class Tool_homorhythm : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -7773,9 +7840,9 @@ class Tool_homorhythm : public HumTool { void markHomophonicNotes(void); int getExtantVoiceCount(HumdrumFile& infile); int getOriginalVoiceCount(HumdrumFile& infile); - void addRawAnalysis (HumdrumFile& infile, vector& raw); - void addAccumulatedScores(HumdrumFile& infile, vector& score); - void addAttacks (HumdrumFile& infile, vector& attacks); + void addRawAnalysis (HumdrumFile& infile, std::vector& raw); + void addAccumulatedScores(HumdrumFile& infile, std::vector& score); + void addAttacks (HumdrumFile& infile, std::vector& attacks); void addFractionAnalysis(HumdrumFile& infile, std::vector& score); private: @@ -7798,8 +7865,8 @@ class Tool_homorhythm2 : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -7808,7 +7875,7 @@ class Tool_homorhythm2 : public HumTool { private: double m_threshold = 0.6; double m_threshold2 = 0.4; - vector m_score; + std::vector m_score; }; @@ -7819,18 +7886,18 @@ class Tool_hproof : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void markNonChordTones(HumdrumFile& infile); void processHarmSpine (HumdrumFile& infile, HTp hstart); - void markNotesInRange (HumdrumFile& infile, HTp ctoken, HTp ntoken, const string& key); - void markHarmonicTones(HTp tok, vector& cts); - void getNewKey (HTp token, HTp ntoken, string& key); + void markNotesInRange (HumdrumFile& infile, HTp ctoken, HTp ntoken, const std::string& key); + void markHarmonicTones(HTp tok, std::vector& cts); + void getNewKey (HTp token, HTp ntoken, std::string& key); private: - vector m_kernspines; + std::vector m_kernspines; }; @@ -7843,7 +7910,7 @@ class Tool_humbreak : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -7852,10 +7919,15 @@ class Tool_humbreak : public HumTool { void addBreaks (HumdrumFile& infile); void removeBreaks (HumdrumFile& infile); void convertPageToLine (HumdrumFile& infile); + void markLineBreakMeasures(HumdrumFile& infile); private: std::map m_lineMeasures; // list of measures to add line breaks to std::map m_pageMeasures; // list of measures to add page breaks to + + std::map m_lineOffset; // measure offsets for measure breaks + std::map m_pageOffset; // measure offsets for page breaks + std::string m_group = "original"; bool m_removeQ = false; bool m_page2lineQ = false; @@ -7870,11 +7942,11 @@ class Tool_humbreak : public HumTool { class TimePoint { public: // file: pointers to the file in which the index refers to - vector file; + std::vector file; // index :: A list of indexes for the lines at which the given timestamp // occurs in each file. The first index is for the reference work. - vector index; + std::vector index; // timestamp :: The duration from the start of the score to given time in score. HumNum timestamp = -1; @@ -7896,7 +7968,7 @@ class TimePoint { class NotePoint { public: HTp token = NULL; // Humdrum token that contains note - string subtoken; // string that represents not (token may be chord) + std::string subtoken; // string that represents not (token may be chord) int subindex = -1; // subtoken index of note (in chord) int measure = -1; // measure number that note is found HumNum measurequarter = -1; // distance from start of measure to note @@ -7907,7 +7979,7 @@ class NotePoint { int processed = 0; // has note been processed/matched int sourceindex = -1; // source file index for note int tpindex = -1; // timepoint index of note in source - vector matched; // indexes to the location of the note in TimePoint list. + std::vector matched; // indexes to the location of the note in TimePoint list. // the index indicate which score the match is related to, // and a value of -1 means there is no equivalent timepoint. void clear(void) { @@ -7939,13 +8011,13 @@ class Tool_humdiff : public HumTool { protected: void compareFiles (HumdrumFile& reference, HumdrumFile& alternate); - void compareTimePoints (vector>& timepoints, HumdrumFile& reference, HumdrumFile& alternate); - void extractTimePoints (vector& points, HumdrumFile& infile); - void printTimePoints (vector& timepoints); - void compareLines (HumNum minval, vector& indexes, vector>& timepoints, vector infiles); - void getNoteList (vector& notelist, HumdrumFile& infile, int line, int measure, int sourceindex, int tpindex); - int findNoteInList (NotePoint& np, vector& nps); - void printNotePoints (vector& notelist); + void compareTimePoints (std::vector>& timepoints, HumdrumFile& reference, HumdrumFile& alternate); + void extractTimePoints (std::vector& points, HumdrumFile& infile); + void printTimePoints (std::vector& timepoints); + void compareLines (HumNum minval, std::vector& indexes, std::vector>& timepoints, std::vector infiles); + void getNoteList (std::vector& notelist, HumdrumFile& infile, int line, int measure, int sourceindex, int tpindex); + int findNoteInList (NotePoint& np, std::vector& nps); + void printNotePoints (std::vector& notelist); void markNote (NotePoint& np); private: @@ -7954,8 +8026,8 @@ class Tool_humdiff : public HumTool { }; -ostream& operator<<(ostream& out, TimePoint& tp); -ostream& operator<<(ostream& out, NotePoint& np); +std::ostream& operator<<(std::ostream& out, TimePoint& tp); +std::ostream& operator<<(std::ostream& out, NotePoint& np); @@ -7966,8 +8038,8 @@ class Tool_humsheet : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); void printRowClasses (HumdrumFile& infile, int row); void printRowContents (HumdrumFile& infile, int row); @@ -8013,8 +8085,8 @@ class Tool_humsort : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -8029,8 +8101,8 @@ class Tool_humtr : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const std::string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -8185,8 +8257,8 @@ class Tool_kernify : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (void); @@ -8208,8 +8280,8 @@ class Tool_kernview : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -8500,19 +8572,19 @@ class Tool_mei2hum : public HumTool { class WordInfo { public: - string word; // text of word + std::string word; // text of word int notes = 0; // number of notes in word HumNum starttime; // start time of word HumNum endtime; // end time of word int bar = 0; // starting barline number for word - vector bars; // starting barline number for each syllable - vector syllables; // list of syllables in word with melisma - vector notecounts; // list of note counts for each syllable in word - vector starttimes; // list of start times for each syllable - vector endtimes; // list of end times for each syllable + std::vector bars; // starting barline number for each syllable + std::vector syllables; // list of syllables in word with melisma + std::vector notecounts; // list of note counts for each syllable in word + std::vector starttimes; // list of start times for each syllable + std::vector endtimes; // list of end times for each syllable HumNum duration(void) { return endtime - starttime; } - string name; - string abbreviation; + std::string name; + std::string abbreviation; int partnum = 0; void clear(void) { starttime = 0; @@ -8539,36 +8611,36 @@ class Tool_melisma : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); void processFile (HumdrumFile& infile); - void getNoteCounts (HumdrumFile& infile, vector>& counts); - void getNoteCountsForLyric (vector>& counts, HTp lyricStart); + void getNoteCounts (HumdrumFile& infile, std::vector>& counts); + void getNoteCountsForLyric (std::vector>& counts, HTp lyricStart); int getCountForSyllable (HTp token); - void replaceLyrics (HumdrumFile& infile, vector>& counts); - void markMelismas (HumdrumFile& infile, vector>& counts); + void replaceLyrics (HumdrumFile& infile, std::vector>& counts); + void markMelismas (HumdrumFile& infile, std::vector>& counts); void markMelismaNotes (HTp text, int count); - void extractWordlist (vector& wordinfo, map& wordlist, - HumdrumFile& infile, vector>& notecount); - string extractWord (WordInfo& winfo, HTp token, vector>& counts); + void extractWordlist (std::vector& wordinfo, std::map& wordlist, + HumdrumFile& infile, std::vector>& notecount); + std::string extractWord (WordInfo& winfo, HTp token, std::vector>& counts); HumNum getEndtime (HTp text); - void printWordlist (HumdrumFile& infile, vector& wordinfo, - map); + void printWordlist (HumdrumFile& infile, std::vector& wordinfo, + std::map); void initializePartInfo (HumdrumFile& infile); - void getMelismaNoteCounts (vector& ncounts, vector& mcounts, + void getMelismaNoteCounts (std::vector& ncounts, std::vector& mcounts, HumdrumFile& infile); double getScoreDuration (HumdrumFile& infile); void initBarlines (HumdrumFile& infile); private: - vector> m_endtimes; // end time of syllables indexed by line/field - vector m_names; // name of parts indexed by track - vector m_abbreviations; // abbreviation of parts indexed by track - vector m_partnums; // part number index by track - vector m_measures; // current measure number + std::vector> m_endtimes; // end time of syllables indexed by line/field + std::vector m_names; // name of parts indexed by track + std::vector m_abbreviations; // abbreviation of parts indexed by track + std::vector m_partnums; // part number index by track + std::vector m_measures; // current measure number }; @@ -8581,13 +8653,13 @@ class Tool_mens2kern : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); void initialize (void); - void processMelody (vector& melody); + void processMelody (std::vector& melody); std::string mens2kernRhythm (const std::string& rhythm, bool altera, bool perfecta, bool imperfecta, int maxima_def, int longa_def, @@ -8611,7 +8683,7 @@ class Tool_meter : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); @@ -8666,16 +8738,16 @@ class Tool_metlev : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: - void fillVoiceResults (vector >& results, + void fillVoiceResults (std::vector >& results, HumdrumFile& infile, - vector& beatlev); + std::vector& beatlev); private: - vector m_kernspines; + std::vector m_kernspines; }; @@ -8688,8 +8760,8 @@ class Tool_modori : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -8722,11 +8794,11 @@ class Tool_modori : public HumTool { void convertInstrumentAbbreviationToOriginal (HTp token); void convertInstrumentAbbreviationToRegular (HTp token); - int getPairedReference (int index, vector& keys); + int getPairedReference (int index, std::vector& keys); void storeModOriReferenceRecords (HumdrumFile& infile); void processExclusiveInterpretationLine(HumdrumFile& infile, int line); bool processStaffCompanionSpines (std::vector tokens); - bool processStaffSpines (vector& tokens); + bool processStaffSpines (std::vector& tokens); void updateLoMo (HumdrumFile& infile); void processLoMo (HTp lomo); void printModoriOutput (HumdrumFile& infile); @@ -9180,6 +9252,16 @@ class Tool_musedata2hum : public HumTool { double m_tempo = 0.0; // for initial tempo from MIDI settings bool m_aboveBelowKernRdf = false; // output RDF**kern above/below markers + // m_measureLineIndex -- keep track of index for processed + // measure for debugging. + int m_measureLineIndex = -1; + + // m_figuredOffset -- increment for the next figure bass offset. + int m_figureOffset = 0; + + // m_quarterDivisions -- currently processed $Q value. + int m_quarterDivisions = 0; + std::map m_usedReferences; std::vector m_postReferences; @@ -9482,8 +9564,8 @@ class MeasureInfo { tracks = tcount; } int num; // measure number - string stopStyle; // styling for end of last measure - string startStyle; // styling for start of first measure + std::string stopStyle; // styling for end of last measure + std::string startStyle; // styling for start of first measure int seg; // measure segment int start; // starting line of segment int stop; // ending line of segment @@ -9491,20 +9573,20 @@ class MeasureInfo { HumdrumFile* file; // musical settings at start of measure - vector sclef; // starting clef of segment - vector skeysig; // starting keysig of segment - vector skey; // starting key of segment - vector stimesig; // starting timesig of segment - vector smet; // starting met of segment - vector stempo; // starting tempo of segment + std::vector sclef; // starting clef of segment + std::vector skeysig; // starting keysig of segment + std::vector skey; // starting key of segment + std::vector stimesig; // starting timesig of segment + std::vector smet; // starting met of segment + std::vector stempo; // starting tempo of segment // musical settings at start of measure - vector eclef; // ending clef of segment - vector ekeysig; // ending keysig of segment - vector ekey; // ending key of segment - vector etimesig; // ending timesig of segment - vector emet; // ending met of segment - vector etempo; // ending tempo of segment + std::vector eclef; // ending clef of segment + std::vector ekeysig; // ending keysig of segment + std::vector ekey; // ending key of segment + std::vector etimesig; // ending timesig of segment + std::vector emet; // ending met of segment + std::vector etempo; // ending tempo of segment }; @@ -9516,62 +9598,62 @@ class Tool_myank : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); void example (void); - void usage (const string& command); + void usage (const std::string& command); void myank (HumdrumFile& infile, - vector& outmeasure); - void removeDollarsFromString(string& buffer, int maxx); - void processFieldEntry (vector& field, - const string& str, + std::vector& outmeasure); + void removeDollarsFromString(std::string& buffer, int maxx); + void processFieldEntry (std::vector& field, + const std::string& str, HumdrumFile& infile, int maxmeasure, - vector& inmeasures, - vector& inmap); - void expandMeasureOutList (vector& measureout, - vector& measurein, - HumdrumFile& infile, const string& optionstring); - void getMeasureStartStop (vector& measurelist, + std::vector& inmeasures, + std::vector& inmap); + void expandMeasureOutList (std::vector& measureout, + std::vector& measurein, + HumdrumFile& infile, const std::string& optionstring); + void getMeasureStartStop (std::vector& measurelist, HumdrumFile& infile); void printEnding (HumdrumFile& infile, int lastline, int adjlin); void printStarting (HumdrumFile& infile); void reconcileSpineBoundary(HumdrumFile& infile, int index1, int index2); void reconcileStartingPosition(HumdrumFile& infile, int index2); - void printJoinLine (vector& splits, int index, int count); + void printJoinLine (std::vector& splits, int index, int count); void printInvisibleMeasure(HumdrumFile& infile, int line); void fillGlobalDefaults (HumdrumFile& infile, - vector& measurein, - vector& inmap); + std::vector& measurein, + std::vector& inmap); void adjustGlobalInterpretations(HumdrumFile& infile, int ii, - vector& outmeasures, + std::vector& outmeasures, int index); void adjustGlobalInterpretationsStart(HumdrumFile& infile, int ii, - vector& outmeasures, + std::vector& outmeasures, int index); - void getMarkString (ostream& out, HumdrumFile& infile); + void getMarkString (std::ostream& out, HumdrumFile& infile); void printDoubleBarline (HumdrumFile& infile, int line); - void insertZerothMeasure (vector& measurelist, + void insertZerothMeasure (std::vector& measurelist, HumdrumFile& infile); - void getMetStates (vector >& metstates, + void getMetStates (std::vector >& metstates, HumdrumFile& infile); MyCoord getLocalMetInfo (HumdrumFile& infile, int row, int track); int atEndOfFile (HumdrumFile& infile, int line); void processFile (HumdrumFile& infile); int getSectionCount (HumdrumFile& infile); - void getSectionString (string& sstring, HumdrumFile& infile, + void getSectionString (std::string& sstring, HumdrumFile& infile, int sec); void collapseSpines (HumdrumFile& infile, int line); - void printMeasureStart (HumdrumFile& infile, int line, const string& style); - std::string expandMultipliers (const string& inputstring); + void printMeasureStart (HumdrumFile& infile, int line, const std::string& style); + std::string expandMultipliers (const std::string& inputstring); - vector analyzeBarNumbers (HumdrumFile& infile); + std::vector analyzeBarNumbers (HumdrumFile& infile); int getBarNumberForLineNumber(int lineNumber); int getStartLineNumber (void); int getEndLineNumber (void); - void printDataLine (HLp line, bool& startLineHandled, const vector& lastLineResolvedTokenLineIndex, const vector& lastLineDurationsFromNoteStart); + void printDataLine (HLp line, bool& startLineHandled, const std::vector& lastLineResolvedTokenLineIndex, const std::vector& lastLineDurationsFromNoteStart); private: int m_debugQ = 0; // used with --debug option @@ -9589,12 +9671,12 @@ class Tool_myank : public HumTool { int m_barnumtextQ = 0; // used with -T option int m_section = 0; // used with --section option int m_sectionCountQ = 0; // used with --section-count option - vector m_measureOutList; // used with -m option - vector m_measureInList; // used with -m option - vector > m_metstates; + std::vector m_measureOutList; // used with -m option + std::vector m_measureInList; // used with -m option + std::vector > m_metstates; - string m_lineRange; // used with -l option - vector m_barNumbersPerLine; // used with -l option + std::string m_lineRange; // used with -l option + std::vector m_barNumbersPerLine; // used with -l option bool m_hideStarting; // used with --hide-starting option bool m_hideEnding; // used with --hide-ending option @@ -9609,14 +9691,14 @@ class Tool_nproof : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); void checkForBlankLines(HumdrumFile& infile); void checkInstrumentInformation(HumdrumFile& infile); void checkKeyInformation(HumdrumFile& infile); void checkSpineTerminations(HumdrumFile& infile); - void checkForValidInstrumentCode(HTp token, vector>& instrumentList); + void checkForValidInstrumentCode(HTp token, std::vector>& instrumentList); void checkReferenceRecords(HumdrumFile& infile); protected: @@ -9647,8 +9729,8 @@ class Tool_ordergps : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (void); @@ -9677,8 +9759,8 @@ class Tool_pccount : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -9694,21 +9776,21 @@ class Tool_pccount : public HumTool { void printReverseVoiceList (void); void printColorList (void); std::string getPitchClassString (int b40); - void printVegaLiteScript (const string& jsonvar, - const string& target, - const string& datavar, + void printVegaLiteScript (const std::string& jsonvar, + const std::string& target, + const std::string& datavar, HumdrumFile& infile); - void printVegaLiteHtml (const string& jsonvar, - const string& target, - const string& datavar, + void printVegaLiteHtml (const std::string& jsonvar, + const std::string& target, + const std::string& datavar, HumdrumFile& infile); - void printVegaLitePage (const string& jsonvar, - const string& target, - const string& datavar, + void printVegaLitePage (const std::string& jsonvar, + const std::string& target, + const std::string& datavar, HumdrumFile& infile); std::string getFinal (HumdrumFile& infile); - double getPercent (const string& pitchclass); - int getCount (const string& pitchclass); + double getPercent (const std::string& pitchclass); + int getCount (const std::string& pitchclass); void setFactorMaximum (void); void setFactorNormalize (void); @@ -9750,18 +9832,18 @@ class Tool_periodicity : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); void processFile (HumdrumFile& infile); - void fillAttackGrids (HumdrumFile& infile, vector>& grids, HumNum minrhy); - void printAttackGrid (ostream& out, HumdrumFile& infile, vector>& grids, HumNum minrhy); - void doAnalysis (vector>& analysis, int level, vector& grid); - void doPeriodicityAnalysis(vector> & analysis, vector& grid, HumNum minrhy); - void printPeriodicityAnalysis(ostream& out, vector>& analysis); - void printSvgAnalysis(ostream& out, vector>& analysis, HumNum minrhy); + void fillAttackGrids (HumdrumFile& infile, std::vector>& grids, HumNum minrhy); + void printAttackGrid (std::ostream& out, HumdrumFile& infile, std::vector>& grids, HumNum minrhy); + void doAnalysis (std::vector>& analysis, int level, std::vector& grid); + void doPeriodicityAnalysis(std::vector> & analysis, std::vector& grid, HumNum minrhy); + void printPeriodicityAnalysis(std::ostream& out, std::vector>& analysis); + void printSvgAnalysis(std::ostream& out, std::vector>& analysis, HumNum minrhy); void getColorMapping(double input, double& hue, double& saturation, double& lightness); private: @@ -9776,8 +9858,8 @@ class Tool_phrase : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void analyzeSpineByRests (int index); @@ -9789,16 +9871,16 @@ class Tool_phrase : public HumTool { void removePhraseMarks (HTp start); private: - vector> m_results; - vector m_starts; + std::vector> m_results; + std::vector m_starts; HumdrumFile m_infile; - vector m_pcount; - vector m_psum; + std::vector m_pcount; + std::vector m_psum; bool m_markQ; bool m_removeQ; bool m_remove2Q; bool m_averageQ; - string m_color; + std::string m_color; }; @@ -9811,15 +9893,15 @@ class Tool_pline : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (void); void processFile (HumdrumFile& infile); - void getPlineInterpretations(HumdrumFile& infile, vector& tokens); - void plineToColor (HumdrumFile& infile, vector& tokens); + void getPlineInterpretations(HumdrumFile& infile, std::vector& tokens); + void plineToColor (HumdrumFile& infile, std::vector& tokens); void markRests (HumdrumFile& infile); void markSpineRests (HTp spineStop); void fillLineInfo (HumdrumFile& infile, std::vector>& lineInfo); @@ -9843,8 +9925,8 @@ class Tool_pnum : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -9873,8 +9955,8 @@ class Tool_recip : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -9883,10 +9965,10 @@ class Tool_recip : public HumTool { void insertAnalysisSpines (HumdrumFile& infile, HumdrumFile& cfile); private: - vector m_kernspines; + std::vector m_kernspines; bool m_graceQ = true; - string m_exinterp = "**recip"; - string m_kernpitch = "e"; + std::string m_exinterp = "**recip"; + std::string m_kernpitch = "e"; }; @@ -9899,8 +9981,8 @@ class Tool_restfill : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -9924,8 +10006,8 @@ class Tool_rid : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -9979,7 +10061,7 @@ class Tool_sab2gs : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -9998,7 +10080,7 @@ class Tool_sab2gs : public HumTool { private: bool m_hasCrossStaff = false; // Middle voice has notes/rests on bottom staff bool m_hasBelowMarker = false; // Input data has RDF**kern down marker - string m_belowMarker = "<"; // RDF**kern marker for staff down + std::string m_belowMarker = "<"; // RDF**kern marker for staff down bool m_downQ = false; // Used only *down/*Xdown for staff changes @@ -10012,8 +10094,8 @@ class Tool_satb2gs : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10030,7 +10112,7 @@ class Tool_satb2gs : public HumTool { void printHeaderLine (HumdrumFile& infile, int line, std::vector>& tracks); bool validateHeader (HumdrumFile& infile); - vector getClefs (HumdrumFile& infile, int line); + std::vector getClefs (HumdrumFile& infile, int line); }; @@ -10042,21 +10124,21 @@ class Tool_scordatura : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); void initialize (void); - void getScordaturaRdfs (vector& rdfs, HumdrumFile& infile); + void getScordaturaRdfs (std::vector& rdfs, HumdrumFile& infile); void processScordatura (HumdrumFile& infile, HTp reference); - void processScordaturas(HumdrumFile& infile, vector& rdfs); + void processScordaturas(HumdrumFile& infile, std::vector& rdfs); void flipScordaturaInfo(HTp reference, int diatonic, int chromatic); - void transposeStrand (HTp sstart, HTp sstop, const string& marker); - void transposeChord (HTp token, const string& marker); - std::string transposeNote (const string& note); - void transposeMarker (HumdrumFile& infile, const string& marker, int diatonic, int chromatic); - std::set parsePitches(const string& input); + void transposeStrand (HTp sstart, HTp sstop, const std::string& marker); + void transposeChord (HTp token, const std::string& marker); + std::string transposeNote (const std::string& note); + void transposeMarker (HumdrumFile& infile, const std::string& marker, int diatonic, int chromatic); + std::set parsePitches(const std::string& input); void markPitches (HumdrumFile& infile); void markPitches (HTp sstart, HTp sstop); void markPitches (HTp token); @@ -10107,7 +10189,7 @@ class Tool_semitones : public HumTool { int filterData(HTp token); std::vector getTieGroup(HTp token); HTp getNextNote(HTp token); - bool hasTieContinue(const string& value); + bool hasTieContinue(const std::string& value); private: @@ -10147,8 +10229,8 @@ class Tool_shed : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10168,9 +10250,9 @@ class Tool_shed : public HumTool { bool isValidDataType (HTp token); bool isValidSpine (HTp token); std::vector addToExInterpList(void); - void parseExpression (const string& value); + void parseExpression (const std::string& value); void prepareSearch (int index); - std::string getExInterp (const string& value); + std::string getExInterp (const std::string& value); private: std::vector m_searches; // search strings @@ -10213,8 +10295,8 @@ class Tool_sic : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10335,9 +10417,9 @@ class MeasureComparisonGrid { std::string getQoff2 (int index); double getScoreDuration2 (void); - ostream& printCorrelationGrid (ostream& out = std::cout); - ostream& printCorrelationDiagonal (ostream& out = std::cout); - ostream& printSvgGrid (ostream& out = std::cout); + std::ostream& printCorrelationGrid (std::ostream& out = std::cout); + std::ostream& printCorrelationDiagonal (std::ostream& out = std::cout); + std::ostream& printSvgGrid (std::ostream& out = std::cout); void getColorMapping (double input, double& hue, double& saturation, double& lightness); @@ -10356,8 +10438,8 @@ class Tool_simat : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile1, HumdrumFile& infile2); - bool run (const string& indata1, const string& indata2, ostream& out); - bool run (HumdrumFile& infile1, HumdrumFile& infile2, ostream& out); + bool run (const std::string& indata1, const std::string& indata2, std::ostream& out); + bool run (HumdrumFile& infile1, HumdrumFile& infile2, std::ostream& out); protected: void initialize (HumdrumFile& infile1, HumdrumFile& infile2); @@ -10378,8 +10460,8 @@ class Tool_slurcheck : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10397,8 +10479,8 @@ class Tool_spinetrace : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -10417,8 +10499,8 @@ class Tool_strophe : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10444,8 +10526,8 @@ class Tool_synco : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10479,8 +10561,8 @@ class Tool_tabber : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -10499,8 +10581,8 @@ class Tool_tassoize : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -10514,13 +10596,13 @@ class Tool_tassoize : public HumTool { void fixInstrumentAbbreviations(HumdrumFile& infile); void addTerminalLongs (HumdrumFile& infile); void deleteDummyTranspositions(HumdrumFile& infile); - string getDate (void); + std::string getDate (void); void adjustSystemDecoration(HumdrumFile& infile); private: - vector> m_pstates; - vector> m_kstates; - vector> m_estates; + std::vector> m_pstates; + std::vector> m_kstates; + std::vector> m_estates; }; @@ -10532,8 +10614,8 @@ class Tool_textdur : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -10541,13 +10623,13 @@ class Tool_textdur : public HumTool { void processFile (HumdrumFile& infile); void printMelismas (HumdrumFile& infile); void printDurations (HumdrumFile& infile); - void getTextSpineStarts(HumdrumFile& infile, vector& starts); - void processTextSpine (vector& starts, int index); + void getTextSpineStarts(HumdrumFile& infile, std::vector& starts); + void processTextSpine (std::vector& starts, int index); int getMelisma (HTp tok1, HTp tok2); HumNum getDuration (HTp tok1, HTp tok2); HTp getTandemKernToken(HTp token); void printInterleaved (HumdrumFile& infile); - void printInterleavedLine(HumdrumLine& line, vector& textTrack); + void printInterleavedLine(HumdrumLine& line, std::vector& textTrack); void printTokenAnalysis(HTp token); void printAnalysis (void); void printDurationAverage(void); @@ -10562,7 +10644,7 @@ class Tool_textdur : public HumTool { private: std::vector m_textStarts; std::vector m_track2column; - std::vector m_columnName; + std::vector m_columnName; std::vector> m_syllables; // List of syllables in **text/**sylba std::vector> m_durations; // List of durations excluding trailing rests @@ -10584,8 +10666,8 @@ class Tool_thru : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10595,9 +10677,9 @@ class Tool_thru : public HumTool { void example (void); void processData (HumdrumFile& infile); void usage (const char* command); - void getLabelSequence (vector& labelsequence, - const string& astring); - int getLabelIndex (vector& labels, string& key); + void getLabelSequence (std::vector& labelsequence, + const std::string& astring); + int getLabelIndex (std::vector& labels, std::string& key); void printLabelList (HumdrumFile& infile); void printLabelInfo (HumdrumFile& infile); int getBarline (HumdrumFile& infile, int line); @@ -10608,8 +10690,8 @@ class Tool_thru : public HumTool { bool m_infoQ = false; // used with -i option bool m_keepQ = false; // used with -k option bool m_quietQ = false; // used with -q option - string m_variation = ""; // used with -v option - string m_realization = ""; // used with -r option + std::string m_variation = ""; // used with -v option + std::string m_realization = ""; // used with -r option }; @@ -10621,7 +10703,7 @@ class Tool_tie : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, std::ostream& out); + bool run (const std::string& indata, std::ostream& out); bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -10658,8 +10740,8 @@ class Tool_timebase : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10682,8 +10764,8 @@ class Tool_transpose : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const std::string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: @@ -10691,14 +10773,14 @@ class Tool_transpose : public HumTool { void initialize (HumdrumFile& infile); void convertScore (HumdrumFile& infile, int style); void processFile (HumdrumFile& infile, - vector& spineprocess); + std::vector& spineprocess); void convertToConcertPitches(HumdrumFile& infile, int line, - vector& tvals); + std::vector& tvals); void convertToWrittenPitches(HumdrumFile& infile, int line, - vector& tvals); + std::vector& tvals); void printNewKeySignature (const std::string& keysig, int trans); void processInterpretationLine(HumdrumFile& infile, int line, - vector& tvals, int style); + std::vector& tvals, int style); int isKeyMarker (const std::string& str); void printNewKeyInterpretation(HumdrumLine& aRecord, int index, int transval); @@ -10712,54 +10794,54 @@ class Tool_transpose : public HumTool { void example (void); void usage (const std::string& command); void printHumdrumDataRecord (HumdrumLine& record, - vector& spineprocess); + std::vector& spineprocess); double pearsonCorrelation (int size, double* x, double* y); void doAutoTransposeAnalysis(HumdrumFile& infile); - void addToHistogramDouble (vector >& histogram, + void addToHistogramDouble (std::vector >& histogram, int pc, double start, double dur, double tdur, int segments); - double storeHistogramForTrack (vector >& histogram, + double storeHistogramForTrack (std::vector >& histogram, HumdrumFile& infile, int track, int segments); - void printHistograms (int segments, vector ktracks, - vector > >& + void printHistograms (int segments, std::vector ktracks, + std::vector > >& trackhist); - void doAutoKeyAnalysis (vector > >& + void doAutoKeyAnalysis (std::vector > >& analysis, int level, int hop, int count, - int segments, vector& ktracks, - vector > >& + int segments, std::vector& ktracks, + std::vector > >& trackhist); - void doTrackKeyAnalysis (vector >& analysis, + void doTrackKeyAnalysis (std::vector >& analysis, int level, int hop, int count, - vector >& trackhist, - vector& majorweights, - vector& minorweights); - void identifyKeyDouble (vector& correls, - vector& histogram, - vector& majorweights, - vector& minorweights); - void fillWeightsWithKostkaPayne(vector& maj, - vector& min); - void printRawTrackAnalysis (vector > >& - analysis, vector& ktracks); - void doSingleAnalysis (vector& analysis, + std::vector >& trackhist, + std::vector& majorweights, + std::vector& minorweights); + void identifyKeyDouble (std::vector& correls, + std::vector& histogram, + std::vector& majorweights, + std::vector& minorweights); + void fillWeightsWithKostkaPayne(std::vector& maj, + std::vector& min); + void printRawTrackAnalysis (std::vector > >& + analysis, std::vector& ktracks); + void doSingleAnalysis (std::vector& analysis, int startindex, int length, - vector >& trackhist, - vector& majorweights, - vector& minorweights); - void identifyKey (vector& correls, - vector& histogram, - vector& majorweights, - vector& minorweights); - void doTranspositionAnalysis(vector > >& + std::vector >& trackhist, + std::vector& majorweights, + std::vector& minorweights); + void identifyKey (std::vector& correls, + std::vector& histogram, + std::vector& majorweights, + std::vector& minorweights); + void doTranspositionAnalysis(std::vector > >& analysis); int calculateTranspositionFromKey(int targetkey, HumdrumFile& infile); void printTransposedToken (HumdrumFile& infile, int row, int col, int transval); void printTransposeInformation(HumdrumFile& infile, - vector& spineprocess, + std::vector& spineprocess, int line, int transval); int getTransposeInfo (HumdrumFile& infile, int row, int col); void printNewKernString (const std::string& string, int transval); @@ -10771,7 +10853,7 @@ class Tool_transpose : public HumTool { int currentkey = 0; int autoQ = 0; // used with --auto option int debugQ = 0; // used with --debug option - string spinestring = ""; // used with -s option + std::string spinestring = ""; // used with -s option int octave = 0; // used with -o option int concertQ = 0; // used with -C option int writtenQ = 0; // used with -W option @@ -10788,8 +10870,8 @@ class Tool_tremolo : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); @@ -10819,15 +10901,15 @@ class Tool_trillspell : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void processFile (HumdrumFile& infile); bool analyzeOrnamentAccidentals(HumdrumFile& infile); - void resetDiatonicStatesWithKeySignature(vector& states, - vector& signature); - void fillKeySignature (vector& states, const string& keysig); + void resetDiatonicStatesWithKeySignature(std::vector& states, + std::vector& signature); + void fillKeySignature (std::vector& states, const std::string& keysig); int getBase40 (int diatonic, int accidental); private: @@ -10844,8 +10926,8 @@ class Tool_tspos : public HumTool { bool run (HumdrumFileSet& infiles); bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); protected: void initialize (HumdrumFile& infile); @@ -10903,7 +10985,7 @@ class Tool_tspos : public HumTool { bool m_triadAttack = false; // used with -x option // Statistical data variables: - vector m_triadState; + std::vector m_triadState; // m_partTriadPositions -- count the number of chordal positions by // voice. The first dimention is the track number of the part, and @@ -10915,10 +10997,10 @@ class Tool_tspos : public HumTool { // 4 = count of third positions in partial triadic chords // 5 = count of root positions in partial triadic chords ("open fifths") // 6 = count of fifth positions in partial triadic chords - std::vector> m_partTriadPositions; + std::vector> m_partTriadPositions; int m_positionCount = 7; // entries in 2nd dim. of m_partTriadPositions - string m_toolName = "tspos"; + std::string m_toolName = "tspos"; std::vector m_voiceCount; // m_voice: used with -v option to limit analysis to sonorities that @@ -10933,7 +11015,33 @@ class Tool_tspos : public HumTool { bool m_questionQ = true; int m_toolCount = 0; - std::vector m_fullNames; + std::vector m_fullNames; +}; + + +class Tool_vcross : public HumTool { + public: + Tool_vcross (void); + ~Tool_vcross () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void getMidiInfo (std::vector& midis, HTp token); + void compareVoices (std::vector& higher, std::vector& lower); + void processLine (HumdrumFile& infile, int index); + + private: + bool m_redQ = false; + bool m_greenQ = false; + bool m_blueQ = false; + + }; diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 61e30070c71..9eee4f4bb42 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Fri Apr 19 18:10:19 PDT 2024 +// Last Modified: Sat May 4 10:07:24 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -2645,11 +2645,12 @@ int Convert::kernToBase40PC(const string& kerndata) { // int Convert::kernToBase40(const string& kerndata) { - int pc = Convert::kernToBase40PC(kerndata); + string trimmed = Convert::trimWhiteSpace(kerndata); + int pc = Convert::kernToBase40PC(trimmed); if (pc < 0) { return pc; } - int octave = Convert::kernToOctaveNumber(kerndata); + int octave = Convert::kernToOctaveNumber(trimmed); return pc + 40 * octave; } @@ -8796,22 +8797,36 @@ void GridStaff::setNullTokenLayer(int layerindex, SliceType type, cerr << "!!SLICE TYPE: " << (int)type << endl; } + bool errorQ = false; if (layerindex < (int)this->size()) { if ((at(layerindex) != NULL) && (at(layerindex)->getToken() != NULL)) { if ((string)*at(layerindex)->getToken() == nulltoken) { // there is already a null data token here, so don't // replace it. return; + } else { + cerr << "GRID STAFF: " << this << endl; + cerr << "Warning, replacing existing token: " + << this->at(layerindex)->getToken() + << " with a null token around time " + << nextdur + << " in layerindex " << layerindex + << endl; + errorQ = true; } - cerr << "Warning, replacing existing token: " - << *this->at(layerindex)->getToken() - << " with a null token around time " - << nextdur - << endl; } } - HumdrumToken* token = new HumdrumToken(nulltoken); - setTokenLayer(layerindex, token, nextdur); + if (errorQ) { + string original = *this->at(layerindex)->getToken(); + HumRegex hre; + hre.replaceDestructive(original, "", ".ZZZ", "g"); + string value = nulltoken + "ZZZ" + original; + HumdrumToken* token = new HumdrumToken(value); + setTokenLayer(layerindex, token, nextdur); + } else { + HumdrumToken* token = new HumdrumToken(nulltoken); + setTokenLayer(layerindex, token, nextdur); + } } @@ -35837,6 +35852,21 @@ ostream& HumdrumToken::printXmlStructureInfo(ostream& out, int level, +////////////////////////////// +// +// HumdrumToken::getBeat -- Return the beat (1 indexed) +// + +HumNum HumdrumToken::getBeat(HumNum scale) { + if (!m_address.hasOwner()) { + return 0; + } else { + return m_address.getOwner()->getBeat(scale); + } +} + + + ////////////////////////////// // // HumdrumToken::printXmlContentInfo -- Print content analysis information. @@ -39895,7 +39925,7 @@ int MuseRecord::getFigureCount(void) { ////////////////////////////// // -// getFigurePointerField -- columns 6 -- 8. +// getFigurePointerField -- columns 6-8. // string MuseRecord::getFigurePointerField(void) { @@ -39904,6 +39934,44 @@ string MuseRecord::getFigurePointerField(void) { } + +////////////////////////////// +// +// getFigurePointer -- columns 6-8 for figures, removing +// spaces. +// + +string MuseRecord::getFigurePointer(void) { + return trimSpaces(getFigurePointerField()); +} + + + +////////////////////////////// +// +// MuseRecord::getFigureDuration -- return the duration +// in ticks for figured bass (to give an offset to next +// figure which happens before another note in the score). +// + +int MuseRecord::getFigureDuration(void) { + string value = getFigurePointer(); + int output = 0; + if (value.empty()) { + return output; + } else { + try { + output = std::stoi(value); + } catch (const std::invalid_argument& e) { + cout << "Invalid integer: " << e.what() << ". Setting to 0." << endl; + output = 0; + } + } + return output; +} + + + ////////////////////////////// // // figurePointerQ -- @@ -40053,14 +40121,10 @@ string MuseRecord::getKernNoteStyle(int beams, int stems) { // place the rhythm stringstream tempdur; - int notetype = getGraphicNoteType(); - if (timeModificationLeftQ()) { - notetype = notetype / 4 * getTimeModificationLeft(); - if (timeModificationRightQ()) { - notetype = notetype * getTimeModificationRight(); - } else { - notetype = notetype * 2; - } + HumNum notetype = getGraphicNoteType(); + HumNum mod = getTimeModification(); + if (mod != 1) { + notetype *= mod; } // logical duration of the note @@ -42150,35 +42214,46 @@ string MuseRecord::getTimeModificationField(void) { ////////////////////////////// // -// MuseRecord::getTimeModification -- +// MuseRecord::getTimeModificationString -- // -string MuseRecord::getTimeModification(void) { +string MuseRecord::getTimeModificationString(void) { string output = getTimeModificationField(); - int index = 2; - while (index >= 0 && output[index] == ' ') { - output.resize(index); - index--; - } - if (output.size() > 2) { - if (output[0] == ' ') { - output[0] = output[1]; - output[1] = output[2]; - output.resize(2); - } + HumRegex hre; + if (hre.search(output, "[1-9A-Z]:[1-9A-Z]")) { + return output; } - if (output.size() > 1) { - if (output[0] == ' ') { - output[0] = output[1]; - output.resize(1); + return ""; +} + + + +////////////////////////////// +// +// MuseRecord::getTimeModification -- +// + +HumNum MuseRecord::getTimeModification(void) { + string output = getTimeModificationField(); + HumRegex hre; + if (hre.search(output, "([1-9A-Z]):([1-9A-Z])")) { + string top = hre.getMatch(1); + string bot = hre.getMatch(2); + int topint = (int)strtol(top.c_str(), NULL, 36); + int botint = (int)strtol(top.c_str(), NULL, 36); + HumNum number(topint, botint); + return number; + } else { + if (hre.search(output, "^([1-9A-Z])")) { + string value = hre.getMatch(1); + int top = (int)strtol(value.c_str(), NULL, 36); + // Time modification can be "3 " for triplets. + HumNum out(top, 2); + return out; + } else { + return 1; } } - if (output[0] == ' ') { - cerr << "Error: funny error occured in time modification " - << "(columns 20-22): " << getLine() << endl; - return ""; - } - return output; } @@ -42190,8 +42265,11 @@ string MuseRecord::getTimeModification(void) { string MuseRecord::getTimeModificationLeftField(void) { string output = getTimeModificationField(); - output.resize(1); - return output; + HumRegex hre; + if (!hre.search(output, "^[1-9A-Z]:[1-9A-Z]$")) { + return " "; + } + return output.substr(0, 1); } @@ -42203,12 +42281,11 @@ string MuseRecord::getTimeModificationLeftField(void) { string MuseRecord::getTimeModificationLeftString(void) { string output = getTimeModificationField(); - if (output[0] == ' ') { - output = ""; - } else { - output.resize(1); + HumRegex hre; + if (!hre.search(output, "^[1-9A-Z]:[1-9A-Z]$")) { + return ""; } - return output; + return output.substr(0, 1); } @@ -42221,8 +42298,8 @@ string MuseRecord::getTimeModificationLeftString(void) { int MuseRecord::getTimeModificationLeft(void) { int output = 0; string recordInfo = getTimeModificationLeftString(); - if (recordInfo[0] == ' ') { - output = 0; + if (recordInfo.empty()) { + return 1; } else { output = (int)strtol(recordInfo.c_str(), NULL, 36); } @@ -42250,13 +42327,12 @@ string MuseRecord::getTimeModificationRightField(void) { // string MuseRecord::getTimeModificationRightString(void) { + HumRegex hre; string output = getTimeModificationField(); - if (output[2] == ' ') { - output = ""; - } else { - output = output[2]; + if (!hre.search(output, "^[1-9A-Z]:[1-9A-Z]$")) { + return " "; } - return output; + return output.substr(2, 1); } @@ -42267,15 +42343,15 @@ string MuseRecord::getTimeModificationRightString(void) { // int MuseRecord::getTimeModificationRight(void) { - int output = 0; string recordInfo = getTimeModificationRightString(); - if (recordInfo[2] == ' ') { - output = 0; + HumRegex hre; + if (recordInfo.empty()) { + return 1; + } else if (!hre.search(recordInfo, "^[1-9A-Z]$")) { + return 1; } else { - string temp = recordInfo.substr(2); - output = (int)strtol(temp.c_str(), NULL, 36); + return (int)strtol(recordInfo.c_str(), NULL, 36); } - return output; } @@ -42285,15 +42361,14 @@ int MuseRecord::getTimeModificationRight(void) { // MuseRecord::timeModificationQ -- // -int MuseRecord::timeModificationQ(void) { - int output = 0; +bool MuseRecord::timeModificationQ(void) { string recordInfo = getTimeModificationField(); - if (recordInfo[0] != ' ' || recordInfo[1] != ' ' || recordInfo[2] != ' ') { - output = 1; + HumRegex hre; + if (hre.search(recordInfo, "^[1-9A-Z]:[1-9A-Z]$")) { + return true; } else { - output = 0; + return false; } - return output; } @@ -42303,15 +42378,16 @@ int MuseRecord::timeModificationQ(void) { // MuseRecord::timeModificationLeftQ -- // -int MuseRecord::timeModificationLeftQ(void) { - int output = 0; +bool MuseRecord::timeModificationLeftQ(void) { string recordInfo = getTimeModificationField(); - if (recordInfo[0] == ' ') { - output = 0; + HumRegex hre; + string value; + value.push_back(recordInfo.at(0)); + if (hre.search(value, "^[1-9A-Z]$")) { + return true; } else { - output = 1; + return false; } - return output; } @@ -42321,15 +42397,16 @@ int MuseRecord::timeModificationLeftQ(void) { // MuseRecord::timeModificationRightQ -- // -int MuseRecord::timeModificationRightQ(void) { - int output = 0; +bool MuseRecord::timeModificationRightQ(void) { string recordInfo = getTimeModificationField(); - if (recordInfo[2] == ' ') { - output = 0; + HumRegex hre; + string value; + value.push_back(recordInfo.at(0)); + if (hre.search(value, "^[1-9A-Z]$")) { + return true; } else { - output = 1; + return false; } - return output; } @@ -50996,10 +51073,91 @@ int Options::optionsArg(void) { // ostream& Options::print(ostream& out) { - for (unsigned int i=0; igetDefinition() << "\t" - << m_optionRegister[i]->getDescription() << endl; + vector declarations; + vector descriptions; + int maxlen = 0; + for (int i=0; i<(int)m_optionRegister.size(); i++) { + declarations.push_back(m_optionRegister[i]->getDefinition()); + if (maxlen < (int)declarations.back().size()) { + maxlen = (int)declarations.back().size(); + } + descriptions.push_back(m_optionRegister[i]->getDescription()); + } + int separation = 3; + + for (int i=0; i<(int)declarations.size(); i++) { + out << declarations[i]; + for (int j=(int)declarations[i].size(); j < maxlen + separation; j++) { + out << ' '; + } + out << descriptions[i] << endl; + } + return out; +} + + + +////////////////////////////// +// +// Options::printEmscripten -- Print a list of the defined options +// when compiled with Emscripten (for use in https://verovio.humdrum.org +// with JavaScript compiled code for a web browser using Humdrum data. +// + +ostream& Options::printEmscripten(ostream& out) { + vector declarations; + vector descriptions; + out << "!!@@BEGIN: PREHTML" << endl; + out << "!!" << endl; + out << "!! " << endl; + out << "!! " << endl; + out << "!! " << endl; + HumRegex hre; + for (int i=0; i<(int)m_optionRegister.size(); i++) { + out << "!! " << endl; + string definition = m_optionRegister[i]->getDefinition(); + string description = m_optionRegister[i]->getDescription(); + string option = ""; + string optionType = ""; + string defaultValue = ""; + string prefix = ""; + if (hre.search(definition, "^([^|]+).*=([a-z]):?(.*)$")) { + option = hre.getMatch(1); + if (option.length() == 0) { + prefix = ""; + } else if (option.length() == 1) { + prefix = "-"; + } else if (option.length() > 1) { + prefix = "--"; + } + optionType = hre.getMatch(2); + defaultValue = hre.getMatch(3); + + if (optionType == "b") { optionType = "boolean"; } + else if (optionType == "s") { optionType = "string"; } + else if (optionType == "i") { optionType = "integer"; } + else if (optionType == "d") { optionType = "double"; } + + hre.replaceDestructive(option, "<", "<", "g"); + hre.replaceDestructive(option, ">", ">", "g"); + hre.replaceDestructive(optionType, "<", "<", "g"); + hre.replaceDestructive(optionType, ">", ">", "g"); + hre.replaceDestructive(defaultValue, "<", "<", "g"); + hre.replaceDestructive(defaultValue, ">", ">", "g"); + hre.replaceDestructive(description, "<", "<", "g"); + hre.replaceDestructive(description, ">", ">", "g"); + + out << "!! "; + out << ""; + out << ""; + out << ""; + out << ""; + out << endl; + } + out << "!! " << endl; } + out << "!!
OptionTypeDefaultDescription
" << prefix << option << "" << optionType << " " << optionType << " " << defaultValue << " " << description << "
" << endl; + out << "!!@@END: PREHTML" << endl; return out; } @@ -51340,8 +51498,11 @@ int Options::getRegIndex(const string& optionName) { } if (optionName == "options") { + #ifndef __EMSCRIPTEN__ print(cout); - return -1; + exit(0); + #endif + return +1; } auto it = m_optionList.find(optionName); @@ -51399,6 +51560,7 @@ bool Options::isOption(const string& aString, int& argp) { #define OPTION_FORM_CONTINUE 2 int Options::storeOption(int index, int& position, int& running) { +cerr << "STORING OPTION INDEX " << index << endl; int optionForm; char tempname[1024]; char optionType = '\0'; @@ -51478,7 +51640,11 @@ int Options::storeOption(int index, int& position, int& running) { if (index >= (int)m_argv.size()) { m_error << "Error: last option requires a parameter" << endl; + #ifdef __EMSCRIPTEN__ + return +1; + #else return -1; + #endif } setModified(tempname, &m_argv[index][position]); @@ -52989,7 +53155,7 @@ string Tool_addic::getInstrumentClass(const string& code) { // Tool_addkey::Tool_addkey(void) { - define("k|key=s", "Add given key designtation to data"); + define("k|key=s", "Add given key designtation to data"); define("K|reference-key=b", "Update or add !!!key: designation, used with -k"); } @@ -53435,7 +53601,7 @@ void Tool_addlabels::processFile(HumdrumFile& infile) { ////////////////////////////// // -// Tool_addlabels::getExpsnsionIndex -- Return index that is where the +// Tool_addlabels::getExpsnsionIndex -- Return index that is where the // expansion labels and 0th label should be printed ABOVE. // @@ -53670,7 +53836,7 @@ void Tool_addlabels::addLabel(vector& llist, HumdrumFile& infile, // Tool_addtempo::Tool_addtempo(void) { - define("q|quarter-notes-per-minute=s:120", "Quarter notes per minute (or list by measure)"); + define("q|quarter-notes-per-minute=d:120.0", "Quarter notes per minute (or list by measure)"); } @@ -53735,21 +53901,27 @@ void Tool_addtempo::initialize(void) { if (hre.search(pieces[i], "^\\s$")) { continue; } - if (hre.search(pieces[i], "^\\s*m\\s*(\\d+)\\s*:\\s*([\\d.]+)\\s*$")) { + if (hre.search(pieces[i], "^\\s*m\\s*(\\d+)\\s*([a-z]?)\\s*:\\s*([\\d.]+)\\s*$")) { int measure = hre.getMatchInt(1); - double tempo = hre.getMatchDouble(2); - m_tempos.emplace_back(measure, tempo); + string soffset = hre.getMatch(2); + int offset = 0; + if (!soffset.empty()) { + offset = soffset.at(0) - 'a'; + } + double tempo = hre.getMatchDouble(3); + m_tempos.emplace_back(measure, tempo, offset); } else if (hre.search(pieces[i], "^\\s*([\\d.]+)\\s*$")) { int measure = 0; + int offset = 0; double tempo = hre.getMatchDouble(1); - m_tempos.emplace_back(measure, tempo); + m_tempos.emplace_back(measure, tempo, offset); } } - auto compareByFirst = [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }; - std::sort(m_tempos.begin(), m_tempos.end(), compareByFirst); + auto compareByFirst = [](const std::tuple& a, const std::tuple& b) { + return std::get<0>(a) < std::get<0>(b); + }; + std::sort(m_tempos.begin(), m_tempos.end(), compareByFirst); } @@ -53785,6 +53957,7 @@ void Tool_addtempo::processFile(HumdrumFile& infile) { } + ////////////////////////////// // // Tool_addtempo::assignTempoChanges -- add non-zero @@ -53795,7 +53968,7 @@ void Tool_addtempo::assignTempoChanges(vector& tlist, HumdrumFile& infil tlist.resize(infile.getLineCount()); std::fill(tlist.begin(), tlist.end(), 0.0); for (int i=0; i<(int)m_tempos.size(); i++) { - addTempo(tlist, infile, m_tempos[i].first, m_tempos[i].second); + addTempo(tlist, infile, std::get<0>(m_tempos[i]), std::get<1>(m_tempos[i]), std::get<2>(m_tempos[i])); } } @@ -53807,7 +53980,7 @@ void Tool_addtempo::assignTempoChanges(vector& tlist, HumdrumFile& infil // void Tool_addtempo::addTempo(vector& tlist, HumdrumFile& infile, - int measure, double tempo) { + int measure, double tempo, int offset) { if (measure == 0) { addTempoToStart(tlist, infile, tempo); @@ -53822,7 +53995,20 @@ void Tool_addtempo::addTempo(vector& tlist, HumdrumFile& infile, } int bar = infile[i].getBarNumber(); if (bar == measure) { - barIndex = i; + if (offset == 0) { + barIndex = i; + break; + } + int counter = 0; + for (int j=i+1; j& tlist, HumdrumFile& infile, ////////////////////////////// // -// Tool_addtempo::addTempoToStart -- +// Tool_addtempo::addTempoToStart -- Can't use letter postfix for 0 measure for now. // void Tool_addtempo::addTempoToStart(vector& tlist, @@ -53995,10 +54181,10 @@ void Tool_addtempo::addTempoToStart(vector& tlist, // Tool_autoaccid::Tool_autoaccid(void) { - define("x|visual=b", "mark visual accidentals only"); - define("y|suppressed=b", "mark hidden accidentals only"); - define("r|remove=b", "remove accidental qualifications"); - define("c|keep-cautionary|keep-courtesy|cautionary|caution|courtesy=b", "keep cautionary accidentals when removing markers"); + define("x|visual=b", "mark visual accidentals only"); + define("y|suppressed=b", "mark hidden accidentals only"); + define("r|remove=b", "remove accidental qualifications"); + define("c|keep-cautionary|keep-courtesy=b", "keep cautionary accidentals when removing markers"); } @@ -55337,20 +55523,19 @@ void Tool_autobeam::removeEdgeRests(HTp& startnote, HTp& endnote) { // Tool_autostem::Tool_autostem(void) { - define("d|debug=b", "Debugging information"); - define("r|remove=b", "Remove stems"); - define("R|removeall=b", "Remove all stems including explicit beams"); - define("o|overwrite|replace=b","Overwrite non-explicit stems in input"); - define("O|overwriteall|replaceall=b", "Overwrite all stems in input"); - define("L|no-long|not-long|not-longs=b", - "Do not put stems one whole notes or breves"); - define("u|up=b", "Middle note on staff has stem up"); - define("p|pos=b", "Display only note vertical positions on staves"); - define("v|voice=b", "Display only voice/layer information"); - define("author=b", "Program author"); - define("version=b", "Program version"); - define("example=b", "Program examples"); - define("h|help=b", "Short description"); + define("d|debug=b", "Debugging information"); + define("r|remove=b", "Remove stems"); + define("R|removeall=b", "Remove all stems including explicit beams"); + define("o|overwrite|replace=b", "Overwrite non-explicit stems in input"); + define("O|overwriteall|replaceall=b", "Overwrite all stems in input"); + define("L|no-long|not-long|not-longs=b", "Do not put stems one whole notes or breves"); + define("u|up=b", "Middle note on staff has stem up"); + define("p|pos=b", "Display only note vertical positions on staves"); + define("v|voice=b", "Display only voice/layer information"); + define("author=b", "Program author"); + define("version=b", "Program version"); + define("example=b", "Program examples"); + define("h|help=b", "Short description"); } @@ -56829,20 +57014,20 @@ void Tool_binroll::processStrand(vector>& roll, HTp starting, // Tool_chantize::Tool_chantize(void) { - define("R|no-reference-records=b", "Do not add reference records"); - define("r|only-add-reference-records=b", "Only add reference records"); + define("R|no-reference-records=b", "do not add reference records"); + define("r|only-add-reference-records=b", "only add reference records"); - define("B|do-not-delete-breaks=b", "Do not delete system/page break markers"); - define("b|only-delete-breaks=b", "only delete breaks"); + define("B|do-not-delete-breaks=b", "do not delete system/page break markers"); + define("b|only-delete-breaks=b", "only delete breaks"); - define("A|do-not-fix-instrument-abbreviations=b", "Do not fix instrument abbreviations"); - define("a|only-fix-instrument-abbreviations=b", "Only fix instrument abbreviations"); + define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations"); + define("a|only-fix-instrument-abbreviations=b", "only fix instrument abbreviations"); - define("E|do-not-fix-editorial-accidentals=b", "Do not fix instrument abbreviations"); - define("e|only-fix-editorial-accidentals=b", "Only fix editorial accidentals"); + define("E|do-not-fix-editorial-accidentals=b", "do not fix instrument abbreviations"); + define("e|only-fix-editorial-accidentals=b", "only fix editorial accidentals"); - define("N|do-not-remove-empty-transpositions=b", "Do not remove empty transposition instructions"); - define ("n|only-remove-empty-transpositions=b", "Only remove empty transpositions"); + define("N|do-not-remove-empty-transpositions=b", "do not remove empty transposition instructions"); + define ("n|only-remove-empty-transpositions=b", "only remove empty transpositions"); } @@ -57695,6 +57880,680 @@ bool Tool_chantize::hasDiamondNotes(HumdrumFile& infile) { +#define UNDEFINED_INTERVAL (-1000) +#define REST_INTERVAL (-1001) + +///////////////////////////////// +// +// Tool_chint::Tool_chint -- Set the recognized options for the tool. +// + +Tool_chint::Tool_chint(void) { + define("b|bottom-part=i:1", "bottom part number to colorize, 1-indexed"); + define("c|chromatic-coloring=b", "chromatic coloring"); + define("d|diatonic=b", "diatonic intervals"); + define("m|middle=b", "show diatonic intervals between staves"); + define("n|negative=b", "show diatonic intervals for cross voices"); + define("t|top-part=i:2", "top part number to colorize, 1-indexed"); + define("i|intervals=b", "display interval names"); + define("B|no-color-bottom=b", "do not color top analysis staff"); + define("T|no-color-top=b", "do not color bottom analysis staff"); + define("8|octave=b", "do not collapse P8 to P1"); +} + + + +///////////////////////////////// +// +// Tool_chint::run -- Do the main work of the tool. +// + +bool Tool_chint::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i kernSpines; + kernSpines = infile.getKernSpineStartList(); + + int maxIndex = (int)kernSpines.size() - 1; + int topIndex = -1; + int botIndex = -1; + + if (getString("top-part") == "$") { + topIndex = maxIndex; + } else { + topIndex = getInteger("top-part") - 1; + } + + if (getString("bottom-part") == "$") { + botIndex = maxIndex; + } else { + botIndex = getInteger("bottom-part") - 1; + } + + if (topIndex < botIndex) { + int temp = botIndex; + botIndex = topIndex; + topIndex = temp; + } + + if ((topIndex < 0) | (botIndex < 0)) { + return; + } + + if ((topIndex > maxIndex) | (botIndex > maxIndex)) { + return; + } + + if (topIndex == botIndex) { + return; + } + + vector topInterval; + vector botInterval; + + getPartIntervals(botInterval, topInterval, kernSpines[botIndex], kernSpines[topIndex], infile); + + int botTrack = kernSpines[botIndex]->getTrack(); + int topTrack = kernSpines[topIndex]->getTrack(); + insertPartColors(infile, botInterval, topInterval, botTrack, topTrack); +} + + + +////////////////////////////// +// +// Tool_chint::getPartIntervals -- Assuming no *x +// + +void Tool_chint::getPartIntervals(vector& botInterval, vector& topInterval, + HTp botSpine, HTp topSpine, HumdrumFile& infile) { + + m_botPitch.resize(infile.getLineCount()); + m_topPitch.resize(infile.getLineCount()); + + std::fill(m_botPitch.begin(), m_botPitch.end(), "."); + std::fill(m_topPitch.begin(), m_topPitch.end(), "."); + + botInterval.resize(infile.getLineCount()); + topInterval.resize(infile.getLineCount()); + + std::fill(botInterval.begin(), botInterval.end(), UNDEFINED_INTERVAL); + std::fill(topInterval.begin(), topInterval.end(), UNDEFINED_INTERVAL); + + HumRegex hre; + HTp current = botSpine->getNextToken(); + int ttrack = topSpine->getTrack(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + + HTp bot = current; + int line = current->getLineIndex(); + HTp top = NULL; + int botField = current->getFieldIndex(); + for (int i=botField+1; igetTrack(); + if (track != ttrack) { + continue; + } + top = token; + break; + } + + if (!top) { + cerr << "TOP TOKEN IS NULL. BOTTOM TOKEN: " << bot << endl; + return; + } + + HTp botResolve = bot; + HTp topResolve = top; + if (botResolve->isNull()) { + botResolve = botResolve->resolveNull(); + } + if (topResolve->isNull()) { + topResolve = topResolve->resolveNull(); + } + if ((!botResolve) || botResolve->isNull()) { + current = current->getNextToken(); + continue; + } + if ((!topResolve) || topResolve->isNull()) { + current = current->getNextToken(); + continue; + } + + if (botResolve->isRest()) { + topInterval[line] = REST_INTERVAL; + botInterval[line] = REST_INTERVAL; + current = current->getNextToken(); + continue; + } + + if (topResolve->isRest()) { + botInterval[line] = REST_INTERVAL; + topInterval[line] = REST_INTERVAL; + current = current->getNextToken(); + continue; + } + + m_botPitch[line] = hre.replaceDestructive(*botResolve, "", " .*"); + m_topPitch[line] = hre.replaceDestructive(*topResolve, "", " .*"); + + int botB40 = abs(botResolve->getBase40Pitch()); + int topB40 = abs(topResolve->getBase40Pitch()); + + int difference = topB40 - botB40; + int negative = 1; + if (difference < 0) { + difference = -difference; + negative = -1; + } + int difference2 = difference % 40; + if (m_octaveQ && (difference2 == 0)) { + if (difference != 0) { + difference2 = 40; + } + } + + botInterval.at(line) = negative * difference2; + topInterval.at(line) = negative * difference2; + + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// insertPartColors -- +// + +void Tool_chint::insertPartColors(HumdrumFile& infile, vector& botInterval, + vector& topInterval, int botTrack, int topTrack) { + + for (int i=0; i output; + output.clear(); + int fields = infile[i].getFieldCount(); + bool botUsed = false; + bool topUsed = false; + bool intervalUsed = false; + for (int j = fields - 1; j >= 0; j--) { + HTp token = infile.token(i, j); + int track = token->getTrack(); + if (!botUsed && (track == botTrack)) { + botUsed = true; + if (!m_noColorBotQ) { + if (token->isNull()) { + output.push_back("."); + } else { + output.push_back(getColorToken(botInterval[i], infile, i, token)); + } + } + } + if (!topUsed && (track == topTrack)) { + topUsed = true; + if (!m_noColorTopQ) { + output.push_back(getColorToken(topInterval[i], infile, i, token)); + } + if ((!intervalUsed) && m_intervalQ) { + intervalUsed = true; + if (token->isNull()) { + output.push_back("."); + } else { + output.push_back(getIntervalToken(topInterval[i], infile, i)); + } + } + } + output.push_back(*token); + } + for (int i=(int)output.size() - 1; i>=0; i--) { + m_humdrum_text << output[i]; + if (i > 0) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; + } +} + + + +////////////////////////////// +// +// Tool_chint::getColorToken -- +// + +string Tool_chint::getColorToken(int interval, HumdrumFile& infile, int line, HTp token) { + int absinterval = interval; + if (absinterval > UNDEFINED_INTERVAL) { + absinterval = abs(absinterval); + } + if (infile[line].isData()) { + if (interval == REST_INTERVAL) { + return "black"; + } + if (interval == UNDEFINED_INTERVAL) { + return "."; + } + if (token->isNull()) { + return "."; + } + if (absinterval > 40) { + return "."; + } else { + return m_color.at(absinterval); + } + } + if (infile[line].isLocalComment()) { + return "!"; + } + HTp firstToken = infile.token(line, 0); + if (firstToken->compare(0, 2, "**") == 0) { + return "**color"; + } + if (*firstToken == "*-") { + return "*-"; + } + if (firstToken->compare(0, 1, "*") == 0) { + return "*"; + } + if (firstToken->isBarline()) { + return *firstToken; + } + return "ERROR"; +} + + + +////////////////////////////// +// +// Tool_chint::getIntervalToken -- +// + +string Tool_chint::getIntervalToken(int interval, HumdrumFile& infile, int line) { + int absinterval = interval; + if (interval > UNDEFINED_INTERVAL) { + absinterval = abs(absinterval); + } + HumRegex hre; + if (infile[line].isData()) { + if (absinterval < 0) { + return "."; + } + bool botTieQ = false; + bool topTieQ = false; + if (hre.search(m_botPitch[line], "[\\]_]")) { + botTieQ = true; + } + if (hre.search(m_topPitch[line], "[\\]_]")) { + topTieQ = true; + } + if (botTieQ && topTieQ) { + return "."; + } + + if (interval > 40) { + // Above an octave is not handled. + return "."; + } else { + if (m_negativeQ) { + if (interval < 0) { + return "-" + m_intervals.at(absinterval); + } else { + return m_intervals.at(absinterval); + } + } else { + return m_intervals.at(absinterval); + } + } + } + if (infile[line].isLocalComment()) { + return "!"; + } + HTp firstToken = infile.token(line, 0); + if (firstToken->compare(0, 2, "**") == 0) { + if (!m_middleQ) { + #ifdef __EMSCRIPTEN__ + return "**adata=hint"; + #else + return "**hint"; + #endif + } else { + #ifdef __EMSCRIPTEN__ + return "**bdata=hint"; + #else + return "**hint"; + #endif + } + } + if (*firstToken == "*-") { + return "*-"; + } + if (firstToken->compare(0, 1, "*") == 0) { + return "*"; + } + if (firstToken->isBarline()) { + return *firstToken; + } + return "ERROR"; +} + + + +////////////////////////////// +// +// Tool_chint::ChromaticColoring -- +// + +void Tool_chint::chromaticColoring(void) { + + m_color.resize(41); + + m_color[0] = "gray"; // P1 + m_color[1] = "lightgray"; // A1 + m_color[2] = "gainsboro"; // AA1 + m_color[3] = "white"; // unused + m_color[4] = "navy"; // d2 + m_color[5] = "darkblue"; // m2 + m_color[6] = "mediumblue"; // M2 + m_color[7] = "royalblue"; // A2 + m_color[8] = "steelblue"; // AA2 + m_color[9] = "white"; // unused + m_color[10] = "darkgreen"; // dd3 + m_color[11] = "green"; // d3 + m_color[12] = "limegreen"; // m3 + m_color[13] = "lawngreen"; // M3 + m_color[14] = "lightgreen"; // A3 + m_color[15] = "brown"; // dd4 + m_color[16] = "darkorange"; // d4 + m_color[17] = "orange"; // P4 + m_color[18] = "gold"; // A4 + m_color[19] = "yellow"; // AA4 + m_color[20] = "white"; // unused + m_color[21] = "mistyrose"; // dd5 + m_color[22] = "hotpink"; // d5 + m_color[23] = "red"; // P5 + m_color[24] = "crimson"; // A5 + m_color[25] = "firebrick"; // AA5 + m_color[26] = "white"; // unused + m_color[27] = "darkturquoise"; // d6 + m_color[28] = "turquoise"; // m6 + m_color[29] = "deepskyblue"; // M6 + m_color[30] = "lightblue"; // A6 + m_color[31] = "powderblue"; // AA6 + m_color[32] = "white"; // unused + m_color[33] = "indigo"; // d7 + m_color[34] = "purple"; // m7 + m_color[35] = "darkmagenta"; // M7 + m_color[36] = "mediumorchid"; // A7 + m_color[37] = "mediumpurple"; // AA7 + m_color[38] = "slategray"; // dd1 + m_color[39] = "dimgray"; // d1 + m_color[40] = "gray"; // P1 + +} + + + +////////////////////////////// +// +// Tool_chint::dissonanceColoring -- +// gray = P1 (unison) +// indigo = P5 (perfect intervals) +// darkviolet = P4 (other perfect intervals) +// dodgerblue = M3, M6 (major 3, 6) +// darkturquoise = m3, m6 (minor 3, 6) +// limegreen = M2, m6 (weak dissonance) +// limegreen = M2, m6 (weak dissonance) +// orange = m2, M6 (strong dissonance) +// gold = A4 (strong dissonance) +// crimson = d5 (strong dissonance) +// red = other +// + +void Tool_chint::dissonanceColoring(void) { + + m_color.resize(41); + + m_color[0] = "gray"; // P1 + m_color[1] = "red"; // A1 + m_color[2] = "red"; // AA1 + m_color[3] = "white"; // unused + m_color[4] = "red"; // dd2 + m_color[5] = "orange"; // m2 + m_color[6] = "limegreen"; // M2 + m_color[7] = "royalblue"; // A2 + m_color[8] = "steelblue"; // AA2 + m_color[9] = "white"; // AA1 + m_color[10] = "red"; // d3 + m_color[11] = "darkturquoise"; // m3 + m_color[12] = "dodgerblue"; // M3 + m_color[13] = "red"; // A3 + m_color[14] = "red"; // AA3 + m_color[15] = "red"; // dd4 + m_color[16] = "red"; // d4 + m_color[17] = "blueviolet"; // P4 + m_color[18] = "gold"; // A4 + m_color[19] = "red"; // AA4 + m_color[20] = "white"; // unused + m_color[21] = "red"; // dd5 + m_color[22] = "hotpink"; // d5 + m_color[23] = "purple"; // P5 + m_color[24] = "red"; // A5 + m_color[25] = "red"; // AA5 + m_color[26] = "white"; // unused + m_color[27] = "red"; // d6 + m_color[28] = "darkturquoise"; // m6 + m_color[29] = "dodgerblue"; // M6 + m_color[30] = "red"; // A6 + m_color[31] = "red"; // AA6 + m_color[32] = "white"; // unused + m_color[33] = "chocolate"; // d7 + m_color[34] = "limegreen"; // m7 + m_color[35] = "darkorange"; // M7 + m_color[36] = "red"; // A7 + m_color[37] = "red"; // AA7 + m_color[38] = "red"; // dd1 + m_color[39] = "red"; // d1 + m_color[40] = "gray"; // P1 + +} + + + +////////////////////////////// +// +// Tool_chint::fillIntervalNames -- +// + +void Tool_chint::fillIntervalNames(void) { + + m_intervals.resize(41); + + m_intervals[0] = "P1"; // C + m_intervals[1] = "A1"; + m_intervals[2] = "AA1"; + m_intervals[3] = "ERROR"; + m_intervals[4] = "d2"; + m_intervals[5] = "m2"; + m_intervals[6] = "M2"; // D + m_intervals[7] = "A2"; + m_intervals[8] = "AA2"; + m_intervals[9] = "ERROR"; + m_intervals[10] = "d3"; + m_intervals[11] = "m3"; + m_intervals[12] = "M3"; // E + m_intervals[13] = "A3"; + m_intervals[14] = "AA3"; + m_intervals[15] = "dd4"; + m_intervals[16] = "d4"; + m_intervals[17] = "P4"; // F + m_intervals[18] = "A4"; + m_intervals[19] = "AA4"; + m_intervals[20] = "ERROR"; + m_intervals[21] = "dd5"; + m_intervals[22] = "d5"; + m_intervals[23] = "P5"; // G + m_intervals[24] = "A5"; + m_intervals[25] = "AA5"; + m_intervals[26] = "ERROR"; + m_intervals[27] = "d6"; + m_intervals[28] = "m6"; + m_intervals[29] = "M6"; // A + m_intervals[30] = "A6"; + m_intervals[31] = "AA6"; + m_intervals[32] = "ERROR"; + m_intervals[33] = "d7"; + m_intervals[34] = "m7"; + m_intervals[35] = "M7"; // B + m_intervals[36] = "A7"; + m_intervals[37] = "AA7"; + m_intervals[38] = "dd1"; + m_intervals[39] = "d1"; + m_intervals[40] = "P8"; + +} + + + +////////////////////////////// +// +// Tool_chint::fillIntervalNamesDiatonic -- +// + +void Tool_chint::fillIntervalNamesDiatonic(void) { + + m_intervals.resize(41); + + m_intervals[0] = "1"; // C + m_intervals[1] = "A1"; + m_intervals[2] = "AA1"; + m_intervals[3] = "ERROR"; + m_intervals[4] = "d2"; + m_intervals[5] = "2"; + m_intervals[6] = "2"; // D + m_intervals[7] = "A2"; + m_intervals[8] = "AA2"; + m_intervals[9] = "ERROR"; + m_intervals[10] = "d3"; + m_intervals[11] = "3"; + m_intervals[12] = "3"; // E + m_intervals[13] = "A3"; + m_intervals[14] = "AA3"; + m_intervals[15] = "dd4"; + m_intervals[16] = "d4"; + m_intervals[17] = "4"; // F + m_intervals[18] = "A4"; + m_intervals[19] = "AA4"; + m_intervals[20] = "ERROR"; + m_intervals[21] = "dd5"; + m_intervals[22] = "d5"; + m_intervals[23] = "5"; // G + m_intervals[24] = "A5"; + m_intervals[25] = "AA5"; + m_intervals[26] = "ERROR"; + m_intervals[27] = "d6"; + m_intervals[28] = "6"; + m_intervals[29] = "6"; // A + m_intervals[30] = "A6"; + m_intervals[31] = "AA6"; + m_intervals[32] = "ERROR"; + m_intervals[33] = "d7"; + m_intervals[34] = "7"; + m_intervals[35] = "7"; // B + m_intervals[36] = "A7"; + m_intervals[37] = "AA7"; + m_intervals[38] = "dd1"; + m_intervals[39] = "d1"; + m_intervals[40] = "8"; + +} + + + + ///////////////////////////////// // @@ -58134,54 +58993,54 @@ void Tool_chord::maximizeChordPitches(vector& notes, // Tool_cint::Tool_cint(void) { - define("base-40|base40|b40|40=b", "display pitches/intervals in base-40"); - define("base-12|base12|b12|12=b", "display pitches/intervals in base-12"); - define("base-7|base7|b7|7|diatonic=b", "display pitches/intervals in base-7"); - define("g|grid|pitch|pitches=b", "display pitch grid used to calculate modules"); - define("r|rhythm=b", "display rhythmic positions of notes"); - define("f|filename=b", "display filenames with --count"); - define("raw=b", "display only modules without formatting"); - define("raw2=b", "display only modules formatted for Vishesh"); - define("c|uncross=b", "uncross crossed voices when creating modules"); - define("k|koption=s:", "Select only two spines to analyze"); - define("C|comma=b", "separate intervals by comma rather than space"); - define("retro|retrospective=b", "Retrospective module display in the score"); - define("suspension|suspensions=b", "mark suspensions"); - define("rows|row=b", "display lattices in row form"); - define("dur|duration=b", "display durations appended to harmonic interval note attacks"); - define("id=b", "ids are echoed in module data"); - define("L|interleaved-lattice=b", "display interleaved lattices"); - define("q|harmonic-parentheses=b", "put square brackets around harmonic intervals"); - define("h|harmonic-marker=b", "put h character after harmonic intervals"); - define("m|melodic-marker=b", "put m character after melodic intervals"); - define("y|melodic-parentheses=b", "put curly braces around melodic intervals"); - define("p|parentheses=b", "put parentheses around modules intervals"); - define("l|lattice=b", "calculate lattice"); - define("loc|location=b", "displayLocation"); - define("s|sustain=b", "display sustain/attack states of notes"); - define("o|octave=b", "reduce compound intervals to within an octave"); - define("H|no-harmonic=b", "don't display harmonic intervals"); - define("M|no-melodic=b", "don't display melodic intervals"); - define("t|top=b", "display top melodic interval of modules"); - define("T|top-only=b", "display only top melodic interval of modules"); - define("U|no-melodic-unisons=b", "no melodic perfect unisons"); - define("attacks|attack=b", "start/stop module chains on pairs of note attacks"); - define("z|zero=b", "display diatonic intervals with 0 offset"); - define("N|note-marker=s:@", "pass-through note marking character"); - define("x|xoption=b", "display attack/sustain information on harmonic intervals only"); - define("n|chain=i:1", "number of sequential modules"); + define("base-40|base40|b40|40=b", "display pitches/intervals in base-40"); + define("base-12|base12|b12|12=b", "display pitches/intervals in base-12"); + define("base-7|base7|b7|7|diatonic=b", "display pitches/intervals in base-7"); + define("g|grid|pitch|pitches=b", "display pitch grid used to calculate modules"); + define("r|rhythm=b", "display rhythmic positions of notes"); + define("f|filename=b", "display filenames with --count"); + define("raw=b", "display only modules without formatting"); + define("raw2=b", "display only modules formatted for Vishesh"); + define("c|uncross=b", "uncross crossed voices when creating modules"); + define("k|koption=s:", "select only two spines to analyze"); + define("C|comma=b", "separate intervals by comma rather than space"); + define("retro|retrospective=b", "retrospective module display in the score"); + define("suspension|suspensions=b", "mark suspensions"); + define("rows|row=b", "display lattices in row form"); + define("dur|duration=b", "display durations appended to harmonic interval note attacks"); + define("id=b", "ids are echoed in module data"); + define("L|interleaved-lattice=b", "display interleaved lattices"); + define("q|harmonic-parentheses=b", "put square brackets around harmonic intervals"); + define("h|harmonic-marker=b", "put h character after harmonic intervals"); + define("m|melodic-marker=b", "put m character after melodic intervals"); + define("y|melodic-parentheses=b", "put curly braces around melodic intervals"); + define("p|parentheses=b", "put parentheses around modules intervals"); + define("l|lattice=b", "calculate lattice"); + define("loc|location=b", "displayLocation"); + define("s|sustain=b", "display sustain/attack states of notes"); + define("o|octave=b", "reduce compound intervals to within an octave"); + define("H|no-harmonic=b", "don't display harmonic intervals"); + define("M|no-melodic=b", "don't display melodic intervals"); + define("t|top=b", "display top melodic interval of modules"); + define("T|top-only=b", "display only top melodic interval of modules"); + define("U|no-melodic-unisons=b", "no melodic perfect unisons"); + define("attacks|attack=b", "start/stop module chains on pairs of note attacks"); + define("z|zero=b", "display diatonic intervals with 0 offset"); + define("N|note-marker=s:@", "pass-through note marking character"); + define("x|xoption=b", "display attack/sustain information on harmonic intervals only"); + define("n|chain=i:1", "number of sequential modules"); define("R|no-rest|no-rests|norest|norests=b", "number of sequential modules"); - define("O|octave-all=b", "transpose all harmonic intervals to within an octave"); - define("chromatic=b", "display intervals as diatonic intervals with chromatic alterations"); - define("color=s:red", "color of marked notes"); - define("search=s:", "search string"); - define("mark=b", "mark matches notes from searches in data"); - define("count=b", "count matched modules from search query"); - define("debug=b"); // determine bad input line num - define("author=b"); // author of program - define("version=b"); // compilation info - define("example=b"); // example usages - define("help=b"); // short description + define("O|octave-all=b", "transpose all harmonic intervals to within an octave"); + define("chromatic=b", "display intervals as diatonic intervals with chromatic alterations"); + define("color=s:red", "color of marked notes"); + define("search=s:", "search string"); + define("mark=b", "mark matches notes from searches in data"); + define("count=b", "count matched modules from search query"); + define("debug=b", "determine bad input line number"); + define("author=b", "author of the program"); + define("version=b", "complation info"); + define("example=b", "example usages"); + define("help=b", "short description"); } @@ -61767,31 +62626,31 @@ string cmr_group_info::getPitch(void) { // Tool_cmr::Tool_cmr(void) { - define("data|raw|raw-data=b", "print analysis data"); - define("m|mark-up|marker-up=s:+", "symbol to mark peak cmr notes"); + define("data|raw|raw-data=b", "print analysis data"); + define("m|mark-up|marker-up=s:+", "symbol to mark peak cmr notes"); define("M|mark-down|marker-down=s:|", "symbol to mark anti-peak cmr notes"); - define("c|color|color-up=s:red", "color of CMR peak notes"); - define("C|color-down=s:orange", "color of CMR anti-peak notes"); - define("r|ignore-rest=d:1.0", "ignore rests smaller than given value (in whole notes)"); - define("n|number=i:3", "number of high notes in a row"); - define("N|cmr-number=b", "show enumeration number of CMR above/below starting note"); - define("d|dur|duration=d:6.0", "maximum duration between cmr note attacks in whole notes"); - define("i|info=b", "print cmr info"); - define("p|peaks=b", "detect only positive cmrs"); - define("t|troughs=b", "detect only negative cmrs"); - define("A|not-accented=b", "counts only cmrs that do not have melodic accentation"); - define("s|syncopation-weight=d:1.0","weight for syncopated notes"); - define("leap|leap-weight=d:0.5", "weight for leapng notes"); - define("l|local-peaks=b", "mark local peaks"); - define("L|only-local-peaks=b", "mark local peaks only"); - define("merge|merged|show-merged=b","print merged groups"); - define("S|summary=b", "summarize CMRs for multiple inputs"); - define("v|vega=b", "output default Vega-lite plot"); - define("V|no-html=b", "output Vega-lite plot without HTML"); - define("countplot=b", "output Vega-lite plot for CMR count"); - define("strengthplot=b", "output Vega-lite plot with strength scores"); - define("h|half=b", "durations given in half notes (mimims)"); - define("D|debug=b", "print debug information"); + define("c|color|color-up=s:red", "color of CMR peak notes"); + define("C|color-down=s:orange", "color of CMR anti-peak notes"); + define("r|ignore-rest=d:1.0", "ignore rests smaller than given value (in whole notes)"); + define("n|number=i:3", "number of high notes in a row"); + define("N|cmr-number=b", "show enumeration number of CMR above/below starting note"); + define("d|dur|duration=d:6.0", "maximum duration between cmr note attacks in whole notes"); + define("i|info=b", "print cmr info"); + define("p|peaks=b", "detect only positive cmrs"); + define("t|troughs=b", "detect only negative cmrs"); + define("A|not-accented=b", "counts only cmrs that do not have melodic accentation"); + define("s|syncopation-weight=d:1.0", "weight for syncopated notes"); + define("leap|leap-weight=d:0.5", "weight for leapng notes"); + define("l|local-peaks=b", "mark local peaks"); + define("L|only-local-peaks=b", "mark local peaks only"); + define("merge|merged|show-merged=b", "print merged groups"); + define("S|summary=b", "summarize CMRs for multiple inputs"); + define("v|vega=b", "output default Vega-lite plot"); + define("V|no-html=b", "output Vega-lite plot without HTML"); + define("countplot=b", "output Vega-lite plot for CMR count"); + define("strengthplot=b", "output Vega-lite plot with strength scores"); + define("h|half=b", "durations given in half notes (mimims)"); + define("D|debug=b", "print debug information"); } @@ -63622,10 +64481,10 @@ string Tool_cmr::getLocalLabelToken(int number, int dir) { // Tool_colorgroups::Tool_colorgroups(void) { - define("A=s:crimson", "Color for group A"); - define("B=s:dodgerblue", "Color for group B"); - define("C=s:purple", "Color for group C"); - define("command=b", "print shed command only"); + define("A=s:crimson", "color for group A"); + define("B=s:dodgerblue", "color for group B"); + define("C=s:purple", "color for group C"); + define("command=b", "print shed command only"); } @@ -63730,24 +64589,24 @@ void Tool_colorgroups::processFile(HumdrumFile& infile) { // Tool_colortriads::Tool_colortriads(void) { - define("A=b", "Do not color triads with diatonic A root"); - define("B=b", "Do not color triads with diatonic B root"); - define("C=b", "Do not color triads with diatonic C root"); - define("D=b", "Do not color triads with diatonic D root"); - define("E=b", "Do not color triads with diatonic E root"); - define("F=b", "Do not color triads with diatonic F root"); - define("G=b", "Do not color triads with diatonic G root"); - define("a=s:darkviolet", "color for A triads"); - define("b=s:darkorange", "color for B triads"); - define("c=s:limegreen", "color for C triads"); - define("d=s:royalblue", "color for D triads"); - define("e=s:crimson", "color for E triads"); - define("f=s:gold", "color for F triads"); - define("g=s:skyblue", "color for G triads"); - define("r|relative=b", "functional coloring (green = key tonic)"); - define("k|key=s", "key to transpose coloring to"); - define("commands=b", "print msearch commands only"); - define("filters=b", "print filter commands only"); + define("A=b", "do not color triads with diatonic A root"); + define("B=b", "do not color triads with diatonic B root"); + define("C=b", "do not color triads with diatonic C root"); + define("D=b", "do not color triads with diatonic D root"); + define("E=b", "do not color triads with diatonic E root"); + define("F=b", "do not color triads with diatonic F root"); + define("G=b", "do not color triads with diatonic G root"); + define("a=s:darkviolet", "color for A triads"); + define("b=s:darkorange", "color for B triads"); + define("c=s:limegreen", "color for C triads"); + define("d=s:royalblue", "color for D triads"); + define("e=s:crimson", "color for E triads"); + define("f=s:gold", "color for F triads"); + define("g=s:skyblue", "color for G triads"); + define("r|relative=b", "functional coloring (green = key tonic)"); + define("k|key=s", "key to transpose coloring to"); + define("commands=b", "print msearch commands only"); + define("filters=b", "print filter commands only"); } @@ -63993,28 +64852,28 @@ void Tool_colortriads::processFile(HumdrumFile& infile) { // Tool_composite::Tool_composite(void) { - define("debug=b", "print debug statements"); - define("a|append=b", "append data to end of line (top of system)"); - define("x|extract=b", "only output composite rhythm spines"); - define("grace=b", "include grace notes in composite rhythms"); - define("u|up-stem=b", "force notes to be up-stem"); + define("debug=b", "print debug statements"); + define("a|append=b", "append data to end of line (top of system)"); + define("x|extract=b", "only output composite rhythm spines"); + define("grace=b", "include grace notes in composite rhythms"); + define("u|up-stem=b", "force notes to be up-stem"); define("C|color-full-composite=b", "color full composite rhythm if score has groups"); define("l|score-size=d:100.0", "set staff size of input score (percent)"); define("L|analysis-size=d:100.0", "set staff size of analysis staves (percent)"); - define("o|only=s", "output notes of given group (A or B)"); - define("r|rhythms=b", "convert input score to rhythms only."); - define("e|events=b", "show event counts on analysis staves."); - define("F|no-full-composite=b", "Do not do full composite rhythm analysis"); - define("c|coincidence=b", "Do coincidence rhythm analysis"); - define("g|group|groups|grouping|groupings=b", "Do group rhythm analysis"); - define("m|mark=b", "Mark coincidences in group analysis and input score"); - define("M|mark-input=b", "Mark coincidences in input score"); + define("o|only=s", "output notes of given group (A or B)"); + define("r|rhythms=b", "convert input score to rhythms only."); + define("e|events=b", "show event counts on analysis staves."); + define("F|no-full-composite=b", "do not do full composite rhythm analysis"); + define("c|coincidence=b", "do coincidence rhythm analysis"); + define("g|group|groups=b", "do group rhythm analysis"); + define("m|mark=b", "mark coincidences in group analysis and input score"); + define("M|mark-input=b", "mark coincidences in input score"); // Numeric analysis options: - define("A|analysis|analyses=s", "List of numeric analysis features to extract"); + define("A|analysis|analyses=s", "list of numeric analysis features to extract"); // Styling for numeric analyses; - define("Z|no-zeros|no-zeroes=b", "do not show zeros in analyses."); + define("Z|no-zeros|no-zeroes=b", "do not show zeros in analyses."); } @@ -67337,31 +68196,31 @@ int Tool_composite::getEventCount(vector& data) { // Tool_compositeold::Tool_compositeold(void) { - define("a|append=b", "append data to end of line (top of system)"); - - define("P|analysis-onsets=b", "count number of note (pitch) onsets in feature"); - define("A|analysis-accents=b", "count number of accents in feature"); - define("O|analysis-ornaments=b", "count number of ornaments in feature"); - define("S|analysis-slurs=b", "count number of slur beginnings/ending in feature"); - define("T|analysis-total=b", "count total number of analysis features for each note"); - define("all|all-analyses=b", "do all analyses"); - - define("grace=b", "include grace notes in composite rhythm"); - define("u|stem-up=b", "stem-up for composite rhythm parts"); - define("x|extract=b", "only output composite rhythm spines"); - define("o|only=s", "output notes of given group"); - define("t|tremolo=b", "preserve tremolos"); - define("B|no-beam=b", "do not apply automatic beaming"); - define("G|only-groups=b", "only split composite rhythm into separate streams by group markers"); - define("g|add-groups=b", "also split composite rhythm into separate streams by group markers"); - define("c|coincidence-rhythm=b", "add coincidence rhythm for groups"); - define("m|match|together=s:limegreen", "mark alignments in group composite analyses"); - define("M=b", "equivalent to -m limegreen"); + define("a|append=b", "append data to end of line (top of system)"); + + define("P|analysis-onsets=b", "count number of note (pitch) onsets in feature"); + define("A|analysis-accents=b", "count number of accents in feature"); + define("O|analysis-ornaments=b", "count number of ornaments in feature"); + define("S|analysis-slurs=b", "count number of slur beginnings/ending in feature"); + define("T|analysis-total=b", "count total number of analysis features for each note"); + define("all|all-analyses=b", "do all analyses"); + + define("grace=b", "include grace notes in composite rhythm"); + define("u|stem-up=b", "stem-up for composite rhythm parts"); + define("x|extract=b", "only output composite rhythm spines"); + define("o|only=s", "output notes of given group"); + define("t|tremolo=b", "preserve tremolos"); + define("B|no-beam=b", "do not apply automatic beaming"); + define("G|only-groups=b", "only split composite rhythm into separate streams by group markers"); + define("g|add-groups=b", "also split composite rhythm into separate streams by group markers"); + define("c|coincidence-rhythm=b", "add coincidence rhythm for groups"); + define("m|match|together=s:limegreen", "mark alignments in group composite analyses"); + define("M=b", "equivalent to -m limegreen"); define("n|together-in-score=s:limegreen", "mark alignments in group in SCORE (not analyses)"); - define("N=b", "equivalent to -n limegreen"); - define("Z|no-zeros|no-zeroes=b", "do not show zeros in analyses."); - define("pitch=s:eR", "pitch to display for composite rhythm"); - define("debug=b", "print debugging information"); + define("N=b", "equivalent to -n limegreen"); + define("Z|no-zeros|no-zeroes=b", "do not show zeros in analyses."); + define("pitch=s:eR", "pitch to display for composite rhythm"); + define("debug=b", "print debugging information"); } @@ -71022,23 +71881,23 @@ string Tool_deg::ScaleDegree::m_forcedKey = ""; // Tool_deg::Tool_deg(void) { - define("above=b", "Display scale degrees above analyzed staff"); - define("arr|arrow|arrows=b", "Display scale degree alterations as arrows"); - define("b|boxes|box=b", "Display scale degrees in boxes"); - define("color=s", "Display color for scale degrees"); - define("c|circ|circles|circle=b", "Display scale degrees in circles"); - define("hat|caret|circumflex=b", "Display hats on scale degrees"); - define("solf|solfege=b", "Display (relative) solfege syllables instead of scale degree numbers"); - define("I|no-input=b", "Do not interleave **deg data with input score in output"); - define("kern=b", "Prefix composite rhythm **kern spine with -I option"); - define("k|kern-tracks=s", "Process only the specified kern spines"); - define("kd|dk|key-default|default-key=s", "Default (initial) key if none specified in data"); - define("kf|fk|key-force|force-key|forced-key|key-forced=s", "Use the given key for analysing deg data (ignore modulations)"); - define("o|octave|octaves|degree=b", "Encode octave information int **degree spines"); - define("r|recip=b", "Prefix output data with **recip spine with -I option"); - define("t|ties=b", "Include scale degrees for tied notes"); - define("s|spine-tracks|spine|spines|track|tracks=s", "Process only the specified spines"); - define("0|O|z|zero|zeros=b", "Show rests as scale degree 0"); + define("above=b", "display scale degrees above analyzed staff"); + define("arr|arrow|arrows=b", "display scale degree alterations as arrows"); + define("b|boxes|box=b", "display scale degrees in boxes"); + define("color=s", "display color for scale degrees"); + define("c|circ|circles|circle=b", "display scale degrees in circles"); + define("hat|caret|circumflex=b", "display hats on scale degrees"); + define("solf|solfege=b", "display (relative) solfege syllables instead of scale degree numbers"); + define("I|no-input=b", "do not interleave **deg data with input score in output"); + define("kern=b", "prefix composite rhythm **kern spine with -I option"); + define("k|kern-tracks=s", "process only the specified kern spines"); + define("kd|dk|key-default|default-key=s", "default (initial) key if none specified in data"); + define("kf|fk|key-force|force-key=s", "use the given key for analysing deg data (ignore modulations)"); + define("o|octave|octaves|degree=b", "encode octave information int **degree spines"); + define("r|recip=b", "prefix output data with **recip spine with -I option"); + define("t|ties=b", "include scale degrees for tied notes"); + define("s|spine-tracks|spine|spines|track|tracks=s", "process only the specified spines"); + define("0|O|z|zero|zeros=b", "show rests as scale degree 0"); } @@ -73479,24 +74338,24 @@ ostream& operator<<(ostream& out, Tool_deg::ScaleDegree* degree) { // Tool_dissonant::Tool_dissonant(void) { - define("r|raw=b", "print raw grid"); - define("p|percent=b", "print counts as percentages"); - define("s|suppress=b", "suppress dissonant notes"); - define("d|diatonic=b", "print diatonic grid"); - define("D|no-dissonant=b", "don't do dissonance anaysis"); - define("m|midi-pitch=b", "print midi-pitch grid"); - define("b|base-40=b", "print base-40 grid"); - define("l|metric-levels=b", "use metric levels in analysis"); - define("k|kern=b", "print kern pitch grid"); - define("V|voice-functions=b", "do cadential-voice-function analysis"); - define("v|voice-number=b", "print voice number of dissonance"); - define("f|self-number=b", "print self voice number of dissonance"); - define("debug=b", "print grid cell information"); - define("u|undirected=b", "use undirected dissonance labels"); - define("c|count=b", "count dissonances by category"); - define("i|x|e|exinterp=s:**cdata-rdiss","specify exinterp for **diss spines"); - define("color|colorize|color-by-rhythm=b", "color dissonant notes by beat level"); - define("color2|colorize2|color-by-interval=b", "color dissonant notes by dissonant interval"); + define("r|raw=b", "print raw grid"); + define("p|percent=b", "print counts as percentages"); + define("s|suppress=b", "suppress dissonant notes"); + define("d|diatonic=b", "print diatonic grid"); + define("D|no-dissonant=b", "don't do dissonance anaysis"); + define("m|midi-pitch=b", "print midi-pitch grid"); + define("b|base-40=b", "print base-40 grid"); + define("l|metric-levels=b", "use metric levels in analysis"); + define("k|kern=b", "print kern pitch grid"); + define("V|voice-functions=b", "do cadential-voice-function analysis"); + define("v|voice-number=b", "print voice number of dissonance"); + define("f|self-number=b", "print self voice number of dissonance"); + define("debug=b", "print grid cell information"); + define("u|undirected=b", "use undirected dissonance labels"); + define("c|count=b", "count dissonances by category"); + define("i|x|e|exinterp=s:**cdata-rdiss", "specify exinterp for **diss spines"); + define("color|color-by-rhythm=b", "color dissonant notes by beat level"); + define("color2|color-by-interval=b", "color dissonant notes by dissonant interval"); } @@ -76318,11 +77177,11 @@ void Tool_double::doubleRhythms(HumdrumFile& infile) { Tool_esac2hum::Tool_esac2hum(void) { define("debug=b", "print debug information"); define("v|verbose=b", "verbose output"); - define("h|header=s:", "Header filename for placement in output"); - define("t|trailer=s:", "Trailer filename for placement in output"); - define("s|split=s:file", "Split song info into separate files"); - define("x|extension=s:.krn", "Split filename extension"); - define("f|first=i:1", "Number of first split filename"); + define("h|header=s:", "header filename for placement in output"); + define("t|trailer=s:", "trailer filename for placement in output"); + define("s|split=s:file", "split song info into separate files"); + define("x|extension=s:.krn", "split filename extension"); + define("f|first=i:1", "number of first split filename"); define("author=b", "author of program"); define("version=b", "compilation info"); define("example=b", "example usages"); @@ -77973,36 +78832,34 @@ void Tool_esac2hum::printString(const string& string, ostream& out) { // Tool_extract::Tool_extract(void) { - define("P|F|S|x|exclude=s:", "Remove listed spines from output"); - define("i=s:", "Exclusive interpretation list to extract from input"); - define("I=s:", "Exclusive interpretation exclusion list"); - define("f|p|s|field|path|spine=s:", - "for extraction of particular spines"); - define("C|count=b", "print a count of the number of spines in file"); - define("c|cointerp=s:**kern", "Exclusive interpretation for cospines"); - define("g|grep=s:", "Extract spines which match a given regex."); - define("r|reverse=b", "reverse order of spines by **kern group"); - define("R=s:**kern", "reverse order of spine by exinterp group"); - define("t|trace=s:", "use a trace file to extract data"); - define("e|expand=b", "expand spines with subspines"); - define("k|kern=s", "Extract by kern spine group"); - define("K|reverse-kern=s", "Extract by kern spine group top to bottom numbering"); - define("E|expand-interp=s:", "expand subspines limited to exinterp"); - define("m|model|method=s:d", "method for extracting secondary spines"); - define("M|cospine-model=s:d", "method for extracting cospines"); - define("Y|no-editoral-rests=b", - "do not display yy marks on interpreted rests"); - define("n|name|b|blank=s:**blank", "Name if exinterp added with 0"); - define("no-empty|no-empties=b", "Suppress spines with only null data tokens"); - define("empty|empties=b", "Only keep spines with only null data tokens"); - define("spine-list=b", "Show spine list and then exit"); - define("no-rest|no-rests=b", "remove **kern spines containing only rests (and their co-spines)"); - - define("debug=b", "print debugging information"); - define("author=b"); // author of program - define("version=b"); // compilation info - define("example=b"); // example usages - define("h|help=b"); // short description + define("P|F|S|x|exclude=s:", "remove listed spines from output"); + define("i=s:", "exclusive interpretation list to extract from input"); + define("I=s:", "exclusive interpretation exclusion list"); + define("f|p|s|field|path|spine=s:", "for extraction of particular spines"); + define("C|count=b", "print a count of the number of spines in file"); + define("c|cointerp=s:**kern", "exclusive interpretation for cospines"); + define("g|grep=s:", "extract spines which match a given regex."); + define("r|reverse=b", "reverse order of spines by **kern group"); + define("R=s:**kern", "reverse order of spine by exinterp group"); + define("t|trace=s:", "use a trace file to extract data"); + define("e|expand=b", "expand spines with subspines"); + define("k|kern=s", "extract by kern spine group"); + define("K|reverse-kern=s", "extract by kern spine group top to bottom numbering"); + define("E|expand-interp=s:", "expand subspines limited to exinterp"); + define("m|model|method=s:d", "method for extracting secondary spines"); + define("M|cospine-model=s:d", "method for extracting cospines"); + define("Y|no-editoral-rests=b", "do not display yy marks on interpreted rests"); + define("n|name|b|blank=s:**blank", "name if exinterp added with 0"); + define("no-empty|no-empties=b", "suppress spines with only null data tokens"); + define("empty|empties=b", "only keep spines with only null data tokens"); + define("spine-list=b", "show spine list and then exit"); + define("no-rest|no-rests=b", "remove **kern spines containing only rests (and their co-spines)"); + + define("debug=b", "print debugging information"); + define("author=b", "author of the program"); + define("version=b", "compilation info"); + define("example=b", "example usages"); + define("h|help=b", "short description"); } @@ -80105,23 +80962,23 @@ string Tool_extract::reverseFieldString(const string& input, int maxval) { // Tool_fb::Tool_fb(void) { - define("c|compound=b", "Output reasonable figured bass numbers within octave"); - define("a|accidentals|accid|acc=b", "Display accidentals in front of the numbers"); - define("b|base|base-track=i:1", "Number of the base kern track (compare with -k)"); - define("i|intervallsatz=b", "Display numbers under their voice instead of under the base staff"); - define("o|sort|order=b", "Sort figured bass numbers by size"); - define("l|lowest=b", "Use lowest note as base note"); - define("n|normalize=b", "Remove number 8 and doubled numbers; adds -co"); - define("r|reduce|abbreviate|abbr=b", "Use abbreviated figures; adds -nco"); - define("t|ties=b", "Hide numbers without attack or changing base (needs -i)"); - define("f|figuredbass=b", "Shortcut for -acorn3"); - define("3|hide-three=b", "Hide number 3 if it has an accidental"); - define("m|negative=b", "Show negative numbers"); - define("above=b", "Show numbers above the staff (**fba)"); - define("rate=s:", "Rate to display the numbers (use a **recip value, e.g. 4, 4.)"); - define("k|kern-tracks=s", "Process only the specified kern spines"); + define("c|compound=b", "output reasonable figured bass numbers within octave"); + define("a|accidentals|accid|acc=b", "display accidentals in front of the numbers"); + define("b|base|base-track=i:1", "number of the base kern track (compare with -k)"); + define("i|intervallsatz=b", "display numbers under their voice instead of under the base staff"); + define("o|sort|order=b", "sort figured bass numbers by size"); + define("l|lowest=b", "use lowest note as base note"); + define("n|normalize=b", "remove number 8 and doubled numbers; adds -co"); + define("r|reduce|abbreviate|abbr=b", "use abbreviated figures; adds -nco"); + define("t|ties=b", "hide numbers without attack or changing base (needs -i)"); + define("f|figuredbass=b", "shortcut for -acorn3"); + define("3|hide-three=b", "hide number 3 if it has an accidental"); + define("m|negative=b", "show negative numbers"); + define("above=b", "show numbers above the staff (**fba)"); + define("rate=s:", "rate to display the numbers (use a **recip value, e.g. 4, 4.)"); + define("k|kern-tracks=s", "process only the specified kern spines"); define("s|spine-tracks|spine|spines|track|tracks=s", "Process only the specified spines"); - define("hint=b", "Determine harmonic intervals with interval quality"); + define("hint=b", "determine harmonic intervals with interval quality"); } @@ -81134,7 +81991,7 @@ const vector FiguredBassAbbreviationMapping::s_m // Tool_filter::Tool_filter(void) { - define("debug=b", "print debug statement"); + define("debug=b", "print debug statement"); define("v|variant=s:", "Run filters labeled with the given variant"); } @@ -81194,6 +82051,15 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { HumdrumFile& infile = infiles[0]; + #ifdef __EMSCRIPTEN__ + bool optionList = getBoolean("options"); + if (optionList) { + cerr << "GOT HERE BEFORE PRINT EMSCDRIPTEN" << endl; + printEmscripten(m_humdrum_text); + m_humdrum_text << infile; + } + #endif + bool status = true; vector > commands; getCommandList(commands, infile); @@ -81216,6 +82082,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(binroll, infile, commands[i].second, status); } else if (commands[i].first == "chantize") { RUNTOOL(chantize, infile, commands[i].second, status); + } else if (commands[i].first == "chint") { + RUNTOOL(chint, infile, commands[i].second, status); } else if (commands[i].first == "chord") { RUNTOOL(chord, infile, commands[i].second, status); } else if (commands[i].first == "cint") { @@ -81324,6 +82192,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(tremolo, infile, commands[i].second, status); } else if (commands[i].first == "trillspell") { RUNTOOL(trillspell, infile, commands[i].second, status); + } else if (commands[i].first == "vcross") { + RUNTOOL(vcross, infile, commands[i].second, status); // filters with aliases: @@ -81982,11 +82852,11 @@ void Tool_fixps::markEmptyVoices(HumdrumFile& infile) { // Tool_flipper::Tool_flipper(void) { - define("k|keep=b", "keep *flip/*Xflip instructions"); - define("a|all=b", "flip globally, not just inside *flip/*Xflip regions"); - define("s|strophe=b", "flip inside of strophes as well"); + define("k|keep=b", "keep *flip/*Xflip instructions"); + define("a|all=b", "flip globally, not just inside *flip/*Xflip regions"); + define("s|strophe=b", "flip inside of strophes as well"); define("S|strophe-only|only-strophe=b", "flip only inside of strophes as well"); - define("i|interp=s:kern", "flip only in this interpretation"); + define("i|interp=s:kern", "flip only in this interpretation"); } @@ -82254,25 +83124,25 @@ void Tool_flipper::extractFlipees(vector>& flipees, // Tool_gasparize::Tool_gasparize(void) { - define("R|no-reference-records=b", "Do not add reference records"); - define("r|only-add-reference-records=b", "Only add reference records"); + define("R|no-reference-records=b", "do not add reference records"); + define("r|only-add-reference-records=b", "only add reference records"); - define("B|do-not-delete-breaks=b", "Do not delete system/page break markers"); - define("b|only-delete-breaks=b", "only delete breaks"); + define("B|do-not-delete-breaks=b", "do not delete system/page break markers"); + define("b|only-delete-breaks=b", "only delete breaks"); - define("A|do-not-fix-instrument-abbreviations=b", "Do not fix instrument abbreviations"); - define("a|only-fix-instrument-abbreviations=b", "Only fix instrument abbreviations"); + define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations"); + define("a|only-fix-instrument-abbreviations=b", "only fix instrument abbreviations"); - define("E|do-not-fix-editorial-accidentals=b", "Do not fix instrument abbreviations"); - define("e|only-fix-editorial-accidentals=b", "Only fix editorial accidentals"); + define("E|do-not-fix-editorial-accidentals=b", "do not fix instrument abbreviations"); + define("e|only-fix-editorial-accidentals=b", "only fix editorial accidentals"); - define("T|do-not-add-terminal-longs=b", "Do not add terminal long markers"); - define("t|only-add-terminal-longs=b", "Only add terminal longs"); + define("T|do-not-add-terminal-longs=b", "do not add terminal long markers"); + define("t|only-add-terminal-longs=b", "only add terminal longs"); - define("no-ties=b", "Do not fix tied notes"); + define("no-ties=b", "do not fix tied notes"); - define("N|do-not-remove-empty-transpositions=b", "Do not remove empty transposition instructions"); - define ("n|only-remove-empty-transpositions=b", "Only remove empty transpositions"); + define("N|do-not-remove-empty-transpositions=b", "do not remove empty transposition instructions"); + define ("n|only-remove-empty-transpositions=b", "only remove empty transpositions"); } @@ -83900,8 +84770,8 @@ void Tool_gasparize::convertNextNoteToJAccidental(HTp current) { // Tool_grep::Tool_grep(void) { - define("v|remove-matching-lines=b", "Remove lines that match regex"); - define("e|regex|regular-expression=s", "Regular expression to search with"); + define("v|remove-matching-lines=b", "remove lines that match regex"); + define("e|regex|regular-expression=s", "regular expression to search with"); } @@ -84193,19 +85063,19 @@ void Tool_half::terminalLongToTerminalBreve(HumdrumFile& infile) { // Tool_homorhythm::Tool_homorhythm(void) { - define("a|append=b", "Append analysis to end of input data"); - define("attacks=b", "Append attack counts for each sonority"); - define("p|prepend=b", "Prepend analysis to end of input data"); - define("r|raw-sonority=b", "Display individual sonority scores only"); - define("raw-score=b", "Display accumulated scores"); - define("M|no-marks=b", "Do not mark homorhythm section notes"); - define("f|fraction=b", "calculate fraction of music that is homorhythm"); - define("v|voice=b", "display voice information or fraction results"); - define("F|filename=b", "show filename for f option"); - define("n|t|threshold=d:4.0", "Threshold score sum required for homorhythm texture detection"); - define("s|score=d:1.0", "Score assigned to a sonority with three or more attacks"); - define("m|intermediate-score=d:0.5", "Score to give sonority between two adjacent attack sonoroties"); - define("l|letter=b", "Display letter scoress before calculations"); + define("a|append=b", "append analysis to end of input data"); + define("attacks=b", "append attack counts for each sonority"); + define("p|prepend=b", "prepend analysis to end of input data"); + define("r|raw-sonority=b", "display individual sonority scores only"); + define("raw-score=b", "display accumulated scores"); + define("M|no-marks=b", "do not mark homorhythm section notes"); + define("f|fraction=b", "calculate fraction of music that is homorhythm"); + define("v|voice=b", "display voice information or fraction results"); + define("F|filename=b", "show filename for f option"); + define("n|t|threshold=d:4.0", "threshold score sum required for homorhythm texture detection"); + define("s|score=d:1.0", "score assigned to a sonority with three or more attacks"); + define("m|intermediate-score=d:0.5", "score to give sonority between two adjacent attack sonoroties"); + define("l|letter=b", "display letter scoress before calculations"); } @@ -84635,11 +85505,11 @@ void Tool_homorhythm::analyzeLine(HumdrumFile& infile, int line) { // Tool_homorhythm2::Tool_homorhythm2(void) { - define("t|threshold=d:1.6", "Threshold score sum required for homorhythm texture detection"); - define("u|threshold2=d:1.3", "Threshold score sum required for semi-homorhythm texture detection"); - define("s|score=b", "Show numeric scores"); - define("n|length=i:4", "Sonority length to calculate"); - define("f|fraction=b", "Report fraction of music that is homorhythm"); + define("t|threshold=d:1.6", "threshold score sum required for homorhythm texture detection"); + define("u|threshold2=d:1.3", "threshold score sum required for semi-homorhythm texture detection"); + define("s|score=b", "show numeric scores"); + define("n|length=i:4", "sonority length to calculate"); + define("f|fraction=b", "report fraction of music that is homorhythm"); } @@ -85029,11 +85899,11 @@ void Tool_hproof::markHarmonicTones(HTp tok, vector& cts) { // Tool_humbreak::Tool_humbreak(void) { - define("m|measures=s", "Measures numbers to place linebreaks before"); - define("p|page-breaks=s", "Measure numbers to place page breaks before"); - define("g|group=s:original", "Line/page break group"); - define("r|remove|remove-breaks=b", "Remove line/page breaks"); - define("l|page-to-line-breaks=b", "Convert page breaks to line breaks"); + define("m|measures=s", "measures numbers to place linebreaks before"); + define("p|page-breaks=s", "measure numbers to place page breaks before"); + define("g|group=s:original", "line/page break group"); + define("r|remove|remove-breaks=b", "remove line/page breaks"); + define("l|page-to-line-breaks=b", "convert page breaks to line breaks"); } @@ -85098,100 +85968,239 @@ void Tool_humbreak::initialize(void) { vector lbs; vector pbs; HumRegex hre; - hre.split(lbs, systemMeasures, "[^\\d]+"); - hre.split(pbs, pageMeasures, "[^\\d]+"); + hre.split(lbs, systemMeasures, "[^\\da-z]+"); + hre.split(pbs, pageMeasures, "[^\\da-z]+"); for (int i=0; i<(int)lbs.size(); i++) { - int number = std::stoi(lbs[i]); - m_lineMeasures[number] = 1; + if (hre.search(lbs[i], "^(p?)(\\d+)([a-z]?)")) { + int number = hre.getMatchInt(2); + if (!hre.getMatch(1).empty()) { + m_pageMeasures[number] = 1; + int offset = 0; + string letter; + if (!hre.getMatch(3).empty()) { + letter = hre.getMatch(3); + offset = letter.at(0) - 'a'; + } + m_pageOffset[number] = offset; + } else { + m_lineMeasures[number] = 1; + int offset = 0; + if (!hre.getMatch(3).empty()) { + string letter = hre.getMatch(3); + offset = letter.at(0) - 'a'; + } + m_lineOffset[number] = offset; + } + } } for (int i=0; i<(int)pbs.size(); i++) { - int number = std::stoi(lbs[i]); - m_pageMeasures[number] = 1; + if (hre.search(pbs[i], "^(\\d+)([a-z]?)")) { + int number = hre.getMatchInt(1); + m_pageMeasures[number] = 1; + int offset = 0; + if (!hre.getMatch(2).empty()) { + string letter = hre.getMatch(2); + offset = letter.at(0) - 'a'; + } + m_pageOffset[number] = offset; + } } } + ////////////////////////////// // -// Tool_humbreak::addBreaks -- +// Tool_humbreak::markLineBreakMeasures -- // -void Tool_humbreak::addBreaks(HumdrumFile& infile) { - +void Tool_humbreak::markLineBreakMeasures(HumdrumFile& infile) { + vector pbreak; + vector lbreak; HumRegex hre; + map used; + for (int i=0; icompare(0, 8, "!!LO:PB:") == 0)) { - // Add group to existing LO:PB: - HTp token = infile.token(i, 0); - HTp barToken = infile.token(i+1, 0); - if (barToken->isBarline()) { - int measure = infile[i+1].getBarNumber(); - int pbStatus = m_pageMeasures[measure]; - if (pbStatus) { - string query = "\\b" + m_group + "\\b"; - if (!hre.match(token, query)) { - m_humdrum_text << token << ", " << m_group << endl; - } else { - m_humdrum_text << token << endl; + if (!infile[i].isBarline()) { + continue; + } + + int barnum = infile[i].getBarNumber(); + if (barnum < 0) { + lbreak.clear(); + pbreak.clear(); + continue; + } + + int status = m_lineMeasures[barnum]; + if (status) { + HLp line = &infile[i]; + int offset = m_lineOffset[barnum]; + if (offset && (used[barnum] == 0)) { + used[barnum] = offset; + int ocounter = 0; + lbreak.clear(); + pbreak.clear(); + for (int j=i+1; jsetValue("auto", "barnum", barnum + 1); } else { - m_humdrum_text << token << endl; + line->setValue("auto", "barnum", barnum + 1); } - m_humdrum_text << infile[i+1] << endl; - i++; - continue; + } else { + line->setValue("auto", "barnum", barnum + 1); } } - if ((icompare(0, 8, "!!LO:LB:") == 0)) { - // Add group to existing LO:LB: - HTp token = infile.token(i, 0); - HTp barToken = infile.token(i+1, 0); - if (barToken->isBarline()) { - int measure = infile[i+1].getBarNumber(); - int lbStatus = m_lineMeasures[measure]; - if (lbStatus) { - string query = "\\b" + m_group + "\\b"; - if (!hre.match(token, query)) { - m_humdrum_text << token << ", " << m_group << endl; - } else { - m_humdrum_text << token << endl; + status = m_pageMeasures[barnum]; + if (status) { + HLp line = &infile[i]; + int offset = m_pageOffset[barnum]; + if (offset) { + int ocounter = 0; + lbreak.clear(); + pbreak.clear(); + for (int j=i+1; jsetValue("auto", "barnum", barnum + 1); + pbreak.back()->setValue("auto", "page", 1); + } + } else { + line->setValue("auto", "barnum", barnum + 1); + line->setValue("auto", "page", 1); } } + } +} - if (!infile[i].isBarline()) { + + +////////////////////////////// +// +// Tool_humbreak::addBreaks -- +// + +void Tool_humbreak::addBreaks(HumdrumFile& infile) { + markLineBreakMeasures(infile); + + HumRegex hre; + for (int i=0; iisBarline()) { + int measure = infile[i+1].getBarNumber(); + int pbStatus = m_pageMeasures[measure]; + if (pbStatus) { + string query = "\\b" + m_group + "\\b"; + if (!hre.match(token, query)) { + m_humdrum_text << token << ", " << m_group << endl; + } else { + m_humdrum_text << token << endl; + } + } else { + m_humdrum_text << token << endl; + } + m_humdrum_text << infile[i+1] << endl; + i++; + continue; + } + } else if (hre.search(token, "^!!LO:LB:")) { + // Add group to existing LO:LB: + HTp token = infile.token(i, 0); + HTp barToken = infile.token(i+1, 0); + if (barToken->isBarline()) { + int measure = infile[i+1].getBarNumber(); + int lbStatus = m_lineMeasures[measure]; + if (lbStatus) { + string query = "\\b" + m_group + "\\b"; + if (!hre.match(token, query)) { + m_humdrum_text << token << ", " << m_group << endl; + } else { + m_humdrum_text << token << endl; + } + } else { + m_humdrum_text << token << endl; + } + m_humdrum_text << infile[i+1] << endl; + i++; + continue; + } + } + } - if (pbStatus) { + if (pageQ) { m_humdrum_text << "!!LO:PB:g=" << m_group << endl; - } else if (lbStatus) { + } else { m_humdrum_text << "!!LO:LB:g=" << m_group << endl; } - m_humdrum_text << infile[i] << endl; } } - ////////////////////////////// // // Tool_humbreak::processFile -- @@ -85723,14 +86732,14 @@ ostream& operator<<(ostream& out, NotePoint& np) { // Tool_humsheet::Tool_humsheet(void) { - define("h|H|html|HTML=b", "output table in HTML wrapper"); - define("i|id|ID=b", "include ID for each cell"); - define("z|zebra=b", "add zebra striping by spine to style"); + define("h|H|html|HTML=b", "output table in HTML wrapper"); + define("i|id|ID=b", "include ID for each cell"); + define("z|zebra=b", "add zebra striping by spine to style"); define("y|z2|zebra2|zebra-2=b", "zebra striping by data type"); - define("t|tab-index=b", "vertical tab indexing"); - define("X|no-exinterp=b", "do not embed exclusive interp data"); - define("J|no-javascript=b", "do not embed javascript code"); - define("S|no-style=b", "do not embed CSS style element"); + define("t|tab-index=b", "vertical tab indexing"); + define("X|no-exinterp=b", "do not embed exclusive interp data"); + define("J|no-javascript=b", "do not embed javascript code"); + define("S|no-style=b", "do not embed CSS style element"); } @@ -86779,11 +87788,11 @@ void Tool_humsheet::analyzeTabIndex(HumdrumFile& infile) { Tool_humsort::Tool_humsort(void) { // add options here - define("n|numeric=b", "Sort numerically"); - define("r|reverse=b", "Sort in reversed order"); - define("s|spine=i:1", "Spine to sort (1-indexed)"); - define("I|do-not-ignore-case=b", "Do not ignore case when sorting alphabetically"); - define("i|e|x|interp|exclusive-interpretation=s", "Exclusive interpretation to sort"); + define("n|numeric=b", "sort numerically"); + define("r|reverse=b", "sort in reversed order"); + define("s|spine=i:1", "spine to sort (1-indexed)"); + define("I|do-not-ignore-case=b", "do not ignore case when sorting alphabetically"); + define("i|e|x|interp|exclusive-interpretation=s", "exclusive interpretation to sort"); } @@ -86947,25 +87956,25 @@ void Tool_humsort::processFile(HumdrumFile& infile) { // Tool_humtr::Tool_humtr(void) { - define("T|no-text|no-lyrics=b", "Do not convert lyrics in **text spines."); - define("L|no-local=b", "Do not convert local LO t parameters."); - define("G|no-global=b", "Do not convert global LO t parameters."); - define("R|no-reference=b", "Do not convert reference record values."); + define("T|no-text|no-lyrics=b", "do not convert lyrics in **text spines."); + define("L|no-local=b", "do not convert local LO t parameters."); + define("G|no-global=b", "do not convert global LO t parameters."); + define("R|no-reference=b", "do not convert reference record values."); define("t|text-only|lyrics-only=b", "convert only lyrics in **text spines."); - define("l|local-only=b", "convert only local LO t parameters."); - define("g|global-only=b", "convert only global LO t parameters."); - define("r|reference-only=b", "convert only reference record values."); + define("l|local-only=b", "convert only local LO t parameters."); + define("g|global-only=b", "convert only global LO t parameters."); + define("r|reference-only=b", "convert only reference record values."); - define("d|data-type=s", "process only given exclusive interpretations"); - define("s|spines=s", "spines to process"); + define("d|data-type=s", "process only given exclusive interpretations"); + define("s|spines=s", "spines to process"); - define("i|input=s", "Input characters to change"); - define("o|output=s", "Output characters to change to"); + define("i|input=s", "input characters to change"); + define("o|output=s", "output characters to change to"); - define("m|replace-map=s", "Characters to change from and to"); - define("M|display-mapping=b", "Display character transliterations mappings"); - define("p|popc|popc2=b", "Add POPC2 character substitutions"); + define("m|replace-map=s", "characters to change from and to"); + define("M|display-mapping=b", "display character transliterations mappings"); + define("p|popc|popc2=b", "add POPC2 character substitutions"); } @@ -87462,41 +88471,41 @@ int Tool_imitation::Enumerator = 0; // Tool_imitation::Tool_imitation(void) { - define("debug=b", "print grid cell information"); - define("e|exinterp=s:**vvdata","specify exinterp for **vvdata spine"); + define("debug=b", "print grid cell information"); + define("e|exinterp=s:**vvdata", "specify exinterp for **vvdata spine"); - define("n|t|threshold=i:7", "minimum number of notes to match"); - define("f|first=b", "only give info for first sequence of matched pair"); + define("n|t|threshold=i:7", "minimum number of notes to match"); + define("f|first=b", "only give info for first sequence of matched pair"); - define("q|quiet|no-info=b", "do not add spines giving information about matches"); + define("q|quiet|no-info=b", "do not add spines giving information about matches"); - define("N|no-enumeration=b", "do not display enumeration number"); - define("C|no-count=b", "do not display note-count number"); - define("D|no-distance=b", "do not display distance between first notes of sequences"); - define("I|no-interval=b", "do not display interval transposite between sequences"); + define("N|no-enumeration=b", "do not display enumeration number"); + define("C|no-count=b", "do not display note-count number"); + define("D|no-distance=b", "do not display distance between first notes of sequences"); + define("I|no-interval=b", "do not display interval transposite between sequences"); - define("NN|no-enumeration2=b", "do not display enumeration number on second sequence"); - define("CC|no-count2=b", "do not display note-count number on second sequence"); - define("DD|no-distance2=b", "do not display distance between first notes of sequences on second sequence"); - define("II|no-interval2=b", "do not display interval transposition between sequences on second sequence"); - define("2|enumerate-second-only=b", "Display enumeration number on second sequence only (no count, distance, or interval"); + define("NN|no-enumeration2=b", "do not display enumeration number on second sequence"); + define("CC|no-count2=b", "do not display note-count number on second sequence"); + define("DD|no-distance2=b", "do not display distance between first notes of sequences on second sequence"); + define("II|no-interval2=b", "do not display interval transposition between sequences on second sequence"); + define("2|enumerate-second-only=b", "display enumeration number on second sequence only (no count, distance, or interval"); - define("p|no-duration=b", "pitch only when matching: do not consider duration"); - define("d|max-distance=d", "maximum distance in quarter notes between imitations"); - define("s|single-mark=b", "place a single mark on matched notes (not one for each match pair"); - define("r|rest=b", "require match trigger to follow a rest"); - define("R|rest2=b", "require match target to also follow a rest"); - define("i|intervals=s", "require given interval sequence in imitation"); - define("M|no-mark=b", "do not mark matched sequences"); - define("Z|no-zero=b", "do not mark imitation starting at the same time"); - define("z|only-zero=b", "Mark only imitation starting at the same time (parallel motion)"); - define("m|measure=b", "Include measure number in imitation information"); - define("b|beat=b", "Include beat number (really quarter-note number) in imitation information"); - define("l|length=b", "Include length of imitation (in quarter-note units)"); + define("p|no-duration=b", "pitch only when matching: do not consider duration"); + define("d|max-distance=d", "maximum distance in quarter notes between imitations"); + define("s|single-mark=b", "place a single mark on matched notes (not one for each match pair"); + define("r|rest=b", "require match trigger to follow a rest"); + define("R|rest2=b", "require match target to also follow a rest"); + define("i|intervals=s", "require given interval sequence in imitation"); + define("M|no-mark=b", "do not mark matched sequences"); + define("Z|no-zero=b", "do not mark imitation starting at the same time"); + define("z|only-zero=b", "mark only imitation starting at the same time (parallel motion)"); + define("m|measure=b", "include measure number in imitation information"); + define("b|beat=b", "include beat number (really quarter-note number) in imitation information"); + define("l|length=b", "include length of imitation (in quarter-note units)"); - define("a|add=b", "add inversions, retrograde, etc. if specified to normal search"); - define("v|inversion=b", "match inversions"); - define("g|retrograde=b", "match retrograde"); + define("a|add=b", "add inversions, retrograde, etc. if specified to normal search"); + define("v|inversion=b", "match inversions"); + define("g|retrograde=b", "match retrograde"); } @@ -88196,12 +89205,12 @@ int Tool_imitation::compareSequences(vector& attack1, // Tool_kern2mens::Tool_kern2mens(void) { - define("N|no-measure-numbers=b", "remove measure numbers"); - define("M|no-measures=b", "remove measures "); - define("I|not-invisible=b", "keep measures visible"); - define("D|no-double-bar=b", "keep thick final barlines"); - define("c|clef=s", "clef to use in mensural notation"); - define("V|no-verovio=b", "don't add verovio styling"); + define("N|no-measure-numbers=b", "remove measure numbers"); + define("M|no-measures=b", "remove measures "); + define("I|not-invisible=b", "keep measures visible"); + define("D|no-double-bar=b", "keep thick final barlines"); + define("c|clef=s", "clef to use in mensural notation"); + define("V|no-verovio=b", "don't add verovio styling"); define("e|evenNoteSpacing|even-note-spacing=b", "add evenNoteSpacing option"); } @@ -88975,9 +89984,9 @@ string Tool_kernify::makeReverseLine(HumdrumLine& line) { // Tool_kernview::Tool_kernview(void) { - define("v|view|s|show=s", "view the list of spines"); - define("g=s", "Regular expression of kern spines to view"); - define("G=s", "Regular expression of kern spines to hide"); + define("v|view|s|show=s", "view the list of spines"); + define("g=s", "regular expression of kern spines to view"); + define("G=s", "regular expression of kern spines to hide"); define("h|hide|r|remove=s", "hide the list of spines"); } @@ -89209,10 +90218,10 @@ void Tool_kernview::processFile(HumdrumFile& infile) { Tool_mei2hum::Tool_mei2hum(void) { define("app|app-label=s", "app label to follow"); - define("r|recip=b", "output **recip spine"); - define("s|stems=b", "include stems in output"); - define("x|xmlids=b", "include xmlids in output"); - define("P|no-place=b", "Do not convert placement attribute"); + define("r|recip=b", "output **recip spine"); + define("s|stems=b", "include stems in output"); + define("x|xmlids=b", "include xmlids in output"); + define("P|no-place=b", "do not convert placement attribute"); m_maxverse.resize(m_maxstaff); fill(m_maxverse.begin(), m_maxverse.end(), 0); @@ -94124,7 +95133,7 @@ Tool_melisma::Tool_melisma(void) { define("r|replace=b", "replace lyrics with note counts"); define("a|average|avg=b", "calculate note-to-syllable ratio"); define("w|words=b", "list words that contain a melisma"); - define("p|part=b", "also calculate note-to-syllable ratios by part"); + define("p|part=b", "also calculate note-to-syllable ratios by part"); } @@ -95249,25 +96258,23 @@ string Tool_mens2kern::mens2kernRhythm(const string& rhythm, bool altera, bool p // Tool_meter::Tool_meter(void) { - - define("c|comma=b", "display decimal points as commas"); - define("d|denominator=b", "display denominator spine"); - define("e|eighth=b", "metric positions in eighth notes rather than beats"); - define("f|float=b", "floating-point beat values instead of rational numbers"); - define("h|half=b", "metric positions in half notes rather than beats"); - define("j|join=b", "join time signature information and metric positions into a single token"); - define("n|numerator=b", "display numerator spine"); - define("q|quarter=b", "metric positions in quarter notes rather than beats"); - define("r|rest=b", "add meteric positions of rests"); - define("s|sixteenth=b", "metric positions in sixteenth notes rather than beats"); + define("c|comma=b", "display decimal points as commas"); + define("d|denominator=b", "display denominator spine"); + define("e|eighth=b", "metric positions in eighth notes rather than beats"); + define("f|float=b", "floating-point beat values instead of rational numbers"); + define("h|half=b", "metric positions in half notes rather than beats"); + define("j|join=b", "join time signature information and metric positions into a single token"); + define("n|numerator=b", "display numerator spine"); + define("q|quarter=b", "metric positions in quarter notes rather than beats"); + define("r|rest=b", "add meteric positions of rests"); + define("s|sixteenth=b", "metric positions in sixteenth notes rather than beats"); define("t|time-signature|tsig|m|meter=b", "display active time signature for each note"); - define("w|whole=b", "metric positions in whole notes rather than beats"); - define("z|zero=b", "start of measure is beat 0 rather than beat 1"); - - define("B|no-beat=b", "Do not display metric positions (beats)"); - define("D|digits=i:0", "number of digits after decimal point"); - define("L|no-label=b", "do not add labels to analysis spines"); + define("w|whole=b", "metric positions in whole notes rather than beats"); + define("z|zero=b", "start of measure is beat 0 rather than beat 1"); + define("B|no-beat=b", "Do not display metric positions (beats)"); + define("D|digits=i:0", "number of digits after decimal point"); + define("L|no-label=b", "do not add labels to analysis spines"); } @@ -96092,14 +97099,14 @@ void Tool_meter::processLine(HumdrumLine& line, vector& curNum, // Tool_metlev::Tool_metlev(void) { - define("a|append=b", "append data analysis to input file"); - define("p|prepend=b", "prepend data analysis to input file"); - define("c|composite=b", "generate composite rhythm"); - define("i|integer=b", "quantize metric levels to int values"); - define("x|attacks-only=b", "only mark lines with note attacks"); - define("G|no-grace-notes=b", "do not mark grace note lines"); - define("k|kern-spine=i:1", "analyze only given kern spine"); - define("e|exinterp=s:blev", "exclusive interpretation type for output"); + define("a|append=b", "append data analysis to input file"); + define("p|prepend=b", "prepend data analysis to input file"); + define("c|composite=b", "generate composite rhythm"); + define("i|integer=b", "quantize metric levels to int values"); + define("x|attacks-only=b", "only mark lines with note attacks"); + define("G|no-grace-notes=b", "do not mark grace note lines"); + define("k|kern-spine=i:1", "analyze only given kern spine"); + define("e|exinterp=s:blev", "exclusive interpretation type for output"); } @@ -96299,17 +97306,17 @@ void Tool_metlev::fillVoiceResults(vector >& results, // Tool_modori::Tool_modori(void) { - define("m|modern=b", "prepare score for modern style"); - define("o|original=b", "prepare score for original style"); - define("d|info=b", "display key/clef/mensuration information"); - define("I|no-instrument-name|no-instrument-names=b", "Do not change part labels"); - define("A|no-instrument-abbreviation|no-instrument-abbreviations=b", "Do not change part label abbreviations"); - define("C|no-clef|no-clefs=b", "Do not change clefs"); - define("K|no-key|no-keys=b", "Do not change key signatures"); - define("L|no-lyrics=b", "Do not change **text exclusive interpretations"); - define("M|no-mensuration|no-mensurations=b", "Do not change mensurations"); - define("R|no-references=b", "Do not change reference records keys"); - define("T|no-text=b", "Do not change !LO:(TX|DY) layout parameters"); + define("m|modern=b", "prepare score for modern style"); + define("o|original=b", "prepare score for original style"); + define("d|info=b", "display key/clef/mensuration information"); + define("I|no-instrument-name|no-instrument-names=b", "do not change part labels"); + define("A|no-instrument-abbreviation|no-instrument-abbreviations=b", "do not change part label abbreviations"); + define("C|no-clef|no-clefs=b", "do not change clefs"); + define("K|no-key|no-keys=b", "do not change key signatures"); + define("L|no-lyrics=b", "do not change **text exclusive interpretations"); + define("M|no-mensuration|no-mensurations=b", "do not change mensurations"); + define("R|no-references=b", "do not change reference records keys"); + define("T|no-text=b", "do not change !LO:(TX|DY) layout parameters"); } @@ -97906,18 +98913,18 @@ void MSearchQueryToken::parseHarmonicQuery(void) { // Tool_msearch::Tool_msearch(void) { - define("debug=b", "diatonic search"); - define("q|query=s:4c4d4e4f4g", "combined rhythm/pitch query string"); - define("p|pitch=s:cdefg", "pitch query string"); - define("i|interval=s:2222", "interval query string"); + define("debug=b", "diatonic search"); + define("q|query=s:4c4d4e4f4g", "combined rhythm/pitch query string"); + define("p|pitch=s:cdefg", "pitch query string"); + define("i|interval=s:2222", "interval query string"); define("r|d|rhythm|duration=s:44444", "rhythm query string"); - define("t|text=s:", "lyrical text query string"); - define("O|no-overlap=b", "do not allow matches to overlap"); - define("x|cross=b", "search across parts"); - define("c|color=s", "highlight color"); - define("m|mark|marker=s:@", "marking character"); - define("M|no-mark|no-marker=b", "do not mark matches"); - define("Q|quiet=b", "quiet mode: do not summarize matches"); + define("t|text=s:", "lyrical text query string"); + define("O|no-overlap=b", "do not allow matches to overlap"); + define("x|cross=b", "search across parts"); + define("c|color=s", "highlight color"); + define("m|mark|marker=s:@", "marking character"); + define("M|no-mark|no-marker=b", "do not mark matches"); + define("Q|quiet=b", "quiet mode: do not summarize matches"); } @@ -99766,10 +100773,10 @@ Tool_musedata2hum::Tool_musedata2hum(void) { // Options& options = m_options; // options.define("k|kern=b","display corresponding **kern data"); - define("g|group=s:score", "The data group to process"); - define("r|recip=b", "Output **recip spine"); - define("s|stems=b", "Include stems in output"); - define("omv|no-omv=b", "Exclude extracted OMV record in output data"); + define("g|group=s:score", "the data group to process"); + define("r|recip=b", "output **recip spine"); + define("s|stems=b", "include stems in output"); + define("omv|no-omv=b", "exclude extracted OMV record in output data"); } @@ -99878,12 +100885,21 @@ bool Tool_musedata2hum::convert(ostream& out, MuseDataSet& mds) { HumdrumFile outfile; outdata.transferTokens(outfile); + outfile.generateLinesFromTokens(); + stringstream sss; + sss << outfile; + outfile.readString(sss.str()); + if (needsAboveBelowKernRdf()) { outfile.appendLine("!!!RDF**kern: > = above"); outfile.appendLine("!!!RDF**kern: < = above"); } + outfile.createLinesFromTokens(); + Tool_trillspell trillspell; + trillspell.run(outfile); + // Convert comments in header of first part: int ii = groupMemberIndex[0]; bool ending = false; @@ -100123,6 +101139,7 @@ bool Tool_musedata2hum::convertPart(HumGrid& outdata, MuseDataSet& mds, int inde bool status = true; int i = 0; while (i < part.getLineCount()) { + m_measureLineIndex = i; i = convertMeasure(outdata, part, partindex, i); } @@ -100286,6 +101303,10 @@ void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { layer = layer - 1; } + if (mr.isAnyNoteOrRest()) { + m_figureOffset = 0; + } + if (mr.isDirection()) { return; } @@ -100312,6 +101333,10 @@ void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { } } + if (!attributes["Q"].empty()) { + m_quarterDivisions = std::stoi(attributes["Q"]); + } + string mclef = attributes["C"]; if (!mclef.empty()) { string kclef = Convert::museClefToKernClef(mclef); @@ -100502,11 +101527,19 @@ void Tool_musedata2hum::addTextDirection(GridMeasure* gm, int part, int staff, void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm, HumNum timestamp, int part, int maxstaff) { string fh = mr.getFigureString(); + int figureDuration = mr.getFigureDuration(); fh = Convert::museFiguredBassToKernFiguredBass(fh); + if (m_figureOffset > 0) { + if (m_quarterDivisions > 0) { + HumNum offset(m_figureOffset, m_quarterDivisions); + timestamp + offset; + } + } if (fh.find(":") == string::npos) { HTp fhtok = new HumdrumToken(fh); m_lastfigure = fhtok; gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; return; } @@ -100514,6 +101547,7 @@ void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm, HTp fhtok = new HumdrumToken(fh); m_lastfigure = fhtok; gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; return; } @@ -100561,6 +101595,7 @@ void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm, HTp fhtok = new HumdrumToken(fh); m_lastfigure = fhtok; gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; return; } @@ -100579,6 +101614,7 @@ void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm, HTp newtok = new HumdrumToken(fh); m_lastfigure = newtok; gm->addFiguredBass(newtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; } @@ -106026,28 +107062,28 @@ void Tool_musicxml2hum::getChildrenVector(vector& children, // Tool_myank::Tool_myank(void) { - define("v|verbose=b", "Verbose output of data"); - define("debug=b", "Debugging information"); - define("inlist=b", "Show input measure list"); - define("outlist=b", "Show output measure list"); - define("mark|marks=b", "Yank measure with marked notes"); - define("T|M|bar-number-text=b", "print barnum with LO text above system "); - define("d|double|dm|md|mdsep|mdseparator=b", "Put double barline between non-consecutive measure segments"); - define("m|b|measures|bars|measure|bar=s", "Measures to yank"); - define("l|lines|line-range=s", "Line numbers range to yank (e.g. 40-50)"); - define("I|i|instrument=b", "Include instrument codes from start of data"); - define("visible|not-invisible=b", "Do not make initial measure invisible"); - define("B|noendbar=b", "Do not print barline at end of data"); - define("max=b", "print maximum measure number"); - define("min=b", "print minimum measure number"); - define("section-count=b", "count the number of sections, JRP style"); - define("section=i:0", "extract given section number (indexed from 1"); - define("author=b", "Program author"); - define("version=b", "Program version"); - define("example=b", "Program examples"); - define("h|help=b", "Short description"); - define("hide-starting=b", "Prevent printStarting"); - define("hide-ending=b", "Prevent printEnding"); + define("v|verbose=b", "verbose output of data"); + define("debug=b", "debugging information"); + define("inlist=b", "show input measure list"); + define("outlist=b", "show output measure list"); + define("mark|marks=b", "yank measure with marked notes"); + define("T|M|bar-number-text=b", "print barnum with LO text above system "); + define("d|double|dm|md|mdsep|mdseparator=b", "put double barline between non-consecutive measure segments"); + define("m|b|measures|bars|measure|bar=s", "measures to yank"); + define("l|lines|line-range=s", "line numbers range to yank (e.g. 40-50)"); + define("I|i|instrument=b", "Include instrument codes from start of data"); + define("visible|not-invisible=b", "do not make initial measure invisible"); + define("B|noendbar=b", "do not print barline at end of data"); + define("max=b", "print maximum measure number"); + define("min=b", "print minimum measure number"); + define("section-count=b", "count the number of sections, JRP style"); + define("section=i:0", "extract given section number (indexed from 1"); + define("author=b", "program author"); + define("version=b", "program version"); + define("example=b", "program examples"); + define("h|help=b", "short description"); + define("hide-starting=b", "prevent printStarting"); + define("hide-ending=b", "prevent printEnding"); } @@ -108789,23 +109825,23 @@ void Tool_myank::usage(const string& ommand) { // Tool_nproof::Tool_nproof(void) { - define("B|no-blank|no-blanks=b", "Do not check for blank lines.\n"); - define("b|only-blank|only-blanks=b", "Only check for blank lines.\n"); + define("B|no-blank|no-blanks=b", "do not check for blank lines.\n"); + define("b|only-blank|only-blanks=b", "only check for blank lines.\n"); - define("I|no-instrument|no-instruments=b", "Do not check instrument interpretations.\n"); - define("i|only-instrument|only-instruments=b", "Only check instrument interpretations.\n"); + define("I|no-instrument|no-instruments=b", "do not check instrument interpretations.\n"); + define("i|only-instrument|only-instruments=b", "only check instrument interpretations.\n"); - define("K|no-key=b", "Do not check for !!!key: manual initial key designation.\n"); - define("k|only-key=b", "Only check for !!!key: manual initial key designation.\n"); + define("K|no-key=b", "do not check for !!!key: manual initial key designation.\n"); + define("k|only-key=b", "only check for !!!key: manual initial key designation.\n"); - define("R|no-reference=b", "Do not check for reference records.\n"); - define("r|only-reference=b", "Only check for reference records.\n"); + define("R|no-reference=b", "do not check for reference records.\n"); + define("r|only-reference=b", "only check for reference records.\n"); - define("T|no-termination|no-terminations=b", "Do not check spine terminations.\n"); - define("t|only-termination|only-terminations=b", "Only check spine terminations.\n"); + define("T|no-termination|no-terminations=b", "do not check spine terminations.\n"); + define("t|only-termination|only-terminations=b", "only check spine terminations.\n"); - define("file|filename=b", "Print filename with raw count (if available).\n"); - define("raw=b", "Only print error count.\n"); + define("file|filename=b", "print filename with raw count (if available).\n"); + define("raw=b", "only print error count.\n"); } @@ -109862,21 +110898,21 @@ void Tool_ordergps::printStaffLine(HumdrumFile& infile) { // Tool_pccount::Tool_pccount(void) { - define("a|attacks=b", "count attacks instead of durations"); - define("d|data|vega-data=b", "display the vega-lite template."); - define("f|full=b", "full count attacks all single sharps and flats."); - define("ff|double-full=b", "full count attacks all double sharps and flats."); - define("h|html=b", "generate vega-lite HTML content"); - define("i|id=s:id", "ID for use as variable and in plot title"); - define("K|no-key|no-final=b", "Do not label key tonic or final"); - define("m|maximum=b", "normalize by maximum count"); - define("n|normalize=b", "normalize counts"); - define("p|page=b", "generate vega-lite stand-alone HTML page"); + define("a|attacks=b", "count attacks instead of durations"); + define("d|data|vega-data=b", "display the vega-lite template."); + define("f|full=b", "full count attacks all single sharps and flats."); + define("ff|double-full=b", "full count attacks all double sharps and flats."); + define("h|html=b", "generate vega-lite HTML content"); + define("i|id=s:id", "ID for use as variable and in plot title"); + define("K|no-key|no-final=b", "do not label key tonic or final"); + define("m|maximum=b", "normalize by maximum count"); + define("n|normalize=b", "normalize counts"); + define("p|page=b", "generate vega-lite stand-alone HTML page"); define("r|ratio|aspect-ratio=d:0.67", "width*ratio=height of vega-lite plot"); - define("s|script|vega-script=b", "generate vega-lite javascript content"); - define("title=s", "Title for plot"); - define("t|template|vega-template=b", "display the vega-lite template."); - define("w|width=i:400", "width of vega-lite plot"); + define("s|script|vega-script=b", "generate vega-lite javascript content"); + define("title=s", "title for plot"); + define("t|template|vega-template=b", "display the vega-lite template."); + define("w|width=i:400", "width of vega-lite plot"); } @@ -110761,14 +111797,14 @@ string Tool_pccount::getPitchClassString(int b40) { // Tool_periodicity::Tool_periodicity(void) { - define("m|min=b", "minimum time unit (other than grace notes)"); + define("m|min=b", "minimum time unit (other than grace notes)"); define("n|max-rows=i:-1", "maxumum number of rows in svg analysis display"); - define("t|track=i:0", "track to analyze"); - define("attacks=b", "extract attack grid)"); - define("raw=b", "show only raw period data"); - define("s|svg=b", "output svg image"); - define("p|power=d:2.0", "scaling power for visual display"); - define("1|one=b", "composite rhythms are not weighted by attack"); + define("t|track=i:0", "track to analyze"); + define("attacks=b", "extract attack grid)"); + define("raw=b", "show only raw period data"); + define("s|svg=b", "output svg image"); + define("p|power=d:2.0", "scaling power for visual display"); + define("1|one=b", "composite rhythms are not weighted by attack"); } @@ -111154,10 +112190,10 @@ void Tool_periodicity::getColorMapping(double input, double& hue, Tool_phrase::Tool_phrase(void) { define("A|no-average=b", "do not do average phrase-length analysis"); - define("R|remove2=b", "remove phrase boundaries in data and do not do analysis"); - define("m|mark=b", "mark phrase boundaries based on rests"); - define("r|remove=b", "remove phrase boundaries in data"); - define("c|color=s", "display color of analysis data"); + define("R|remove2=b", "remove phrase boundaries in data and do not do analysis"); + define("m|mark=b", "mark phrase boundaries based on rests"); + define("r|remove=b", "remove phrase boundaries in data"); + define("c|color=s", "display color of analysis data"); } @@ -111538,7 +112574,7 @@ bool Tool_phrase::hasPhraseMarks(HTp start) { // Tool_pline::Tool_pline(void) { - define("c|color=b", "color poetic lines (currently only by notes)"); + define("c|color=b", "color poetic lines (currently only by notes)"); define("o|overlap=b", "do overlap analysis/markup"); } @@ -111808,13 +112844,13 @@ void Tool_pline::getPlineInterpretations(HumdrumFile& infile, vector& token // Tool_pnum::Tool_pnum(void) { - define("b|base=i:midi", "numeric base of pitch to extract"); - define("D|no-duration=b", "do not include duration"); - define("c|pitch-class=b", "give numeric pitch-class rather than pitch"); - define("o|octave=b", "give octave rather than pitch"); - define("r|rest=s:0", "representation string for rests"); - define("R|no-rests=b", "do not include rests in conversion"); - define("x|attacks-only=b", "only mark lines with note attacks"); + define("b|base=i:midi", "numeric base of pitch to extract"); + define("D|no-duration=b", "do not include duration"); + define("c|pitch-class=b", "give numeric pitch-class rather than pitch"); + define("o|octave=b", "give octave rather than pitch"); + define("r|rest=s:0", "representation string for rests"); + define("R|no-rests=b", "do not include rests in conversion"); + define("x|attacks-only=b", "only mark lines with note attacks"); } @@ -112274,8 +113310,8 @@ void Tool_recip::initialize(HumdrumFile& infile) { // Tool_restfill::Tool_restfill(void) { - define("y|hidden-rests=b", "hide inserted rests"); - define("i|exinterp=s:kern", "type of spine to fill with rests"); + define("y|hidden-rests=b", "hide inserted rests"); + define("i|exinterp=s:kern", "type of spine to fill with rests"); } @@ -112514,24 +113550,24 @@ HumNum Tool_restfill::getNextTime(HTp token) { Tool_rid::Tool_rid(void) { // Humdrum Toolkit classic rid options: - define("D|all-data=b", "remove all data records"); - define("d|null-data=b", "remove null data records"); - define("G|all-global=b", "remove all global comments"); - define("g|null-global=b", "remove null global comments"); - define("I|all-interpretation=b", "remove all interpretation records"); - define("i|null-interpretation=b", "remove null interpretation records"); - define("L|all-local-comment=b", "remove all local comments"); - define("l|1|null-local-comment=b", "remove null local comments"); + define("D|all-data=b", "remove all data records"); + define("d|null-data=b", "remove null data records"); + define("G|all-global=b", "remove all global comments"); + define("g|null-global=b", "remove null global comments"); + define("I|all-interpretation=b", "remove all interpretation records"); + define("i|null-interpretation=b", "remove null interpretation records"); + define("L|all-local-comment=b", "remove all local comments"); + define("l|1|null-local-comment=b", "remove null local comments"); define("T|all-tandem-interpretation=b", "remove all tandem interpretations"); - define("U|u=b", "remove unnecessary (duplicate ex. interps."); - define("k|consider-kern-only=b", "for -d, only consider **kern spines."); - define("V=b","negate filtering effect of program."); - define("H|no-humdrum-syntax=b", "equivalent to -GLIMd."); + define("U|u=b", "remove unnecessary (duplicate ex. interps."); + define("k|consider-kern-only=b", "for -d, only consider **kern spines."); + define("V=b", "negate filtering effect of program."); + define("H|no-humdrum-syntax=b", "equivalent to -GLIMd."); // additional options - define("M|all-barlines=b", "remove measure lines"); - define("C|all-comments=b", "remove all comment lines"); - define("c=b", "remove global and local comment lines"); + define("M|all-barlines=b", "remove measure lines"); + define("C|all-comments=b", "remove all comment lines"); + define("c=b", "remove global and local comment lines"); } @@ -112891,7 +113927,7 @@ void Tool_ruthfix::createTiedNote(HTp left, HTp right) { Tool_sab2gs::Tool_sab2gs(void) { define("b|below=s:<", "Marker for displaying on next staff below"); - define("d|down=b", "Use only *down/*Xdown interpretations"); + define("d|down=b", "Use only *down/*Xdown interpretations"); } @@ -113108,7 +114144,7 @@ void Tool_sab2gs::printSpineSplit(HumdrumFile& infile, int index, vector& k // Ignore the second kern spine as it does not exist yet in the // output data. nextIndex++; - // Then store any non-kern spines between the second and third kern spines to + // Then store any non-kern spines between the second and third kern spines to // append to the end of the data line later. string postData; for (int i=nextIndex; i& k // output data. // HTp savedKernToken = infile.token(index, nextIndex); nextIndex++; - // Then store any non-kern spines between the second and third kern spines to + // Then store any non-kern spines between the second and third kern spines to // append to the end of the data line later. string postData; for (int i=nextIndex; i& // Save the second kern spine as it does not exist yet in the // output data. HTp savedKernToken = infile.token(index, nextIndex++); - // Then store any non-kern spines between the second and third kern spines to + // Then store any non-kern spines between the second and third kern spines to // append to the end of the data line later. string postData; for (int i=nextIndex; i& // Ignore the second kern spine as it does not exist yet in the // output data. nextIndex++; - // Then store any non-kern spines between the second and third kern spines to + // Then store any non-kern spines between the second and third kern spines to // append to the end of the data line later. string postData; for (int i=nextIndex; i& labelsequence, // Tool_tie::Tool_tie(void) { - define("s|split=b", "split overfill notes into tied notes across barlines."); - define("m|merge=b", "merge tied notes into a single note."); - define("p|printable=b", "merge tied notes only if single note is a printable note."); - define("M|mark=b", "Mark overfill notes."); - define("i|invisible=b", "Mark overfill barlines invisible."); - define("I|skip-invisible=b", "Skip invisible measures when splitting overfill durations."); + define("s|split=b", "split overfill notes into tied notes across barlines."); + define("m|merge=b", "merge tied notes into a single note."); + define("p|printable=b", "merge tied notes only if single note is a printable note."); + define("M|mark=b", "mark overfill notes."); + define("i|invisible=b", "mark overfill barlines invisible."); + define("I|skip-invisible=b", "skip invisible measures when splitting overfill durations."); } @@ -121148,10 +122185,10 @@ HumNum Tool_tie::getDurationToNextBarline(HTp tok) { // Tool_timebase::Tool_timebase(void) { - define("g|grace=b", "Keep grace notes"); - define("m|min=b", "Use minimum time in score for timebase"); - define("t|timebase=s:16", "Timebase rhythm"); - define("q|quiet=b", "Quite mode: Do not output warnings"); + define("g|grace=b", "keep grace notes"); + define("m|min=b", "use minimum time in score for timebase"); + define("t|timebase=s:16", "timebase rhythm"); + define("q|quiet=b", "quiet mode: Do not output warnings"); } @@ -121194,13 +122231,6 @@ bool Tool_timebase::run(HumdrumFile& infile) { // void Tool_timebase::processFile(HumdrumFile& infile) { - // Test code: - #ifdef __EMSCRIPTEN__ - std::cout << "Compiled with Emscripten into WebAssembly or JavaScript." << std::endl; - #else - std::cout << "Compiled as a native OS executable." << std::endl; - #endif - m_grace = getBoolean("grace"); m_quiet = getBoolean("quiet"); if (!getBoolean("timebase")) { @@ -121326,10 +122356,10 @@ Tool_transpose::Tool_transpose(void) { define("n|negate=b", "negate transposition indications"); define("rotation=b", "display transposition in half-steps"); - define("author=b", "author of program"); - define("version=b", "compilation info"); - define("example=b", "example usages"); - define("help=b", "short description"); + define("author=b", "author of program"); + define("version=b", "compilation info"); + define("example=b", "example usages"); + define("help=b", "short description"); } @@ -122905,9 +123935,9 @@ void Tool_transpose::initialize(HumdrumFile& infile) { // Tool_tremolo::Tool_tremolo(void) { - define("k|keep=b", "Keep tremolo rhythm markup"); - define("F|no-fill=b", "Do not fill in tremolo spaces"); - define("T|no-tremolo-interpretation=b", "Do not add *tremolo/*Xtremolo marks"); + define("k|keep=b", "keep tremolo rhythm markup"); + define("F|no-fill=b", "do not fill in tremolo spaces"); + define("T|no-tremolo-interpretation=b", "do not add *tremolo/*Xtremolo marks"); } @@ -123598,7 +124628,8 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { } if (infile[i].isInterpretation()) { for (j=0; jisKern()) { + HTp token = infile.token(i, j); + if (!token->isKern()) { continue; } if (infile[i].token(j)->compare(0, 3, "*k[") == 0) { @@ -123632,23 +124663,24 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { } for (j=0; jisKern()) { + HTp token = infile.token(i, j); + if (!token->isKern()) { continue; } - if (infile[i].token(j)->isNull()) { + if (token->isNull()) { continue; } - if (infile[i].token(j)->isRest()) { + if (token->isRest()) { continue; } - int subcount = infile[i].token(j)->getSubtokenCount(); - track = infile[i].token(j)->getTrack(); + int subcount = token->getSubtokenCount(); + track = token->getTrack(); HumRegex hre; int rindex = rtracks[track]; for (k=0; kgetSubtoken(k); + string subtok = token->getSubtoken(k); int b40 = Convert::kernToBase40(subtok); int diatonic = Convert::kernToBase7(subtok); if (diatonic < 0) { @@ -123670,12 +124702,12 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } else { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } } else if ((subtok.find("T") != string::npos) && !hre.search(subtok, "[tT]x")) { int nextup = getBase40(diatonic + 1, dstates[rindex][diatonic+1]); @@ -123686,12 +124718,12 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } else { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } } else if ((subtok.find("M") != string::npos) && !hre.search(subtok, "[Mm]x")) { // major-second upper mordent @@ -123703,12 +124735,12 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } else { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } } else if ((subtok.find("m") != string::npos) && !hre.search(subtok, "[Mm]x")) { // minor-second upper mordent @@ -123720,12 +124752,12 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } else { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } } else if ((subtok.find("W") != string::npos) && !hre.search(subtok, "[Ww]x")) { // major-second lower mordent @@ -123737,12 +124769,12 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } else { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } } else if ((subtok.find("w") != string::npos) && !hre.search(subtok, "[Ww]x")) { // minor-second lower mordent @@ -123754,12 +124786,12 @@ bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } else { if (m_xmark) { hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g"); } - infile[i].token(j)->replaceSubtoken(k, subtok); + token->replaceSubtoken(k, subtok); } } // deal with turns and inverted turns here. @@ -123835,19 +124867,19 @@ int Tool_trillspell::getBase40(int diatonic, int accidental) { // Tool_tspos::Tool_tspos(void) { - define("d|double=b", "highlight only doubled notes in triads"); - define("3|no-thirds=b", "do not color thirds"); - define("5|no-fifths=b", "do not color fifths"); - define("T|no-triads=b", "do not color full triads"); - define("m|minor-triads=b", "only analyze major triad"); - define("M|major-triads=b", "only analyze minor triads"); - define("x|attacks=b", "only process sonorities with three unique triadic pitch classes attacking at once (sustains in additional voices are allowed)"); - define("v|voice-count=i:0", "Only analyze sonorities with given voice count"); - define("c|compressed=b", "Compress music to see more on each system"); - define("top=b", "mark top voice in analysis output"); - define("t|table=b", "add analysis table above score"); - define("V|all-voices=b", "Require all voices in score to be sounding"); - define("Q|no-question=b", "Do not show question mark in table header"); + define("d|double=b", "highlight only doubled notes in triads"); + define("3|no-thirds=b", "do not color thirds"); + define("5|no-fifths=b", "do not color fifths"); + define("T|no-triads=b", "do not color full triads"); + define("m|minor-triads=b", "only analyze major triad"); + define("M|major-triads=b", "only analyze minor triads"); + define("x|attacks=b", "only process sonorities with three unique triadic pitch classes attacking at once (sustains in additional voices are allowed)"); + define("v|voice-count=i:0", "only analyze sonorities with given voice count"); + define("c|compressed=b", "compress music to see more on each system"); + define("top=b", "mark top voice in analysis output"); + define("t|table=b", "add analysis table above score"); + define("V|all-voices=b", "require all voices in score to be sounding"); + define("Q|no-question=b", "do not show question mark in table header"); } @@ -125199,4 +126231,265 @@ int Tool_tspos::logisticColorMap(double input, double max) { + +///////////////////////////////// +// +// Tool_vcross::Tool_vcross -- Set the recognized options for the tool. +// + +Tool_vcross::Tool_vcross(void) { +} + + + +///////////////////////////////// +// +// Tool_vcross::run -- Do the main work of the tool. +// + +bool Tool_vcross::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i> tokens; + int ctrack = -1; + for (int j=0; jisKern()) { + continue; + } + int track = token->getTrack(); + if (ctrack == -1) { + tokens.resize(tokens.size() + 1); + ctrack = track; + } else if (ctrack != track) { + tokens.resize(tokens.size() + 1); + ctrack = track; + } + tokens.back().push_back(token); + } + for (int i=1; i<(int)tokens.size(); i++) { + compareVoices(tokens.at(i-1), tokens.at(i)); + } +} + + + +////////////////////////////// +// +// Tool_vcross::compareVoices -- +// + +void Tool_vcross::compareVoices(vector& lower, vector& higher) { + vector> midihi(higher.size()); + vector> midilo(lower.size()); + + for (int i=0; i<(int)higher.size(); i++) { + getMidiInfo(midihi.at(i), higher.at(i)); + } + + for (int i=0; i<(int)lower.size(); i++) { + getMidiInfo(midilo.at(i), lower.at(i)); + } + + int highestLo = -1; + int lowestHi = -1; + + for (int i=0; i<(int)midihi.size(); i++) { + for (int j=0; j<(int)midihi.at(i).size(); j++) { + if (midihi[i][j] < 0) { + continue; + } + if (midihi[i][j] < 0) { + continue; + } else if (lowestHi < 0) { + lowestHi = midihi[i][j]; + } else if (midihi[i][j] < lowestHi) { + lowestHi = midihi[i][j]; + } + } + } + + for (int i=0; i<(int)midilo.size(); i++) { + for (int j=0; j<(int)midilo.at(i).size(); j++) { + if (midilo[i][j] < 0) { + continue; + } + if (midilo[i][j] > highestLo) { + highestLo = midilo[i][j]; + } + } + } + + if (highestLo < 0) { + return; + } + if (lowestHi < 0) { + return; + } + if (highestLo < lowestHi) { + return; + } + + for (int i=0; i<(int)midihi.size(); i++) { + for (int j=0; j<(int)midihi.at(i).size(); j++) { + if (midihi[i][j] < 0) { + continue; + } + if (midihi[i][j] < highestLo) { + if (!higher.at(i)->isNull()) { + string subtok = higher.at(i)->getSubtoken(j); + subtok += "🟦"; + m_blueQ = true; + higher.at(i)->replaceSubtoken(j, subtok); + } + } else if (midihi[i][j] == highestLo) { + if (!higher.at(i)->isNull()) { + string subtok = higher.at(i)->getSubtoken(j); + subtok += "🟩"; + m_greenQ = true; + higher.at(i)->replaceSubtoken(j, subtok); + } + } + } + } + + for (int i=0; i<(int)midilo.size(); i++) { + for (int j=0; j<(int)midilo.at(i).size(); j++) { + if (midilo[i][j] < 0) { + continue; + } + if (midilo[i][j] > lowestHi) { + if (!lower.at(i)->isNull()) { + string subtok = lower.at(i)->getSubtoken(j); + subtok += "🟥"; + m_redQ = true; + lower.at(i)->replaceSubtoken(j, subtok); + } + } else if (midilo[i][j] == lowestHi) { + if (!lower.at(i)->isNull()) { + string subtok = lower.at(i)->getSubtoken(j); + subtok += "🟩"; + m_greenQ = true; + lower.at(i)->replaceSubtoken(j, subtok); + } + } + } + } +} + + + +////////////////////////////// +// +// Tool_vcross::getMidiInfo -- +// + +void Tool_vcross::getMidiInfo(vector& midis, HTp token) { + if (token->isNull()) { + token = token->resolveNull(); + if (!token) { + midis.clear(); + return; + } + } + vector subtokens = token->getSubtokens(); + midis.resize(subtokens.size()); + for (int i=0; i<(int)subtokens.size(); i++) { + if (subtokens[i].find("r") != string::npos) { + midis.at(i) = -1; + continue; + } + midis.at(i) = Convert::kernToMidiNoteNumber(subtokens.at(i)); + } +} + + + } // end namespace hum From 6051c8ecead8f8e62418ea8d29f18b01fc46ead4 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 6 May 2024 11:30:21 +0200 Subject: [PATCH 129/383] allow use-symbols to be set to no explicitly --- src/iomusxml.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index 667e4d99a68..a7bb45ee05c 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -1658,9 +1658,17 @@ bool MusicXmlInput::ReadMusicXmlMeasure( for (pugi::xml_node::iterator it = node.begin(); it != node.end(); ++it) { // first check if there is a multi measure rest if (it->select_node(".//multiple-rest")) { - const int multiRestLength = it->select_node(".//multiple-rest").node().text().as_int(); + const pugi::xml_node multiRestNode = it->select_node(".//multiple-rest").node(); + const int multiRestLength = multiRestNode.text().as_int(); + const std::string symbols = multiRestNode.attribute("use-symbols").as_string(); MultiRest *multiRest = new MultiRest; - if (it->select_node(".//multiple-rest[@use-symbols='yes']")) multiRest->SetBlock(BOOLEAN_false); + if (symbols == "no") { + // default by MusicXML specification + multiRest->SetBlock(BOOLEAN_true); + } + else if (symbols == "yes") { + multiRest->SetBlock(BOOLEAN_false); + } multiRest->SetNum(multiRestLength); Layer *layer = SelectLayer(1, measure); AddLayerElement(layer, multiRest); From 45e50d5dbcc890e951237ce3fab8fdc4bcb7fb1f Mon Sep 17 00:00:00 2001 From: Serge Poltavski Date: Mon, 6 May 2024 14:26:57 +0300 Subject: [PATCH 130/383] cmake: win include fix for MSYS --- cmake/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 5d2b688547a..5c6927714f9 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -57,6 +57,10 @@ else() add_definitions(-DDEBUG) endif() + if(WIN32) + include_directories(../include/win32) + endif() + # jsonxx raises -Wdollar-in-identifier-extension # gcc 8.3.1 does not like -Wdollar-in-identifier-extension option. add_definitions(-Wall -W -pedantic -Wno-unused-parameter -Wno-dollar-in-identifier-extension) From d7e0d9c8ed9b429fac4fe61ff8cca4da6c3ab8a2 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Tue, 7 May 2024 11:01:43 +0200 Subject: [PATCH 131/383] Tested GH actions This configuration has been tested and should work. --- .github/workflows/python-ci-wheel.yml | 39 +++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index ee9ee651098..3f42b9a57ab 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -23,13 +23,25 @@ jobs: fail-fast: false # Build the wheels for Linux, Windows and macOS matrix: - os: [macos-latest, windows-latest, ubuntu-latest] + os: [macos-13, windows-latest, ubuntu-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] architecture: [x86, x64] include: - - os: macos-latest + - os: macos-13 architecture: x64 platform_id: macosx_* + # - os: macos-14 + # architecture: arm64 + # platform_id: macosx_* + # python-version: "3.10" + # - os: macos-14 + # architecture: arm64 + # platform_id: macosx_* + # python-version: "3.11" + # - os: macos-14 + # architecture: arm64 + # platform_id: macosx_* + # python-version: "3.12" - os: windows-latest architecture: x64 platform_id: win_amd64 @@ -40,10 +52,20 @@ jobs: architecture: x64 platform_id: manylinux_x86_64 exclude: - - os: macos-latest + - os: macos-13 architecture: x86 - os: ubuntu-latest architecture: x86 + # - os: macos-14 + # architecture: "x86" + # - os: macos-14 + # architecture: "x64" + # - os: macos-14 + # python-version: "3.8" + # - os: macos-14 + # python-version: "3.9" + # - os: macos-14 + # python-version: "3.10" steps: #===============================================# @@ -53,11 +75,12 @@ jobs: fetch-depth: 0 - uses: nuget/setup-nuget@v1 + if: always() && runner.os == 'Windows' with: nuget-version: "latest" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -82,7 +105,7 @@ jobs: #===============================================# # wheels - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 + uses: pypa/cibuildwheel@v2.17.0 with: output-dir: wheelhouse env: @@ -91,7 +114,7 @@ jobs: CIBW_ARCHS_MACOS: x86_64 arm64 CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=10.15 - CIBW_TEST_SKIP: cp*-macosx_arm64 + # CIBW_TEST_SKIP: cp*-macosx_arm64 CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_MANYLINUX_I686_IMAGE: manylinux2014 CIBW_BEFORE_ALL_MACOS: brew update && brew install swig @@ -154,7 +177,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" @@ -229,7 +252,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" architecture: "x64" From a6355f42309a75b2b6bb3317b0bb3d1b8c0d143f Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Tue, 7 May 2024 14:41:37 +0200 Subject: [PATCH 132/383] ci(gh-actions): include macos-14 in python wheel workflow (rebased) --- .github/workflows/python-ci-wheel.yml | 47 ++++++++++----------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index 3f42b9a57ab..1c0520f7069 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -23,25 +23,16 @@ jobs: fail-fast: false # Build the wheels for Linux, Windows and macOS matrix: - os: [macos-13, windows-latest, ubuntu-latest] + os: [macos-13, macos-14, windows-latest, ubuntu-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - architecture: [x86, x64] + architecture: [x86, x64, arm64] include: - os: macos-13 architecture: x64 - platform_id: macosx_* - # - os: macos-14 - # architecture: arm64 - # platform_id: macosx_* - # python-version: "3.10" - # - os: macos-14 - # architecture: arm64 - # platform_id: macosx_* - # python-version: "3.11" - # - os: macos-14 - # architecture: arm64 - # platform_id: macosx_* - # python-version: "3.12" + platform_id: macosx_x86_64 + - os: macos-14 + architecture: arm64 + platform_id: macosx_arm64 - os: windows-latest architecture: x64 platform_id: win_amd64 @@ -54,18 +45,18 @@ jobs: exclude: - os: macos-13 architecture: x86 + - os: macos-13 + architecture: arm64 + - os: macos-14 + architecture: x86 + - os: macos-14 + architecture: x64 - os: ubuntu-latest architecture: x86 - # - os: macos-14 - # architecture: "x86" - # - os: macos-14 - # architecture: "x64" - # - os: macos-14 - # python-version: "3.8" - # - os: macos-14 - # python-version: "3.9" - # - os: macos-14 - # python-version: "3.10" + - os: ubuntu-latest + architecture: arm64 + - os: windows-latest + architecture: arm64 steps: #===============================================# @@ -109,12 +100,10 @@ jobs: with: output-dir: wheelhouse env: - CIBW_SKIP: cp37-macosx_arm64 CIBW_BUILD: ${{ env.CIBW_BUILD_IDENTIFIER }} CIBW_ARCHS_MACOS: x86_64 arm64 CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=10.15 - # CIBW_TEST_SKIP: cp*-macosx_arm64 CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_MANYLINUX_I686_IMAGE: manylinux2014 CIBW_BEFORE_ALL_MACOS: brew update && brew install swig @@ -142,7 +131,7 @@ jobs: - name: Install from wheel on macOS working-directory: wheelhouse if: always() && runner.os == 'macOS' - run: python -m pip install ./*x86_64.whl + run: python -m pip install ./*.whl # Wildcard use is different with PowerShell # cf. https://stackoverflow.com/a/43900040 @@ -159,7 +148,7 @@ jobs: # Upload artifacts - uses: actions/upload-artifact@v3 with: - name: cibuildwheel-${{ runner.os }}-python-${{ matrix.python-version }} + name: cibuildwheel-${{ runner.os }}-python-${{ matrix.python-version }}-${{ matrix.architecture }} path: ./wheelhouse/*.whl #===============================================# From 056ea07768976f98087ccc6dd860ac136f58fb04 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Tue, 7 May 2024 15:20:54 +0200 Subject: [PATCH 133/383] ci(gh-actions): update actions and use hash for semver tags --- .github/workflows/python-ci-wheel.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index 1c0520f7069..2c337e47b39 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -61,17 +61,17 @@ jobs: steps: #===============================================# # Set up - - uses: actions/checkout@v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 0 - - uses: nuget/setup-nuget@v1 + - uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0 if: always() && runner.os == 'Windows' with: nuget-version: "latest" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -96,7 +96,7 @@ jobs: #===============================================# # wheels - name: Build wheels - uses: pypa/cibuildwheel@v2.17.0 + uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 with: output-dir: wheelhouse env: @@ -146,7 +146,7 @@ jobs: #===============================================# # Upload artifacts - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: cibuildwheel-${{ runner.os }}-python-${{ matrix.python-version }}-${{ matrix.architecture }} path: ./wheelhouse/*.whl @@ -161,12 +161,12 @@ jobs: steps: #===============================================# # Set up - - uses: actions/checkout@v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: "3.9" @@ -222,7 +222,7 @@ jobs: #===============================================# # Upload artifact - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: sdist-${{ runner.os }}-python-3.9 path: dist/*.tar.gz @@ -238,10 +238,10 @@ jobs: steps: #===============================================# # Set up - - uses: actions/checkout@v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set up Python 3.9 - uses: actions/setup-python@v5 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: "3.9" architecture: "x64" @@ -256,7 +256,7 @@ jobs: #===============================================# # Prepare artifacts - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: path: bindings/python/artifacts/ From 337ae37bdd1c109cc4807efbe98627001cc62404 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 7 May 2024 11:04:15 -0400 Subject: [PATCH 134/383] Fix jumping glyphs && clean up ToggleLigature() --- src/editortoolkit_neume.cpp | 96 ++++++++++++++----------------------- 1 file changed, 37 insertions(+), 59 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 0b9c7de6846..9e9f8287fa3 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1825,10 +1825,7 @@ bool EditorToolkitNeume::Set(std::string elementId, std::string attrType, std::s success = true; else if (AttModule::SetVisual(element, attrType, attrValue)) success = true; - if (success && m_doc->HasFacsimile()) { - m_doc->PrepareData(); - m_doc->GetDrawingPage()->LayOut(true); - } + m_editInfo.import("status", success ? "OK" : "FAILURE"); m_editInfo.import("message", success ? "" : "Could not set attribute '" + attrType + "' to '" + attrValue + "'."); return success; @@ -1992,10 +1989,6 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) pi->AdjustPitchByOffset(shift); } } - if (success && m_doc->HasFacsimile()) { - m_doc->PrepareData(); - m_doc->GetDrawingPage()->LayOut(true); - } m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -3359,8 +3352,6 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) bool success2 = false; Facsimile *facsimile = m_doc->GetFacsimile(); assert(facsimile); - Surface *surface = vrv_cast(facsimile->FindDescendantByType(SURFACE)); - assert(surface); std::string firstNcId = elementIds[0]; std::string secondNcId = elementIds[1]; // Check if you can get drawing page @@ -3386,79 +3377,66 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) return false; } - bool isLigature; + bool isLigature = false; if (firstNc->HasAttribute("ligated", "true") && secondNc->HasAttribute("ligated", "true")) { isLigature = true; } else { - isLigature = false; - Set(firstNc->GetID(), "tilt", ""); - Set(secondNc->GetID(), "tilt", ""); - Set(firstNc->GetID(), "curve", ""); - Set(secondNc->GetID(), "curve", ""); + Set(firstNcId, "tilt", ""); + Set(secondNcId, "tilt", ""); + Set(firstNcId, "curve", ""); + Set(secondNcId, "curve", ""); } - Zone *zone = new Zone(); - // set ligature to false and update zone of second Nc - if (isLigature) { - if (AttModule::SetNeumes(firstNc, "ligated", "false")) success1 = true; - - int ligUlx = firstNc->GetZone()->GetUlx(); - int ligUly = firstNc->GetZone()->GetUly(); - int ligLrx = firstNc->GetZone()->GetLrx(); - int ligLry = firstNc->GetZone()->GetLry(); + Zone *firstNcZone = firstNc->GetZone(); + Zone *secondNcZone = secondNc->GetZone(); - Staff *staff = dynamic_cast(firstNc->GetFirstAncestor(STAFF)); - assert(staff); + int ligUlx = firstNcZone->GetUlx(); + int ligUly = firstNcZone->GetUly(); + int ligLrx = firstNcZone->GetLrx(); + int ligLry = firstNcZone->GetLry(); - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + Staff *staff = dynamic_cast(firstNc->GetFirstAncestor(STAFF)); + assert(staff); + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - zone->SetUlx(ligUlx + noteWidth); - zone->SetUly(ligUly + noteHeight); - zone->SetLrx(ligLrx + noteWidth); - zone->SetLry(ligLry + noteHeight); + // set ligature to false and update zone of second Nc + if (isLigature) { + if (Set(firstNcId, "ligated", "false")) success1 = true; - secondNc->AttachZone(zone); + secondNcZone->SetUlx(ligUlx + noteWidth); + secondNcZone->SetUly(ligUly + noteHeight); + secondNcZone->SetLrx(ligLrx + noteWidth); + secondNcZone->SetLry(ligLry + noteHeight); - if (AttModule::SetNeumes(secondNc, "ligated", "false")) success2 = true; + if (Set(secondNcId, "ligated", "false")) success2 = true; } // set ligature to true and update zones to be the same - else if (!isLigature) { - if (AttModule::SetNeumes(firstNc, "ligated", "true")) success1 = true; - - zone->SetUlx(firstNc->GetZone()->GetUlx()); - zone->SetUly(firstNc->GetZone()->GetUly()); - zone->SetLrx(firstNc->GetZone()->GetLrx()); - zone->SetLry(firstNc->GetZone()->GetLry()); + else { + if (Set(firstNcId, "ligated", "true")) success1 = true; - secondNc->AttachZone(zone); + secondNcZone->SetUlx(ligUlx); + secondNcZone->SetUly(ligUly + noteHeight); + secondNcZone->SetLrx(ligLrx); + secondNcZone->SetLry(ligLry + noteHeight); - if (AttModule::SetNeumes(secondNc, "ligated", "true")) success2 = true; - } - // else { - // LogError("isLigature is invalid!"); - // m_editInfo.import("status", "FAILURE"); - // m_editInfo.import("message", "isLigature value '" + isLigature + "' is invalid."); - // return false; - // } - if (success1 && success2 && m_doc->HasFacsimile()) { - m_doc->PrepareData(); - m_doc->GetDrawingPage()->LayOut(true); + if (Set(secondNcId, "ligated", "true")) success2 = true; } - m_editInfo.import("status", "OK"); - m_editInfo.import("message", ""); + if (!(success1 && success2)) { LogWarning("Unable to update ligature attribute"); m_editInfo.import("message", "Unable to update ligature attribute."); m_editInfo.import("status", "WARNING"); + return false; } - surface->AddChild(zone); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); return success1 && success2; } From 5d66475a39cdde29d3d78b1122f81f5e4e419750 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 7 May 2024 11:05:25 -0400 Subject: [PATCH 135/383] Remove zone changes in drawing method --- src/view_element.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 3fc6cf08ad8..a2bba2eefea 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -678,19 +678,6 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); - if (m_doc->IsTranscription() && element->HasFacs()) { - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - - FacsimileInterface *fi = element->GetFacsimileInterface(); - fi->GetZone()->SetUlx(x); - fi->GetZone()->SetUly(ToDeviceContextY(y)); - fi->GetZone()->SetLrx(x + noteWidth); - fi->GetZone()->SetLry(ToDeviceContextY(y - noteHeight)); - } - // Possibly draw enclosing brackets this->DrawClefEnclosing(dc, clef, staff, sym, x, y); From 28271fbdfad13c55658fd59c1a8ae53071950437 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 7 May 2024 17:56:50 +0200 Subject: [PATCH 136/383] Start development 4.3.0 --- CHANGELOG.md | 8 ++++++++ Verovio.podspec | 2 +- bindings/java/pom.xml | 2 +- bindings/python/.pypi-version | 2 +- codemeta.json | 4 ++-- emscripten/npm/package.json | 2 +- include/vrv/vrvdef.h | 2 +- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc3eedc0ce..bde5a9c1af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog ## [unreleased] + +## [4.2.1] - 2024-05-07 +* Fix GitHub actions (Python release only) + +## [4.2.0] - 2024-05-05 * Support for `fTrem@unitdur` (@eNote-GmbH) * Upgrade to C++20 +* Update of the Midifile library +* Fix lyric position in MIDI output +* Fix string formatting output with some locale configurations (@ammatwain) ## [4.1.0] - 2023-12-15 * Support for staves ordered by `scoreDef` diff --git a/Verovio.podspec b/Verovio.podspec index 93b69a5ae10..a9ec8abbf82 100644 --- a/Verovio.podspec +++ b/Verovio.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Verovio' - s.version = '4.2.0-dev' + s.version = '4.3.0-dev' s.license = { :type => 'LGPL', :file => 'COPYING' } s.homepage = 'https://www.verovio.org/index.xhtml' s.authors = { 'Contributors List' => 'https://github.com/rism-digital/verovio/graphs/contributors' } diff --git a/bindings/java/pom.xml b/bindings/java/pom.xml index 33ef0597eb8..12e049ef9e8 100644 --- a/bindings/java/pom.xml +++ b/bindings/java/pom.xml @@ -4,7 +4,7 @@ org.rism.verovio VerovioToolkit - 4.2.0-dev + 4.3.0-dev jar VerovioToolkit diff --git a/bindings/python/.pypi-version b/bindings/python/.pypi-version index 84066c019f4..76d263aca3f 100644 --- a/bindings/python/.pypi-version +++ b/bindings/python/.pypi-version @@ -1,3 +1,3 @@ # dummy file used by setup.py for counting revisions when publishing to test.pypi # counting can be reset by making a change to this file -4.2.0 +4.3.0 diff --git a/codemeta.json b/codemeta.json index 390078a93ba..6461b01b695 100644 --- a/codemeta.json +++ b/codemeta.json @@ -4,8 +4,8 @@ "identifier": "Verovio", "name": "Verovio", "description": "Verovio is a fast, portable and lightweight open-source library for engraving Music Encoding Initiative (MEI) music scores into SVG.", - "softwareVersion": "4.2.0-dev", - "datePublished": "2023-12-15", + "softwareVersion": "4.3.0-dev", + "datePublished": "2024-05-07", "license": "https://www.gnu.org/licenses/lgpl-3.0", "programmingLanguage": [{ "@type": "ComputerLanguage", diff --git a/emscripten/npm/package.json b/emscripten/npm/package.json index 68404c71027..ee8f09137b4 100644 --- a/emscripten/npm/package.json +++ b/emscripten/npm/package.json @@ -1,6 +1,6 @@ { "name": "verovio", - "version": "4.2.0-alpha", + "version": "4.3.0-alpha", "description": "This is the stable version of the verovio package", "main": "dist/verovio-toolkit-wasm.js", "exports": { diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index eb3644af011..526d3af9fac 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -39,7 +39,7 @@ namespace vrv { //---------------------------------------------------------------------------- #define VERSION_MAJOR 4 -#define VERSION_MINOR 2 +#define VERSION_MINOR 3 #define VERSION_REVISION 0 // Adds "-dev" in the version number - should be set to false for releases #define VERSION_DEV true From 04a7d454646a879acf544c63a057837ea80a9831 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Wed, 8 May 2024 21:09:17 +0200 Subject: [PATCH 137/383] update actions --- .github/workflows/ci_build.yml | 6 +++--- .github/workflows/tests_build.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index ca515fa61ad..422c595105e 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -276,7 +276,7 @@ jobs: - name: Upload js build artifact (${{ matrix.toolkit.target }}) if: ${{ matrix.toolkit.upload == true }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.TOOLKIT_BUILD }} path: ${{ github.workspace }}/${{ env.TEMP_DIR }}/${{ matrix.toolkit.filepath }} @@ -301,7 +301,7 @@ jobs: run: cp data/*.css $GITHUB_WORKSPACE/$TEMP_DIR/data/ - name: Upload font data artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.TOOLKIT_BUILD }} path: ${{ github.workspace }}/${{ env.TEMP_DIR }} @@ -455,7 +455,7 @@ jobs: run: (cat verovio.conf ; echo "OUTPUT_DIRECTORY = $GITHUB_WORKSPACE/$DOXYGEN_DIR") | doxygen - - name: Upload doxygen build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.DOC_BUILD }} path: ${{ github.workspace }}/${{ env.DOXYGEN_DIR }} diff --git a/.github/workflows/tests_build.yml b/.github/workflows/tests_build.yml index 042d65eaf72..a76db42cad0 100644 --- a/.github/workflows/tests_build.yml +++ b/.github/workflows/tests_build.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: FranzDiebold/github-env-vars-action@v2.7.0 + - uses: FranzDiebold/github-env-vars-action@v2.8.0 - name: Get Short SHA run: | echo "SHORT_SHA=`echo ${{ github.event.pull_request.head.sha }} | cut -c1-7`" >> $GITHUB_ENV @@ -109,14 +109,14 @@ jobs: ls -al - name: Upload results as artefacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-suite-diff path: ${{ github.workspace }}/${{ env.OUTPUT_DIR }}/ - name: Check existence of the log.md file id: check_files - uses: andstor/file-existence-action@v2 + uses: andstor/file-existence-action@v3 with: files: "${{ github.workspace }}/${{ env.OUTPUT_DIR }}/log.md" From b77cbfdd3c7594951a407459c608be1d7ef31d3f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 9 May 2024 14:34:54 +0200 Subject: [PATCH 138/383] Revert "Update actions" --- .github/workflows/ci_build.yml | 6 +++--- .github/workflows/tests_build.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 422c595105e..ca515fa61ad 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -276,7 +276,7 @@ jobs: - name: Upload js build artifact (${{ matrix.toolkit.target }}) if: ${{ matrix.toolkit.upload == true }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: ${{ env.TOOLKIT_BUILD }} path: ${{ github.workspace }}/${{ env.TEMP_DIR }}/${{ matrix.toolkit.filepath }} @@ -301,7 +301,7 @@ jobs: run: cp data/*.css $GITHUB_WORKSPACE/$TEMP_DIR/data/ - name: Upload font data artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: ${{ env.TOOLKIT_BUILD }} path: ${{ github.workspace }}/${{ env.TEMP_DIR }} @@ -455,7 +455,7 @@ jobs: run: (cat verovio.conf ; echo "OUTPUT_DIRECTORY = $GITHUB_WORKSPACE/$DOXYGEN_DIR") | doxygen - - name: Upload doxygen build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: ${{ env.DOC_BUILD }} path: ${{ github.workspace }}/${{ env.DOXYGEN_DIR }} diff --git a/.github/workflows/tests_build.yml b/.github/workflows/tests_build.yml index a76db42cad0..042d65eaf72 100644 --- a/.github/workflows/tests_build.yml +++ b/.github/workflows/tests_build.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: FranzDiebold/github-env-vars-action@v2.8.0 + - uses: FranzDiebold/github-env-vars-action@v2.7.0 - name: Get Short SHA run: | echo "SHORT_SHA=`echo ${{ github.event.pull_request.head.sha }} | cut -c1-7`" >> $GITHUB_ENV @@ -109,14 +109,14 @@ jobs: ls -al - name: Upload results as artefacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: test-suite-diff path: ${{ github.workspace }}/${{ env.OUTPUT_DIR }}/ - name: Check existence of the log.md file id: check_files - uses: andstor/file-existence-action@v3 + uses: andstor/file-existence-action@v2 with: files: "${{ github.workspace }}/${{ env.OUTPUT_DIR }}/log.md" From 2d84bf24651018cea106f66757e721fc1a384509 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 9 May 2024 18:22:33 +0200 Subject: [PATCH 139/383] update only test build action --- .github/workflows/tests_build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests_build.yml b/.github/workflows/tests_build.yml index 042d65eaf72..a76db42cad0 100644 --- a/.github/workflows/tests_build.yml +++ b/.github/workflows/tests_build.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: FranzDiebold/github-env-vars-action@v2.7.0 + - uses: FranzDiebold/github-env-vars-action@v2.8.0 - name: Get Short SHA run: | echo "SHORT_SHA=`echo ${{ github.event.pull_request.head.sha }} | cut -c1-7`" >> $GITHUB_ENV @@ -109,14 +109,14 @@ jobs: ls -al - name: Upload results as artefacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-suite-diff path: ${{ github.workspace }}/${{ env.OUTPUT_DIR }}/ - name: Check existence of the log.md file id: check_files - uses: andstor/file-existence-action@v2 + uses: andstor/file-existence-action@v3 with: files: "${{ github.workspace }}/${{ env.OUTPUT_DIR }}/log.md" From 142e75b1cbd650c74aeb3b774b4305d189a867f8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 14 May 2024 08:53:38 +0200 Subject: [PATCH 140/383] Add missing functor file to xcode --- Verovio.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 9d46f1aadd8..06ae5b7bc23 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -785,6 +785,12 @@ 4DCB7AA426D3C9600047F01D /* crc.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DCB7AA226D3C9600047F01D /* crc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DD11DC42240E78B00A405D8 /* c_wrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD11DC22240E78B00A405D8 /* c_wrapper.cpp */; }; 4DD11DC52240E78B00A405D8 /* c_wrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD11DC32240E78B00A405D8 /* c_wrapper.h */; }; + 4DD49C662BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD49C652BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp */; }; + 4DD49C682BECC096006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD49C652BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp */; }; + 4DD49C692BECC097006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD49C652BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp */; }; + 4DD49C6A2BECC098006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD49C652BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp */; }; + 4DD49C6B2BECC0A0006D1C2E /* adjustyrelfortranscriptionfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD49C672BECC083006D1C2E /* adjustyrelfortranscriptionfunctor.h */; }; + 4DD49C6C2BECC0A1006D1C2E /* adjustyrelfortranscriptionfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD49C672BECC083006D1C2E /* adjustyrelfortranscriptionfunctor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DD7C0FC27A55CEA00B9C017 /* timemap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD7C0FB27A55CEA00B9C017 /* timemap.cpp */; }; 4DD7C0FD27A55CEA00B9C017 /* timemap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DD7C0FB27A55CEA00B9C017 /* timemap.cpp */; }; 4DD7C0FF27A55CFD00B9C017 /* timemap.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD7C0FE27A55CFD00B9C017 /* timemap.h */; }; @@ -2016,6 +2022,8 @@ 4DCB7AA226D3C9600047F01D /* crc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crc.h; path = include/crc/crc.h; sourceTree = SOURCE_ROOT; }; 4DD11DC22240E78B00A405D8 /* c_wrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = c_wrapper.cpp; path = tools/c_wrapper.cpp; sourceTree = ""; }; 4DD11DC32240E78B00A405D8 /* c_wrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = c_wrapper.h; path = tools/c_wrapper.h; sourceTree = ""; }; + 4DD49C652BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = adjustyrelfortranscriptionfunctor.cpp; path = src/adjustyrelfortranscriptionfunctor.cpp; sourceTree = ""; }; + 4DD49C672BECC083006D1C2E /* adjustyrelfortranscriptionfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = adjustyrelfortranscriptionfunctor.h; path = include/vrv/adjustyrelfortranscriptionfunctor.h; sourceTree = ""; }; 4DD7C0FB27A55CEA00B9C017 /* timemap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = timemap.cpp; path = src/timemap.cpp; sourceTree = ""; }; 4DD7C0FE27A55CFD00B9C017 /* timemap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = timemap.h; path = include/vrv/timemap.h; sourceTree = ""; }; 4DDBBB551C7AE43E00054AFF /* hairpin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hairpin.h; path = include/vrv/hairpin.h; sourceTree = ""; }; @@ -3088,6 +3096,8 @@ E7A3790929BB41CD00E3BA98 /* adjustxposfunctor.h */, E78F204929D98D2300CD5910 /* adjustxrelfortranscriptionfunctor.cpp */, E78F204629D98CD400CD5910 /* adjustxrelfortranscriptionfunctor.h */, + 4DD49C652BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp */, + 4DD49C672BECC083006D1C2E /* adjustyrelfortranscriptionfunctor.h */, E7ED8A6729C111C300735875 /* cachehorizontallayoutfunctor.cpp */, E7ED8A6429C1119000735875 /* cachehorizontallayoutfunctor.h */, E790165E298BCB27008FDB4E /* calcalignmentxposfunctor.cpp */, @@ -3240,6 +3250,7 @@ 4DACCA172990F2E600B55913 /* attdef.h in Headers */, 4DB3D8EC1F83D18300B5FC2B /* proport.h in Headers */, 4DEC4DDE21C8295700D1D273 /* lem.h in Headers */, + 4DD49C6B2BECC0A0006D1C2E /* adjustyrelfortranscriptionfunctor.h in Headers */, E778BDAE29D5BD3D00672D51 /* adjuststaffoverlapfunctor.h in Headers */, E7E9C11A29B0EF9600CFCE2F /* adjusttempofunctor.h in Headers */, 8F59294518854BF800FE51AD /* layerelement.h in Headers */, @@ -3479,6 +3490,7 @@ E790165A298BCA97008FDB4E /* calcalignmentxposfunctor.h in Headers */, BB4C4B9622A932E5001F6AF0 /* drawinginterface.h in Headers */, BB4C4B4022A932D7001F6AF0 /* barline.h in Headers */, + 4DD49C6C2BECC0A1006D1C2E /* adjustyrelfortranscriptionfunctor.h in Headers */, BB4C4B9E22A932E5001F6AF0 /* plistinterface.h in Headers */, 4DA0EADE22BB77AF00A7EBEB /* zone.h in Headers */, BB4C4A9122A9328F001F6AF0 /* boundingbox.h in Headers */, @@ -4079,6 +4091,7 @@ 4D1694391E3A44F300569BF4 /* tuplet.cpp in Sources */, 4D72A5E0208A37F0009DEC1E /* mnum.cpp in Sources */, 4D4FCD0D1F5455FF0009C455 /* staffgrp.cpp in Sources */, + 4DD49C682BECC096006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */, 4D16943A1E3A44F300569BF4 /* pugixml.cpp in Sources */, 403BEFF5206C00E900D022D5 /* multirest.cpp in Sources */, 4DA0EAEB22BB77C300A7EBEB /* editortoolkit_neume.cpp in Sources */, @@ -4341,6 +4354,7 @@ E778BDB129D5BD6000672D51 /* adjuststaffoverlapfunctor.cpp in Sources */, E7A03CD429D6176000C02941 /* adjusttupletsyfunctor.cpp in Sources */, 403BEFEE206C00B500D022D5 /* mrpt.cpp in Sources */, + 4DD49C662BECC073006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */, 8F086F04188539540037FD8E /* tie.cpp in Sources */, 4D1BE7711C688F5A0086DC0E /* MidiFile.cpp in Sources */, 4D89F9122018A93300A4D336 /* svg.cpp in Sources */, @@ -4646,6 +4660,7 @@ 4DACC9BA2990F29A00B55913 /* atts_frettab.cpp in Sources */, 8F3DD32618854B090051330C /* iodarms.cpp in Sources */, 8F3DD32818854B090051330C /* iomei.cpp in Sources */, + 4DD49C6A2BECC098006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */, 8F3DD32A18854B090051330C /* iomusxml.cpp in Sources */, 40E1CEDF205060FD0007C8AF /* labelabbr.cpp in Sources */, 4DEC4DA821C81ED400D1D273 /* reg.cpp in Sources */, @@ -4930,6 +4945,7 @@ 4DA0EAED22BB77C300A7EBEB /* editortoolkit_neume.cpp in Sources */, BB4C4B3722A932CF001F6AF0 /* trill.cpp in Sources */, BB4C4B1F22A932CF001F6AF0 /* breath.cpp in Sources */, + 4DD49C692BECC097006D1C2E /* adjustyrelfortranscriptionfunctor.cpp in Sources */, BB4C4AC522A932B6001F6AF0 /* measure.cpp in Sources */, 4D2461DE246BE2E9002BBCCD /* expansionmap.cpp in Sources */, E7BCFFB6281297980012513D /* resources.cpp in Sources */, From e0816e072dc17e22fd919ecc9d34910e624d0ef0 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 14 May 2024 09:36:47 -0400 Subject: [PATCH 141/383] Remove debug log errors --- src/editortoolkit_neume.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 9e9f8287fa3..f7455216bb1 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2685,7 +2685,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e // if there are no full parents we need to make a new one to attach everything to if (fullParents.empty()) { - LogError("empty"); if (elementClass == NC) { parent = new Neume(); } @@ -2812,8 +2811,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e } } - LogError("2"); - // change the pitch of any pitched elements whose clef may have changed assert(newClef); ListOfObjects pitchedChildren; @@ -2828,7 +2825,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e } } } - LogError("3"); // Delete any empty parents for (auto it = parents.begin(); it != parents.end(); ++it) { From 9b23c6ba967b2dd78dbb8e210b1aa105afe94f45 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Tue, 14 May 2024 16:16:13 +0200 Subject: [PATCH 142/383] fix #3677 --- src/iomusxml.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index a7bb45ee05c..ff211065ffc 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -1834,6 +1834,8 @@ void MusicXmlInput::ReadMusicXmlAttributes( } section->AddChild(scoreDef); + } else if (time && node.select_node("ancestor::part[(preceding-sibling::part)]")) { + m_meterUnit = time.child("beat-type").text().as_int(); } pugi::xpath_node measureRepeat = node.select_node("measure-style/measure-repeat"); From dc3744942f95563c5a4632e0078cb69123202b11 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Tue, 14 May 2024 16:21:05 +0200 Subject: [PATCH 143/383] style: formatting --- src/iomusxml.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index ff211065ffc..001017a6f9d 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -1834,7 +1834,8 @@ void MusicXmlInput::ReadMusicXmlAttributes( } section->AddChild(scoreDef); - } else if (time && node.select_node("ancestor::part[(preceding-sibling::part)]")) { + } + else if (time && node.select_node("ancestor::part[(preceding-sibling::part)]")) { m_meterUnit = time.child("beat-type").text().as_int(); } From 0c7623e9ac4bc224c61a5cd4be1f425a2364e180 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 14 May 2024 13:12:24 -0400 Subject: [PATCH 144/383] Remove automatic staff changing for custos and accid Neon issue https://github.com/DDMAL/Neon/issues/1208 --- src/editortoolkit_neume.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index f7455216bb1..d17546ce48a 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -521,7 +521,6 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) zone->ShiftByXY(x, -y); AdjustPitchFromPosition(element); - ChangeStaff(elementId); } else if (element->HasInterface(INTERFACE_PITCH) || element->Is(NEUME) || element->Is(SYLLABLE)) { Layer *layer = dynamic_cast(element->GetFirstAncestor(LAYER)); @@ -714,7 +713,6 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) assert(zone); zone->ShiftByXY(x, -y); } - ChangeStaff(elementId); } else if (element->Is(DIVLINE)) { DivLine *divLine = dynamic_cast(element); From 4e9ece81704dfb2af63eef703a3ed60a9f497642 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 14 May 2024 13:12:44 -0400 Subject: [PATCH 145/383] Clean up absolute calculation --- include/vrv/editortoolkit_neume.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index 85ec79836cb..90dd931c667 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -122,8 +122,8 @@ struct ClosestBB { int offset = (x - ulx) * tan(rotate * M_PI / 180.0); uly = uly + offset; lry = lry + offset; - int xDiff = std::max((ulx > x ? ulx - x : 0), (x > lrx ? x - lrx : 0)); - int yDiff = std::max((uly > y ? uly - y : 0), (y > lry ? y - lry : 0)); + int xDiff = std::abs(x - ulx); + int yDiff = std::abs(y - uly); return sqrt(xDiff * xDiff + yDiff * yDiff); } @@ -143,6 +143,7 @@ struct ClosestBB { }; // To be used with std::stable_sort to find the position to insert a new accid / divLine +// TODO: use closesBB instead struct ClosestNeume { int x; int y; From 92e0b029f8fc8de8515b1891c3de0d8bd370778a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 15 May 2024 09:12:16 +0200 Subject: [PATCH 146/383] Set deployment target in xcode [skip-ci] --- Verovio.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 06ae5b7bc23..9c99a0a38a6 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -5172,7 +5172,7 @@ "$(inherited)", NO_HUMDRUM_SUPPORT, ); - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5184,7 +5184,7 @@ CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; GCC_PREPROCESSOR_DEFINITIONS = NO_HUMDRUM_SUPPORT; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; From 5cc0512a953c689732ae254d791f033710599a43 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 15 May 2024 09:52:57 -0400 Subject: [PATCH 147/383] Fix clef dragging and staff changing Neon issue https://github.com/DDMAL/Neon/issues/1208 --- src/editortoolkit_neume.cpp | 6 +++--- src/staff.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index d17546ce48a..2487fcd9043 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -383,9 +383,9 @@ bool EditorToolkitNeume::ClefMovementHandler(Clef *clef, int x, int y) if (clef->HasFacs()) { Zone *zone = clef->GetZone(); assert(zone); - zone->ShiftByXY(x, - (clefLine - initialClefLine) * 2 * staff->m_drawingStaffSize - - x * tan(staff->GetDrawingRotate() * M_PI / 180.0)); + int y = (clefLine - initialClefLine) * 2 * staff->m_drawingStaffSize + - x * tan(staff->GetDrawingRotate() * M_PI / 180.0); + zone->ShiftByXY(x, -y); } layer->ReorderByXPos(); diff --git a/src/staff.cpp b/src/staff.cpp index fc45d680088..9360300b579 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -161,7 +161,7 @@ double Staff::GetDrawingRotate() const if (this->HasFacs()) { const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); assert(doc); - if (doc->IsFacs()) { + if (doc->IsFacs() || doc->IsTranscription()) { return FacsimileInterface::GetDrawingRotate(); } } From 62b16fd36fefc574de9623591fc6ace622ab17bb Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 15 May 2024 10:00:35 -0400 Subject: [PATCH 148/383] Remove unused variables --- src/editortoolkit_neume.cpp | 2 -- src/view_neume.cpp | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 2487fcd9043..5509dbbdc26 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -3344,8 +3344,6 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) assert(elementIds.size() == 2); bool success1 = false; bool success2 = false; - Facsimile *facsimile = m_doc->GetFacsimile(); - assert(facsimile); std::string firstNcId = elementIds[0]; std::string secondNcId = elementIds[1]; // Check if you can get drawing page diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 51ea1373dff..b332c29a859 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -63,9 +63,6 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer assert(staff); assert(measure); - Liquescent *liquescent = dynamic_cast(element); - assert(liquescent); - struct drawingParams { wchar_t fontNo = SMUFL_E990_chantPunctum; wchar_t fontNoLiq[5] = {}; @@ -83,7 +80,6 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer int clefLine = clef->GetLine(); Nc *nc = dynamic_cast(element->GetParent()); - assert(liquescent); if (nc->GetCurve() == curvatureDirection_CURVE_c) { params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; From 559d8d7638aeaaee37de814ea888a277b51a48b9 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 12:48:11 +0200 Subject: [PATCH 149/383] Revert "Add support for neumes encoded line by line" --- .../vrv/adjustyrelfortranscriptionfunctor.h | 56 --- include/vrv/devicecontext.h | 7 - include/vrv/doc.h | 14 - include/vrv/editortoolkit_neume.h | 25 +- include/vrv/facsimilefunctor.h | 6 +- include/vrv/facsimileinterface.h | 7 - include/vrv/layerelement.h | 1 - include/vrv/measure.h | 14 +- include/vrv/staff.h | 17 - include/vrv/view.h | 1 - include/vrv/vrvdef.h | 8 - src/adjustyrelfortranscriptionfunctor.cpp | 35 -- src/convertfunctor.cpp | 18 +- src/devicecontext.cpp | 5 - src/doc.cpp | 11 +- src/editortoolkit_neume.cpp | 434 ++++++------------ src/facsimilefunctor.cpp | 75 +-- src/iodarms.cpp | 2 +- src/iohumdrum.cpp | 2 +- src/iomei.cpp | 50 +- src/iopae.cpp | 4 +- src/layerelement.cpp | 19 +- src/measure.cpp | 25 +- src/miscfunctor.cpp | 1 - src/page.cpp | 3 - src/savefunctor.cpp | 4 +- src/staff.cpp | 17 +- src/svgdevicecontext.cpp | 4 +- src/toolkit.cpp | 3 +- src/view_element.cpp | 84 +++- src/view_neume.cpp | 217 ++++----- src/view_page.cpp | 17 +- src/view_text.cpp | 13 +- 33 files changed, 420 insertions(+), 779 deletions(-) delete mode 100644 include/vrv/adjustyrelfortranscriptionfunctor.h delete mode 100644 src/adjustyrelfortranscriptionfunctor.cpp diff --git a/include/vrv/adjustyrelfortranscriptionfunctor.h b/include/vrv/adjustyrelfortranscriptionfunctor.h deleted file mode 100644 index af26cbda9e2..00000000000 --- a/include/vrv/adjustyrelfortranscriptionfunctor.h +++ /dev/null @@ -1,56 +0,0 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: adjustyrelfortranscriptionfunctor.h -// Author: Yinan Zhou -// Created: 2024 -// Copyright (c) Authors and others. All rights reserved. -///////////////////////////////////////////////////////////////////////////// - -#ifndef __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ -#define __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ - -#include "functor.h" - -namespace vrv { - -//---------------------------------------------------------------------------- -// AdjustYRelForTranscriptionFunctor -//---------------------------------------------------------------------------- - -/** - * This class adjusts the YRel positions taking into account the bounding boxes. - */ -class AdjustYRelForTranscriptionFunctor : public Functor { -public: - /** - * @name Constructors, destructors - */ - ///@{ - AdjustYRelForTranscriptionFunctor(); - virtual ~AdjustYRelForTranscriptionFunctor() = default; - ///@} - - /* - * Abstract base implementation - */ - bool ImplementsEndInterface() const override { return false; } - - /* - * Functor interface - */ - ///@{ - FunctorCode VisitLayerElement(LayerElement *layerElement) override; - ///@} - -protected: - // -private: - // -public: - // -private: - // -}; - -} // namespace vrv - -#endif // __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index 95c62a81208..b319c835a1a 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -74,7 +74,6 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; - m_viewBoxFactor = (double)DEFINITION_FACTOR; } DeviceContext(ClassId classId) { @@ -90,7 +89,6 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; - m_viewBoxFactor = (double)DEFINITION_FACTOR; } virtual ~DeviceContext(){}; ClassId GetClassId() const { return m_classId; } @@ -126,14 +124,12 @@ class DeviceContext { m_baseWidth = width; m_baseHeight = height; } - void SetViewBoxFactor(double ppuFactor); int GetWidth() const { return m_width; } int GetHeight() const { return m_height; } int GetContentHeight() const { return m_contentHeight; } double GetUserScaleX() { return m_userScaleX; } double GetUserScaleY() { return m_userScaleY; } std::pair GetBaseSize() const { return std::make_pair(m_baseWidth, m_baseHeight); } - double GetViewBoxFactor() const { return m_viewBoxFactor; } ///@} /** @@ -369,9 +365,6 @@ class DeviceContext { /** stores the scale as requested by the used */ double m_userScaleX; double m_userScaleY; - - /** stores the viewbox factor taking into account the DEFINTION_FACTOR and the PPU */ - double m_viewBoxFactor; }; } // namespace vrv diff --git a/include/vrv/doc.h b/include/vrv/doc.h index 48876eec45e..687f6f068e2 100644 --- a/include/vrv/doc.h +++ b/include/vrv/doc.h @@ -451,14 +451,6 @@ class Doc : public Object { bool IsMensuralMusicOnly() const { return m_isMensuralMusicOnly; } ///@} - /** - * @name Setter for and getter for neume-line flag - */ - ///@{ - void SetNeumeLines(bool isNeumeLines) { m_isNeumeLines = isNeumeLines; } - bool IsNeumeLines() const { return m_isNeumeLines; } - ///@} - /** * @name Setter and getter for facsimile */ @@ -668,12 +660,6 @@ class Doc : public Object { */ bool m_isMensuralMusicOnly; - /** - * A flag to indicate that the document contains neume lines. - * This is a special document type where neume lines are encoded with
- */ - bool m_isNeumeLines; - /** Page width (MEI scoredef@page.width) - currently not saved */ int m_pageWidth; /** Page height (MEI scoredef@page.height) - currently not saved */ diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index 85ec79836cb..11828f083cb 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -16,7 +16,6 @@ #include "doc.h" #include "editortoolkit.h" -#include "measure.h" #include "view.h" #include "vrv.h" #include "zone.h" @@ -51,8 +50,6 @@ class EditorToolkitNeume : public EditorToolkit { bool Set(std::string elementId, std::string attrType, std::string attrValue); bool SetText(std::string elementId, const std::string &text); bool SetClef(std::string elementId, std::string shape); - bool SetLiquescent(std::string elementId, std::string shape); - bool SortStaves(); bool Split(std::string elementId, int x); bool SplitNeume(std::string elementId, std::string ncId); bool Remove(std::string elementId); @@ -83,7 +80,6 @@ class EditorToolkitNeume : public EditorToolkit { bool ParseSetAction(jsonxx::Object param, std::string *elementId, std::string *attrType, std::string *attrValue); bool ParseSetTextAction(jsonxx::Object param, std::string *elementId, std::string *text); bool ParseSetClefAction(jsonxx::Object param, std::string *elementId, std::string *shape); - bool ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *shape); bool ParseSplitAction(jsonxx::Object param, std::string *elementId, int *x); bool ParseSplitNeumeAction(jsonxx::Object param, std::string *elementId, std::string *ncId); bool ParseRemoveAction(jsonxx::Object param, std::string *elementId); @@ -181,26 +177,11 @@ struct ClosestNeume { struct StaffSort { // Sort staves left-to-right and top-to-bottom // Sort by y if there is no intersection, by x if there is x intersection is smaller than half length of staff line - - // Update 2024-04: - // Used only in neume lines, - // System->(Measure->Staff) - // Need to sort Measure to sort staff bool operator()(Object *a, Object *b) { - if (!a->Is(SYSTEM) || !b->Is(SYSTEM)) return false; - if (!a->FindDescendantByType(MEASURE) || !b->FindDescendantByType(MEASURE)) return false; - Measure *measureA = dynamic_cast(a->FindDescendantByType(MEASURE)); - Measure *measureB = dynamic_cast(b->FindDescendantByType(MEASURE)); - if (!measureA->IsNeumeLine() || !measureB->IsNeumeLine()) return true; - Object *staffA = a->FindDescendantByType(STAFF); - Object *staffB = b->FindDescendantByType(STAFF); - assert(staffA); - assert(staffB); - Zone *zoneA = staffA->GetFacsimileInterface()->GetZone(); - Zone *zoneB = staffB->GetFacsimileInterface()->GetZone(); - assert(zoneA); - assert(zoneB); + if (!a->GetFacsimileInterface() || !b->GetFacsimileInterface()) return true; + Zone *zoneA = a->GetFacsimileInterface()->GetZone(); + Zone *zoneB = b->GetFacsimileInterface()->GetZone(); int aLowest, bLowest, aHighest, bHighest; diff --git a/include/vrv/facsimilefunctor.h b/include/vrv/facsimilefunctor.h index 7ccf8f3e772..1273343626c 100644 --- a/include/vrv/facsimilefunctor.h +++ b/include/vrv/facsimilefunctor.h @@ -42,7 +42,7 @@ class SyncFromFacsimileFunctor : public Functor { /* * Abstract base implementation */ - bool ImplementsEndInterface() const override { return true; } + bool ImplementsEndInterface() const override { return false; } /* * Functor interface @@ -51,7 +51,6 @@ class SyncFromFacsimileFunctor : public Functor { FunctorCode VisitLayerElement(LayerElement *layerElement) override; FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitPage(Page *page) override; - FunctorCode VisitPageEnd(Page *page) override; FunctorCode VisitPb(Pb *pb) override; FunctorCode VisitSb(Sb *sb) override; FunctorCode VisitStaff(Staff *staff) override; @@ -72,9 +71,6 @@ class SyncFromFacsimileFunctor : public Functor { // Page *m_currentPage; System *m_currentSystem; - Measure *m_currentNeumeLine; - /** map to store the zone corresponding to a staff */ - std::map m_staffZones; }; //---------------------------------------------------------------------------- diff --git a/include/vrv/facsimileinterface.h b/include/vrv/facsimileinterface.h index 84f46ddb9b8..7afafef144b 100644 --- a/include/vrv/facsimileinterface.h +++ b/include/vrv/facsimileinterface.h @@ -58,13 +58,6 @@ class FacsimileInterface : public Interface, public AttFacsimile { Zone *GetZone() { return m_zone; } const Zone *GetZone() const { return m_zone; } ///@} - /// - - /** Get the surface */ - ///@{ - Surface *GetSurface() { return m_surface; } - const Surface *GetSurface() const { return m_surface; } - ///@} //-----------------// // Pseudo functors // diff --git a/include/vrv/layerelement.h b/include/vrv/layerelement.h index ea8307a9da1..cf8a7af9e32 100644 --- a/include/vrv/layerelement.h +++ b/include/vrv/layerelement.h @@ -387,7 +387,6 @@ class LayerElement : public Object, public: /** Absolute position X. This is used for facsimile (transcription) encoding */ int m_drawingFacsX; - int m_drawingFacsY; // This is used only for accid, syl /** * This stores a pointer to the cross-staff (if any) and the appropriate layer * See PrepareCrossStaffFunctor diff --git a/include/vrv/measure.h b/include/vrv/measure.h index dcf96d15243..49ab432a0d7 100644 --- a/include/vrv/measure.h +++ b/include/vrv/measure.h @@ -53,7 +53,7 @@ class Measure : public Object, * Reset method resets all attribute classes */ ///@{ - Measure(MeasureType measuredMusic = MEASURED, int logMeasureNb = -1); + Measure(bool measuredMusic = true, int logMeasureNb = -1); virtual ~Measure(); Object *Clone() const override { return new Measure(*this); }; void Reset() override; @@ -79,12 +79,7 @@ class Measure : public Object, /** * Return true if measured music (otherwise we have fake measures) */ - bool IsMeasuredMusic() const { return (m_measureType == MEASURED); } - - /** - * Return true if the measure represents a neume (section) line - */ - bool IsNeumeLine() const { return (m_measureType == NEUMELINE); } + bool IsMeasuredMusic() const { return m_measuredMusic; } /** * Get and set the measure index @@ -409,10 +404,9 @@ class Measure : public Object, private: /** - * Indicate measured music (CMN), unmeasured (fake measures for mensural or neumes) or neume lines - * Neume line measure are created from
+ * Indicates measured music (otherwise we have fake measures) */ - MeasureType m_measureType; + bool m_measuredMusic; /** * The unique measure index diff --git a/include/vrv/staff.h b/include/vrv/staff.h index 92a699a43bf..cd68fe3eaa4 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -112,17 +112,6 @@ class Staff : public Object, } ///@} - /** - * @name Getters and setters for the rotation. - * Used only with facsimile rendering. - */ - ///@{ - void SetDrawingRotation(double drawingRotation) { m_drawingRotation = drawingRotation; } - double GetDrawingRotation() const { return m_drawingRotation; } - bool HasDrawingRotation() const { return (m_drawingRotation != 0.0); } - int GetDrawingRotationOffsetFor(int x); - ///@} - /** * Delete all the legder line arrays. */ @@ -301,12 +290,6 @@ class Staff : public Object, ArrayOfLedgerLines m_ledgerLinesAboveCue; ArrayOfLedgerLines m_ledgerLinesBelowCue; ///@} - - /** - * The drawing rotation. - * Used only with facsimile rendering - */ - double m_drawingRotation; }; } // namespace vrv diff --git a/include/vrv/view.h b/include/vrv/view.h index 485d1d9b1a9..2f59e4c6657 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -416,7 +416,6 @@ class View { ///@{ void DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); - void DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNeume(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); ///@} diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index bda87157121..526d3af9fac 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -661,14 +661,6 @@ enum SmuflTextFont { SMUFL_NONE = 0, SMUFL_FONT_SELECTED, SMUFL_FONT_FALLBACK }; enum GraphicID { PRIMARY = 0, SPANNING, SYMBOLREF }; -//---------------------------------------------------------------------------- -// Measure type -//---------------------------------------------------------------------------- - -enum MeasureType { MEASURED = 0, UNMEASURED, NEUMELINE }; - -#define NEUME_LINE_TYPE "neon-neume-line" - //---------------------------------------------------------------------------- // Legacy Wolfgang defines //---------------------------------------------------------------------------- diff --git a/src/adjustyrelfortranscriptionfunctor.cpp b/src/adjustyrelfortranscriptionfunctor.cpp deleted file mode 100644 index 8bcc92402d1..00000000000 --- a/src/adjustyrelfortranscriptionfunctor.cpp +++ /dev/null @@ -1,35 +0,0 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: adjustyrelfortranscriptionfunctor.cpp -// Author: Yinan Zhou -// Created: 2024 -// Copyright (c) Authors and others. All rights reserved. -///////////////////////////////////////////////////////////////////////////// - -#include "adjustyrelfortranscriptionfunctor.h" - -//---------------------------------------------------------------------------- - -//---------------------------------------------------------------------------- - -namespace vrv { - -//---------------------------------------------------------------------------- -// AdjustYRelForTranscriptionFunctor -//---------------------------------------------------------------------------- - -AdjustYRelForTranscriptionFunctor::AdjustYRelForTranscriptionFunctor() : Functor() {} - -FunctorCode AdjustYRelForTranscriptionFunctor::VisitLayerElement(LayerElement *layerElement) -{ - if (layerElement->m_drawingFacsY == VRV_UNSET) return FUNCTOR_CONTINUE; - - if (layerElement->IsScoreDefElement()) return FUNCTOR_SIBLINGS; - - if (!layerElement->HasSelfBB()) return FUNCTOR_CONTINUE; - - layerElement->SetDrawingYRel(-layerElement->GetSelfY1()); - - return FUNCTOR_CONTINUE; -} - -} // namespace vrv diff --git a/src/convertfunctor.cpp b/src/convertfunctor.cpp index d80c5db720f..61838252781 100644 --- a/src/convertfunctor.cpp +++ b/src/convertfunctor.cpp @@ -187,12 +187,9 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) bool nextIsBarline = (next && next->Is(BARLINE)); // See if we create proper measures and what to do with the barLine - MeasureType convertToMeasured = UNMEASURED; - if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { - convertToMeasured = MEASURED; - } + bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); - if (convertToMeasured == MEASURED) { + if (convertToMeasured) { // barLine object will be deleted m_targetMeasure->SetRight(barLine->GetForm()); } @@ -215,7 +212,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { m_targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured == MEASURED) { + if (convertToMeasured) { m_targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1 + m_segmentIdx)); } m_targetSubSystem->AddChild(m_targetMeasure); @@ -280,10 +277,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) return FUNCTOR_CONTINUE; } - MeasureType convertToMeasured = UNMEASURED; - if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { - convertToMeasured = MEASURED; - } + bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); assert(m_targetSystem); assert(m_layerTree); @@ -295,7 +289,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) // Create the first measure segment - problem: we are dropping the section element - we should create a score-based // MEI file instead Measure *targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured == MEASURED) { + if (convertToMeasured) { targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1)); } m_targetSubSystem->AddChild(targetMeasure); @@ -381,7 +375,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitSyllable(Syllable *syllable) // Make a segment break // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { - m_targetMeasure = new Measure(UNMEASURED); + m_targetMeasure = new Measure(false); m_targetSubSystem->AddChild(m_targetMeasure); // Add a staff with same attributes as in the previous segment m_targetStaff = new Staff(*m_targetStaff); diff --git a/src/devicecontext.cpp b/src/devicecontext.cpp index e734a35919d..09a868e56ab 100644 --- a/src/devicecontext.cpp +++ b/src/devicecontext.cpp @@ -129,11 +129,6 @@ const Resources *DeviceContext::GetResources(bool showWarning) const return m_resources; } -void DeviceContext::SetViewBoxFactor(double ppuFactor) -{ - m_viewBoxFactor = double(DEFINITION_FACTOR) / ppuFactor; -} - void DeviceContext::SetPen(int color, int width, int style, int dashLength, int gapLength, int lineCap, int lineJoin) { float opacityValue; diff --git a/src/doc.cpp b/src/doc.cpp index 4300e41b8d8..5b13dd476d7 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -132,7 +132,6 @@ void Doc::Reset() m_timemapTempo = 0.0; m_markup = MARKUP_DEFAULT; m_isMensuralMusicOnly = false; - m_isNeumeLines = false; m_isCastOff = false; m_visibleScores.clear(); @@ -1408,8 +1407,6 @@ void Doc::SyncToFacsimileDoc() if (!m_facsimile->FindDescendantByType(SURFACE)) { m_facsimile->AddChild(new Surface()); } - this->ScoreDefSetCurrentDoc(); - m_facsimile->SetType("transcription"); m_facsimile->ClearChildren(); @@ -2131,10 +2128,8 @@ int Doc::GetAdjustedDrawingPageHeight() const { assert(m_drawingPage); - // Take into account the PPU when getting the page height in facsimile if (this->IsTranscription() || this->IsFacs()) { - const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); - return m_drawingPage->m_pageHeight / factor; + return m_drawingPage->m_pageHeight / DEFINITION_FACTOR; } int contentHeight = m_drawingPage->GetContentHeight(); @@ -2145,10 +2140,8 @@ int Doc::GetAdjustedDrawingPageWidth() const { assert(m_drawingPage); - // Take into account the PPU when getting the page width in facsimile if (this->IsTranscription() || this->IsFacs()) { - const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); - return m_drawingPage->m_pageWidth / factor; + return m_drawingPage->m_pageWidth / DEFINITION_FACTOR; } int contentWidth = m_drawingPage->GetContentWidth(); diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 9e9f8287fa3..2b1d30f6dc8 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -25,21 +25,19 @@ #include "divline.h" #include "layer.h" #include "liquescent.h" -#include "measure.h" #include "nc.h" #include "neume.h" #include "page.h" #include "rend.h" -#include "sb.h" #include "score.h" #include "staff.h" #include "staffdef.h" #include "surface.h" #include "syl.h" #include "syllable.h" -#include "system.h" #include "text.h" #include "vrv.h" + //-------------------------------------------------------------------------------- namespace vrv { @@ -138,13 +136,6 @@ bool EditorToolkitNeume::ParseEditorAction(const std::string &json_editorAction) } LogWarning("Could not parse the set clef action"); } - else if (action == "setLiquescent") { - std::string elementId, curve; - if (this->ParseSetLiquescentAction(json.get("param"), &elementId, &curve)) { - return this->SetLiquescent(elementId, curve); - } - LogWarning("Could not parse the set liquescent action"); - } else if (action == "remove") { std::string elementId; if (this->ParseRemoveAction(json.get("param"), &elementId)) { @@ -676,12 +667,11 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) if (fi->GetZone() != NULL) zones.insert(fi->GetZone()); } for (auto it = zones.begin(); it != zones.end(); ++it) { + // Transform y to device context (*it)->ShiftByXY(x, -y); } - SortStaves(); - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + staff->GetParent()->StableSort(StaffSort()); return true; // Can't reorder by layer since staves contain layers } @@ -741,7 +731,6 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) } Layer *layer = vrv_cast(element->GetFirstAncestor(LAYER)); layer->ReorderByXPos(); // Reflect position order of elements internally (and in the resulting output file) - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", status); m_editInfo.import("message", message); return true; @@ -756,7 +745,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in m_editInfo.import("message", "Could not get drawing page."); return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -792,27 +781,26 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Zone *zone = new Zone(); if (elementType == "staff") { - Object *page = m_doc->GetDrawingPage(); - System *newSystem = new System(); - Sb *newSb = new Sb(); - Measure *newMeasure = new Measure(NEUMELINE); + Object *parent; Staff *newStaff; - Layer *newLayer = new Layer(); std::string columnValue; - // Use closest existing staff (if there is one) if (staff) { + parent = staff->GetParent(); + assert(parent); columnValue = staff->GetType(); - int n = page->GetChildCount(SYSTEM) + 1; + int n = parent->GetChildCount() + 1; newStaff = new Staff(n); newStaff->m_drawingStaffDef = staff->m_drawingStaffDef; newStaff->m_drawingNotationType = staff->m_drawingNotationType; newStaff->m_drawingLines = staff->m_drawingLines; } else { + parent = m_doc->GetDrawingPage()->FindDescendantByType(MEASURE); + assert(parent); newStaff = new Staff(1); newStaff->m_drawingStaffDef = vrv_cast( - m_doc->GetCorrespondingScore(page)->GetScoreDef()->FindDescendantByType(STAFFDEF)); + m_doc->GetCorrespondingScore(parent)->GetScoreDef()->FindDescendantByType(STAFFDEF)); newStaff->m_drawingNotationType = NOTATIONTYPE_neume; newStaff->m_drawingLines = 4; } @@ -826,18 +814,30 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in surface->AddChild(zone); newStaff->AttachZone(zone); if (columnValue.length()) newStaff->SetType(columnValue); - + Layer *newLayer = new Layer(); newStaff->AddChild(newLayer); - newMeasure->AddChild(newStaff); - newSystem->AddChild(newSb); - newSystem->AddChild(newMeasure); - newSystem->SetDrawingScoreDef(vrv_cast(m_doc->GetCorrespondingScore(page)->GetScoreDef())); - - page->InsertAfter(page->GetFirst(SCORE), newSystem); - SortStaves(); - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + if (staff) { + // Find index to insert new staff + ListOfObjects staves = parent->FindAllDescendantsByType(STAFF, false); + std::vector stavesVector(staves.begin(), staves.end()); + stavesVector.push_back(newStaff); + StaffSort staffSort; + std::stable_sort(stavesVector.begin(), stavesVector.end(), staffSort); + for (int i = 0; i < (int)staves.size(); ++i) { + if (stavesVector.at(i) == newStaff) { + parent->InsertChild(newStaff, i); + parent->Modify(); + + m_editInfo.import("uuid", newStaff->GetID()); + m_editInfo.import("status", status); + m_editInfo.import("message", message); + + return true; + } + } + } + parent->AddChild(newStaff); m_editInfo.import("uuid", newStaff->GetID()); m_editInfo.import("status", status); @@ -881,21 +881,20 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - const int offsetX = (int)(noteWidth / 2); // Set up facsimile - zone->SetUlx(ulx - offsetX); + zone->SetUlx(ulx); zone->SetUly(uly); - zone->SetLrx(ulx + offsetX); + zone->SetLrx(ulx + noteWidth); zone->SetLry(uly + noteHeight); // add syl bounding box if Facs - if (m_doc->HasFacsimile()) { + if (m_doc->GetType() == Facs) { FacsimileInterface *fi = vrv_cast(syl->GetFacsimileInterface()); assert(fi); sylZone = new Zone(); - int staffLry = staff->GetZone()->GetLry(); + int staffLry = staff->GetFacsimileInterface()->GetZone()->GetLry(); // width height and offset can be adjusted int bboxHeight = 175; @@ -906,7 +905,8 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in int offsetY = 0; if (theta) { double factor = 1.3; - offsetY = (int)((ulx - staff->GetZone()->GetUlx()) * tan(theta * M_PI / 180.0) / factor); + offsetY = (int)((ulx - staff->GetFacsimileInterface()->GetZone()->GetUlx()) * tan(theta * M_PI / 180.0) + / factor); } sylZone->SetUlx(ulx); @@ -947,6 +947,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in contour = it->second; } else if (it->first == "curve") { + Liquescent *liquescent = new Liquescent(); curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; if (it->second == "a") { curve = curvatureDirection_CURVE_a; @@ -956,7 +957,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in curve = curvatureDirection_CURVE_c; nc->SetCurve(curve); } - Liquescent *liquescent = new Liquescent(); nc->AddChild(liquescent); } } @@ -995,9 +995,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in // Apply offset due to rotate newUly += (newUlx - ulx) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); - newZone->SetUlx(newUlx - offsetX); + newZone->SetUlx(newUlx); newZone->SetUly(newUly); - newZone->SetLrx(newUlx + offsetX); + newZone->SetLrx(newUlx + noteWidth); newZone->SetLry(newUly + noteHeight); newNc->AttachZone(newZone); @@ -1027,21 +1027,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Clef *clef = new Clef(); data_CLEFSHAPE clefShape = CLEFSHAPE_NONE; - const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int offsetR, offsetL; - for (auto it = attributes.begin(); it != attributes.end(); ++it) { if (it->first == "shape") { if (it->second == "C") { clefShape = CLEFSHAPE_C; - offsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); - offsetL = offsetR; break; } else if (it->second == "F") { clefShape = CLEFSHAPE_F; - offsetR = 0; - offsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); break; } } @@ -1055,16 +1048,17 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } clef->SetShape(clefShape); - int yDiff = -staff->GetZone()->GetUly() + uly; + const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int yDiff = -staff->GetDrawingY() + uly; yDiff += ((ulx - staff->GetZone()->GetUlx())) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); // Subtract distance due to rotate. int clefLine = staff->m_drawingLines - round((double)yDiff / (double)staffSize); clef->SetLine(clefLine); Zone *zone = new Zone(); - zone->SetUlx(ulx - offsetR); + zone->SetUlx(ulx); zone->SetUly(uly); - zone->SetLrx(ulx + offsetL); + zone->SetLrx(ulx + staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); zone->SetLry(uly + staffSize / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); clef->AttachZone(zone); Surface *surface = dynamic_cast(facsimile->FindDescendantByType(SURFACE)); @@ -1110,14 +1104,13 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - const int offsetX = (int)(noteWidth / 4); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx + offsetX); + zone->SetUlx(ulx); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth + offsetX); + zone->SetLrx(ulx + noteWidth); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); if (!AdjustPitchFromPosition(custos)) { @@ -1164,14 +1157,13 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx + offsetX); + zone->SetUlx(ulx); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth + offsetX); + zone->SetLrx(ulx + noteWidth); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); @@ -1229,14 +1221,13 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx + offsetX); + zone->SetUlx(ulx); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth + offsetX); + zone->SetLrx(ulx + noteWidth); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); @@ -1250,9 +1241,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } layer->ReorderByXPos(); - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("status", status); m_editInfo.import("message", message); return true; @@ -1266,7 +1254,7 @@ bool EditorToolkitNeume::InsertToSyllable(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1416,7 +1404,7 @@ bool EditorToolkitNeume::MoveOutsideSyllable(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1616,7 +1604,7 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1694,8 +1682,6 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) zone->SetLry(uly + offsetY + height); } - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -1787,8 +1773,6 @@ bool EditorToolkitNeume::Merge(std::vector elementIds) m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - // TODO change zones for staff children return true; @@ -1825,7 +1809,10 @@ bool EditorToolkitNeume::Set(std::string elementId, std::string attrType, std::s success = true; else if (AttModule::SetVisual(element, attrType, attrValue)) success = true; - + if (success && m_doc->GetType() != Facs) { + m_doc->PrepareData(); + m_doc->GetDrawingPage()->LayOut(true); + } m_editInfo.import("status", success ? "OK" : "FAILURE"); m_editInfo.import("message", success ? "" : "Could not set attribute '" + attrType + "' to '" + attrValue + "'."); return success; @@ -1891,8 +1878,12 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) std::u32string str = U""; text->SetText(str); syl->AddChild(text); + syllable->AddChild(syl); - if (m_doc->HasFacsimile()) { + Text *textChild = new Text(); + textChild->SetText(wtext); + syl->AddChild(textChild); + if (m_doc->GetType() == Facs) { // Create a default bounding box Zone *zone = new Zone(); int ulx, uly, lrx, lry; @@ -1929,9 +1920,6 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) m_editInfo.import("message", "Element type '" + element->GetClassName() + "' is unsupported for SetText."); return false; } - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("status", success ? status : "FAILURE"); m_editInfo.import("message", success ? message : "SetText method failed."); return success; @@ -1989,98 +1977,15 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) pi->AdjustPitchByOffset(shift); } } - m_editInfo.import("status", "OK"); - m_editInfo.import("message", ""); - return true; -} - -bool EditorToolkitNeume::SetLiquescent(std::string elementId, std::string curve) -{ - if (!m_doc->GetDrawingPage()) { - LogError("Could not get the drawing page."); - m_editInfo.import("status", "FAILURE"); - m_editInfo.import("message", "Could not get the drawing page."); - return false; - } - - Nc *nc = vrv_cast(m_doc->GetDrawingPage()->FindDescendantByID(elementId)); - assert(nc); - bool hasLiquscent = nc->GetChildCount(); - - if (curve == "a") { - curvatureDirection_CURVE curve = curvatureDirection_CURVE_a; - nc->SetCurve(curve); - if (!hasLiquscent) { - Liquescent *liquescent = new Liquescent(); - nc->AddChild(liquescent); - } + if (success && m_doc->GetType() != Facs) { + m_doc->PrepareData(); + m_doc->GetDrawingPage()->LayOut(true); } - else if (curve == "c") { - curvatureDirection_CURVE curve = curvatureDirection_CURVE_c; - nc->SetCurve(curve); - if (!hasLiquscent) { - Liquescent *liquescent = new Liquescent(); - nc->AddChild(liquescent); - } - } - else { - // For unset curve - curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; - nc->SetCurve(curve); - if (hasLiquscent) { - Liquescent *liquescent = vrv_cast(nc->FindDescendantByType(LIQUESCENT)); - nc->DeleteChild(liquescent); - } - } - m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; } -bool EditorToolkitNeume::SortStaves() -{ - if (!m_doc->GetDrawingPage()) { - LogError("Could not get drawing page."); - m_editInfo.import("status", "FAILURE"); - m_editInfo.import("message", "Could not get drawing page."); - return false; - } - - Object *page = m_doc->GetDrawingPage(); - - page->StableSort(StaffSort()); - - Object *pb = page->FindDescendantByType(PB); - Object *milestoneEnd = page->FindDescendantByType(SYSTEM_MILESTONE_END); - Object *section = page->FindDescendantByType(SECTION); - assert(pb); - assert(milestoneEnd); - assert(section); - - Object *pbParent = pb->GetParent(); - Object *milestoneEndParent = milestoneEnd->GetParent(); - Object *sectionParent = section->GetParent(); - int pbIdx = pbParent->GetChildIndex(pb); - int milestoneEndIdx = milestoneEndParent->GetChildIndex(milestoneEnd); - int sectionIdx = sectionParent->GetChildIndex(section); - - pb = pbParent->DetachChild(pbIdx); - milestoneEnd = milestoneEndParent->DetachChild(milestoneEndIdx); - section = sectionParent->DetachChild(sectionIdx); - - Object *firstSystem = page->GetFirst(SYSTEM); - Object *lastSystem = page->GetLast(SYSTEM); - assert(firstSystem); - assert(lastSystem); - - firstSystem->InsertChild(section, 0); - firstSystem->InsertChild(pb, 1); - lastSystem->InsertChild(milestoneEnd, lastSystem->GetChildCount()); - - return true; -} - bool EditorToolkitNeume::Split(std::string elementId, int x) { if (!m_doc->GetDrawingPage()) { @@ -2169,9 +2074,6 @@ bool EditorToolkitNeume::Split(std::string elementId, int x) } } layer->ClearRelinquishedChildren(); - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); m_editInfo.import("uuid", splitStaff->GetID()); @@ -2206,7 +2108,7 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) linkedSyllable->AddChild(syl); // Create default bounding box if facs - if (m_doc->HasFacsimile()) { + if (m_doc->GetType() == Facs) { Zone *zone = new Zone(); zone->SetUlx( @@ -2225,8 +2127,6 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) FacsimileInterface *fi = syl->GetFacsimileInterface(); assert(fi); fi->AttachZone(zone); - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); } } } @@ -2378,7 +2278,7 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx m_editInfo.import("message", "Could not get the drawing page."); return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogWarning("Resizing is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Resizing is only available in facsimile mode."); @@ -2407,28 +2307,11 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx zone->SetUly(uly); zone->SetLrx(lrx); zone->SetLry(lry); - double orgRotate = staff->GetDrawingRotation(); if (!isnan(rotate)) { zone->SetRotate(rotate); } zone->Modify(); - SortStaves(); - - if (staff->HasDrawingRotation()) { - ListOfObjects accids = staff->FindAllDescendantsByType(ACCID); - for (auto it = accids.begin(); it != accids.end(); ++it) { - Accid *accid = dynamic_cast(*it); - FacsimileInterface *fi = accid->GetFacsimileInterface(); - Zone *accidZone = fi->GetZone(); - double rotationOffset = (accid->GetDrawingX() - staff->GetDrawingX()) * tan(rotate * M_PI / 180.0); - if (orgRotate) { - double orgOffset = (accid->GetDrawingX() - staff->GetDrawingX()) * tan(orgRotate * M_PI / 180.0); - rotationOffset -= orgOffset; - } - accidZone->SetUly(accidZone->GetUly() + int(rotationOffset)); - accidZone->SetLry(accidZone->GetLry() + int(rotationOffset)); - } - } + staff->GetParent()->StableSort(StaffSort()); } else if (obj->Is(SYL)) { Syl *syl = vrv_cast(obj); @@ -2470,9 +2353,6 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx m_editInfo.import("message", "Element of type '" + obj->GetClassName() + "' is unsupported."); return false; } - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -2685,7 +2565,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e // if there are no full parents we need to make a new one to attach everything to if (fullParents.empty()) { - LogError("empty"); if (elementClass == NC) { parent = new Neume(); } @@ -2708,7 +2587,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e parent->AddChild(syl); // add a default bounding box if you need to - if (m_doc->HasFacsimile()) { + if (m_doc->GetType() == Facs) { Zone *zone = new Zone(); zone->SetUlx(parent->GetFirst(NEUME)->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -2766,7 +2645,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e std::u32string fullString = U""; for (auto it = fullParents.begin(); it != fullParents.end(); ++it) { Syl *syl = dynamic_cast((*it)->FindDescendantByType(SYL)); - if (syl != NULL && m_doc->HasFacsimile()) { + if (syl != NULL && m_doc->GetType() == Facs) { Zone *zone = dynamic_cast(syl->GetFacsimileInterface()->GetZone()); if (fullSyl == NULL) { @@ -2800,7 +2679,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e fullText->SetText(fullString); parent->AddChild(fullSyl); - if (m_doc->HasFacsimile()) { + if (m_doc->GetType() == Facs) { Zone *zone = dynamic_cast(fullSyl->GetFacsimileInterface()->GetZone()); zone->SetUlx(ulx); zone->SetUly(uly); @@ -2812,8 +2691,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e } } - LogError("2"); - // change the pitch of any pitched elements whose clef may have changed assert(newClef); ListOfObjects pitchedChildren; @@ -2828,7 +2705,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e } } } - LogError("3"); // Delete any empty parents for (auto it = parents.begin(); it != parents.end(); ++it) { @@ -2842,10 +2718,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e + obj->GetChildCount(CLEF))) { Object *leftover; while ((leftover = obj->FindDescendantByType(SYL)) != NULL) { - Zone *zone = dynamic_cast(leftover->GetFacsimileInterface()->GetZone()); - if (zone != NULL) { - m_doc->GetFacsimile()->FindDescendantByType(SURFACE)->DeleteChild(zone); - } obj->DeleteChild(leftover); } while ((leftover = obj->FindDescendantByType(DIVLINE)) != NULL) { @@ -2866,8 +2738,6 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e secondParent->ReorderByXPos(); - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("uuid", parent->GetID()); m_editInfo.import("status", status); m_editInfo.import("message", message); @@ -2901,7 +2771,6 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector ListOfObjects syllables; // List of syllables used. groupType=neume only. jsonxx::Array uuidArray; - bool breakOnEnd = false; // Check if you can get drawing page if (!m_doc->GetDrawingPage()) { @@ -2976,7 +2845,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } } - while (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { + if (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { fparent = el->GetFirstAncestor(SYLLABLE); sparent = el->GetFirstAncestor(LAYER); if (fparent && sparent) { @@ -2986,15 +2855,10 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector fparent->ReorderByXPos(); uuidArray << (*it); it = elementIds.erase(it); - if (it == elementIds.end()) { - breakOnEnd = true; - break; - } + if (it == elementIds.end()) break; el = m_doc->GetDrawingPage()->FindDescendantByID(*it); } } - if (breakOnEnd) break; - if (elementIds.begin() == it || firstIsSyl) { // if the element is a syl we want it to stay attached to the first element // we'll still need to initialize all the parents, thus the bool @@ -3096,7 +2960,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector newParent->AddChild(syl); // Create default bounding box if facs - if (m_doc->HasFacsimile()) { + if (m_doc->GetType() == Facs) { Zone *zone = new Zone(); zone->SetUlx(el->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -3150,8 +3014,6 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); m_editInfo.import("uuid", uuidArray); @@ -3319,6 +3181,7 @@ bool EditorToolkitNeume::ChangeGroup(std::string elementId, std::string contour) } zone->SetUlx(newUlx); zone->SetUly(newUly); + ; zone->SetLrx(newLrx); zone->SetLry(newLry); @@ -3336,9 +3199,6 @@ bool EditorToolkitNeume::ChangeGroup(std::string elementId, std::string contour) initialLry = newLry; prevNc = newNc; } - - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - m_editInfo.import("uuid", el->GetID()); m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); @@ -3352,6 +3212,8 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) bool success2 = false; Facsimile *facsimile = m_doc->GetFacsimile(); assert(facsimile); + Surface *surface = vrv_cast(facsimile->FindDescendantByType(SURFACE)); + assert(surface); std::string firstNcId = elementIds[0]; std::string secondNcId = elementIds[1]; // Check if you can get drawing page @@ -3377,66 +3239,77 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) return false; } - bool isLigature = false; + bool isLigature; if (firstNc->HasAttribute("ligated", "true") && secondNc->HasAttribute("ligated", "true")) { isLigature = true; } else { - Set(firstNcId, "tilt", ""); - Set(secondNcId, "tilt", ""); - Set(firstNcId, "curve", ""); - Set(secondNcId, "curve", ""); + isLigature = false; + Set(firstNc->GetID(), "tilt", ""); + Set(secondNc->GetID(), "tilt", ""); + Set(firstNc->GetID(), "curve", ""); + Set(secondNc->GetID(), "curve", ""); } - Zone *firstNcZone = firstNc->GetZone(); - Zone *secondNcZone = secondNc->GetZone(); + Zone *zone = new Zone(); + // set ligature to false and update zone of second Nc + if (isLigature) { + if (AttModule::SetNeumes(firstNc, "ligated", "false")) success1 = true; - int ligUlx = firstNcZone->GetUlx(); - int ligUly = firstNcZone->GetUly(); - int ligLrx = firstNcZone->GetLrx(); - int ligLry = firstNcZone->GetLry(); + int ligUlx = firstNc->GetZone()->GetUlx(); + int ligUly = firstNc->GetZone()->GetUly(); + int ligLrx = firstNc->GetZone()->GetLrx(); + int ligLry = firstNc->GetZone()->GetLry(); - Staff *staff = dynamic_cast(firstNc->GetFirstAncestor(STAFF)); - assert(staff); - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + Staff *staff = dynamic_cast(firstNc->GetFirstAncestor(STAFF)); + assert(staff); - // set ligature to false and update zone of second Nc - if (isLigature) { - if (Set(firstNcId, "ligated", "false")) success1 = true; + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - secondNcZone->SetUlx(ligUlx + noteWidth); - secondNcZone->SetUly(ligUly + noteHeight); - secondNcZone->SetLrx(ligLrx + noteWidth); - secondNcZone->SetLry(ligLry + noteHeight); + zone->SetUlx(ligUlx + noteWidth); + zone->SetUly(ligUly + noteHeight); + zone->SetLrx(ligLrx + noteWidth); + zone->SetLry(ligLry + noteHeight); - if (Set(secondNcId, "ligated", "false")) success2 = true; + secondNc->AttachZone(zone); + + if (AttModule::SetNeumes(secondNc, "ligated", "false")) success2 = true; } // set ligature to true and update zones to be the same - else { - if (Set(firstNcId, "ligated", "true")) success1 = true; + else if (!isLigature) { + if (AttModule::SetNeumes(firstNc, "ligated", "true")) success1 = true; - secondNcZone->SetUlx(ligUlx); - secondNcZone->SetUly(ligUly + noteHeight); - secondNcZone->SetLrx(ligLrx); - secondNcZone->SetLry(ligLry + noteHeight); + zone->SetUlx(firstNc->GetZone()->GetUlx()); + zone->SetUly(firstNc->GetZone()->GetUly()); + zone->SetLrx(firstNc->GetZone()->GetLrx()); + zone->SetLry(firstNc->GetZone()->GetLry()); - if (Set(secondNcId, "ligated", "true")) success2 = true; - } + secondNc->AttachZone(zone); + if (AttModule::SetNeumes(secondNc, "ligated", "true")) success2 = true; + } + // else { + // LogError("isLigature is invalid!"); + // m_editInfo.import("status", "FAILURE"); + // m_editInfo.import("message", "isLigature value '" + isLigature + "' is invalid."); + // return false; + // } + if (success1 && success2 && m_doc->GetType() != Facs) { + m_doc->PrepareData(); + m_doc->GetDrawingPage()->LayOut(true); + } + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); if (!(success1 && success2)) { LogWarning("Unable to update ligature attribute"); m_editInfo.import("message", "Unable to update ligature attribute."); m_editInfo.import("status", "WARNING"); - return false; } - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - - m_editInfo.import("status", "OK"); - m_editInfo.import("message", ""); + surface->AddChild(zone); return success1 && success2; } @@ -3449,7 +3322,7 @@ bool EditorToolkitNeume::ChangeStaff(std::string elementId) return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogWarning("Staff re-association is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Staff re-association is only available in facsimile mode."); @@ -3654,7 +3527,7 @@ bool EditorToolkitNeume::ChangeStaffTo(std::string elementId, std::string staffI return false; } - if (!m_doc->HasFacsimile()) { + if (m_doc->GetType() != Facs) { LogWarning("Staff re-association is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Staff re-association is only available in facsimile mode."); @@ -3972,21 +3845,6 @@ bool EditorToolkitNeume::ParseSetClefAction(jsonxx::Object param, std::string *e return true; } -bool EditorToolkitNeume::ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *curve) -{ - if (!param.has("elementId")) { - LogWarning("Could not parse 'elementId'"); - return false; - } - *elementId = param.get("elementId"); - if (!param.has("curve")) { - LogWarning("Could not parse 'curve'"); - return false; - } - *curve = param.get("curve"); - return true; -} - bool EditorToolkitNeume::ParseRemoveAction(jsonxx::Object param, std::string *elementId) { if (!param.has("elementId")) return false; @@ -4166,12 +4024,15 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - const int pitchDifference = round( - (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) - - m_view->ToLogicalY(fi->GetZone()->GetUly())) - / staffSize - - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); - pi->AdjustPitchByOffset(-pitchDifference); + // Use the same pitchDifference equation for both syllables and custos + const int pitchDifference + = round((double)(staff->GetDrawingY() + (2 * staffSize * (staff->m_drawingLines - clef->GetLine())) + - fi->GetZone()->GetUly() + - ((fi->GetZone()->GetUlx() - staff->GetZone()->GetUlx()) + * tan(-staff->GetDrawingRotate() * M_PI / 180.0))) + / (double)(staffSize)); + + pi->AdjustPitchByOffset(pitchDifference); return true; } @@ -4213,8 +4074,6 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); for (auto it = pitchedChildren.begin(); it != pitchedChildren.end(); ++it) { - if ((*it)->Is(LIQUESCENT)) continue; - FacsimileInterface *fi = (*it)->GetFacsimileInterface(); if (fi == NULL || !fi->HasFacs()) { LogError("Could not adjust pitch: child %s does not have facsimile data", (*it)->GetID().c_str()); @@ -4233,12 +4092,15 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) } pi->SetOct(octave); - const int pitchDifference = round( - (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) - - m_view->ToLogicalY(fi->GetZone()->GetUly())) - / staffSize - - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); - pi->AdjustPitchByOffset(-pitchDifference); + // Use the same pitchDifference equation for both syllables and custos + const int pitchDifference + = round((double)(staff->GetDrawingY() + (2 * staffSize * (staff->m_drawingLines - clef->GetLine())) + - fi->GetZone()->GetUly() + - ((fi->GetZone()->GetUlx() - staff->GetZone()->GetUlx()) + * tan(-staff->GetDrawingRotate() * M_PI / 180.0))) + / (double)(staffSize)); + + pi->AdjustPitchByOffset(pitchDifference); } return true; diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index f70d6804d9c..73ec1ef23ca 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -12,7 +12,6 @@ #include "doc.h" #include "layerelement.h" #include "measure.h" -#include "miscfunctor.h" #include "page.h" #include "pb.h" #include "sb.h" @@ -40,80 +39,42 @@ SyncFromFacsimileFunctor::SyncFromFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ ACCID, CLEF, CUSTOS, DIVLINE, LIQUESCENT, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; Zone *zone = layerElement->GetZone(); assert(zone); layerElement->m_drawingFacsX = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); - if (layerElement->Is({ ACCID, SYL })) { - layerElement->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); - } return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitMeasure(Measure *measure) { - // neon specific code - measure have no zone, we will use the staff one in VisitStaff - if (measure->IsNeumeLine()) { - m_currentNeumeLine = measure; - } - else { - Zone *zone = measure->GetZone(); - assert(zone); - measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); - measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); - } + Zone *zone = measure->GetZone(); + assert(zone); + measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitPage(Page *page) { - m_staffZones.clear(); m_currentPage = page; m_doc->SetDrawingPage(m_currentPage->GetIdx()); return FUNCTOR_CONTINUE; } -FunctorCode SyncFromFacsimileFunctor::VisitPageEnd(Page *page) -{ - // Used for adjusting staff size in neon - filled in VisitStaff - if (m_staffZones.empty()) return FUNCTOR_CONTINUE; - - // The staff size is calculated based on the zone height and takes into acocunt the rotation - for (auto &[staff, zone] : m_staffZones) { - double rotate = (zone->HasRotate()) ? zone->GetRotate() : 0.0; - int yDiff - = zone->GetLry() - zone->GetUly() - (zone->GetLrx() - zone->GetUlx()) * tan(abs(rotate) * M_PI / 180.0); - staff->m_drawingStaffSize - = 100 * yDiff / (m_doc->GetOptions()->m_unit.GetValue() * 2 * (staff->m_drawingLines - 1)); - staff->SetDrawingRotation(rotate); - } - - // Since we multiply all values by the DEFINITION_FACTOR, set it as PPU - m_currentPage->SetPPUFactor(DEFINITION_FACTOR); - if (m_currentPage->GetPPUFactor() != 1.0) { - ApplyPPUFactorFunctor applyPPUFactor; - m_currentPage->Process(applyPPUFactor); - m_doc->UpdatePageDrawingSizes(); - } - - return FUNCTOR_CONTINUE; -} - FunctorCode SyncFromFacsimileFunctor::VisitPb(Pb *pb) { // This would happen if we run the functor on data not converted to page-based assert(m_currentPage); Zone *zone = pb->GetZone(); - Surface *surface = pb->GetSurface(); - if (!surface && zone && zone->GetParent()) - surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; - assert(zone || surface); - // Use the (parent) surface attributes if given + assert(zone && zone->GetParent()); + Surface *surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; + // Use the parent surface attributes if given if (surface && surface->HasLrx() && surface->HasLry()) { m_currentPage->m_pageHeight = surface->GetLry() * DEFINITION_FACTOR; m_currentPage->m_pageWidth = surface->GetLrx() * DEFINITION_FACTOR; @@ -148,27 +109,12 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) assert(zone); staff->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); - // neon specific code - set the position of the pseudo measure (neume line) - if (m_currentNeumeLine) { - m_currentNeumeLine->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); - m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); - m_staffZones[staff] = zone; - - // The staff slope is going up. The y left position needs to be adjusted accordingly - if (zone->GetRotate() < 0) { - staff->m_drawingFacsY = staff->m_drawingFacsY - + (m_currentNeumeLine->m_drawingFacsX2 - m_currentNeumeLine->m_drawingFacsX1) - * tan(zone->GetRotate() * M_PI / 180.0); - } - } - return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitSystem(System *system) { m_currentSystem = system; - m_currentNeumeLine = NULL; return FUNCTOR_CONTINUE; } @@ -190,13 +136,10 @@ SyncToFacsimileFunctor::SyncToFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncToFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ ACCID, CLEF, CUSTOS, DIVLINE, LIQUESCENT, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; Zone *zone = this->GetZone(layerElement, layerElement->GetClassName()); zone->SetUlx(m_view.ToDeviceContextX(layerElement->GetDrawingX()) / DEFINITION_FACTOR + m_pageMarginLeft); - if (layerElement->Is({ ACCID, SYL })) { - zone->SetUly(m_view.ToDeviceContextY(layerElement->GetDrawingY()) / DEFINITION_FACTOR + m_pageMarginTop); - } return FUNCTOR_CONTINUE; } diff --git a/src/iodarms.cpp b/src/iodarms.cpp index 0f31bb84223..debf3f3181a 100644 --- a/src/iodarms.cpp +++ b/src/iodarms.cpp @@ -473,7 +473,7 @@ bool DarmsInput::Import(const std::string &data_str) score->AddChild(section); m_staff = new Staff(1); - m_measure = new Measure(MEASURED, 1); + m_measure = new Measure(true, 1); m_layer = new Layer(); m_layer->SetN(1); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 5e7423a1e7b..4353e83816a 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -29294,7 +29294,7 @@ void HumdrumInput::setupSystemMeasure(int startline, int endline) } if (hasMensuralStaff(&infile[startline])) { - m_measure = new Measure(UNMEASURED); + m_measure = new Measure(false); } else { m_measure = new Measure(); diff --git a/src/iomei.cpp b/src/iomei.cpp index 5d1e8ae1200..aa139f99077 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -445,14 +445,7 @@ bool MEIOutput::WriteObjectInternal(Object *object, bool useCustomScoreDef) this->WriteSymbolTable(m_currentNode, vrv_cast(object)); } else if (object->Is(MEASURE)) { - Measure *measure = vrv_cast(object); - assert(measure); - std::string name = "measure"; - if (measure->IsNeumeLine()) { - name = "section"; - measure->SetType(NEUME_LINE_TYPE); - } - m_currentNode = m_currentNode.append_child(name.c_str()); + m_currentNode = m_currentNode.append_child("measure"); this->WriteMeasure(m_currentNode, vrv_cast(object)); } else if (object->Is(STAFF)) { @@ -1901,7 +1894,7 @@ void MEIOutput::WriteMeasure(pugi::xml_node currentNode, Measure *measure) measure->WritePointing(currentNode); measure->WriteTyped(currentNode); // For now we copy the adjusted value of coord.x1 and coord.x2 to xAbs and xAbs2 respectively - if ((measure->m_drawingFacsX1 != VRV_UNSET) && (measure->m_drawingFacsX2 != VRV_UNSET) && !m_doc->IsNeumeLines()) { + if ((measure->m_drawingFacsX1 != VRV_UNSET) && (measure->m_drawingFacsX2 != VRV_UNSET)) { measure->SetCoordX1(measure->m_drawingFacsX1 / DEFINITION_FACTOR); measure->SetCoordX2(measure->m_drawingFacsX2 / DEFINITION_FACTOR); measure->WriteCoordX1(currentNode); @@ -2233,7 +2226,7 @@ void MEIOutput::WriteStaff(pugi::xml_node currentNode, Staff *staff) staff->WriteVisibility(currentNode); // y position - if (staff->m_drawingFacsY != VRV_UNSET && !(m_doc->IsNeumeLines())) { + if (staff->m_drawingFacsY != VRV_UNSET) { staff->SetCoordY1(staff->m_drawingFacsY / DEFINITION_FACTOR); staff->WriteCoordY1(currentNode); } @@ -2313,7 +2306,7 @@ void MEIOutput::WriteLayerElement(pugi::xml_node currentNode, LayerElement *elem this->WriteLinkingInterface(currentNode, element); element->WriteLabelled(currentNode); element->WriteTyped(currentNode); - if (element->m_drawingFacsX != VRV_UNSET && !(m_doc->IsNeumeLines())) { + if (element->m_drawingFacsX != VRV_UNSET) { element->SetCoordX1(element->m_drawingFacsX / DEFINITION_FACTOR); element->WriteCoordX1(currentNode); } @@ -2710,7 +2703,6 @@ void MEIOutput::WriteNc(pugi::xml_node currentNode, Nc *nc) this->WritePitchInterface(currentNode, nc); this->WritePositionInterface(currentNode, nc); nc->WriteColor(currentNode); - nc->WriteCurvatureDirection(currentNode); nc->WriteIntervalMelodic(currentNode); nc->WriteNcForm(currentNode); } @@ -4402,13 +4394,7 @@ bool MEIInput::ReadScore(Object *parent, pugi::xml_node score) bool MEIInput::ReadSection(Object *parent, pugi::xml_node section) { Section *vrvSection = new Section(); - this->ReadSystemElement(section, vrvSection); - - if (vrvSection->GetType() == NEUME_LINE_TYPE) { - delete vrvSection; - m_doc->SetNeumeLines(true); - return ReadSectionChildren(parent, section); - } + this->SetMeiID(section, vrvSection); vrvSection->ReadNNumberLike(section); vrvSection->ReadSectionVis(section); @@ -4468,13 +4454,8 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) else if (std::string(current.name()) == "staff") { if (!unmeasured) { if (parent->Is(SECTION)) { - if (m_doc->IsNeumeLines()) { - unmeasured = new Measure(NEUMELINE); - } - else { - unmeasured = new Measure(UNMEASURED); - m_doc->SetMensuralMusicOnly(true); - } + unmeasured = new Measure(false); + m_doc->SetMensuralMusicOnly(true); parent->AddChild(unmeasured); } else { @@ -4502,15 +4483,9 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) } // New for blank files in neume notation - if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume) - && !parent->FindDescendantByType(MEASURE)) { - if (m_doc->IsNeumeLines()) { - unmeasured = new Measure(NEUMELINE); - } - else { - unmeasured = new Measure(UNMEASURED); - m_doc->SetMensuralMusicOnly(true); - } + if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume)) { + unmeasured = new Measure(false); + m_doc->SetMensuralMusicOnly(true); parent->AddChild(unmeasured); } return success; @@ -4653,7 +4628,7 @@ bool MEIInput::ReadSystemChildren(Object *parent, pugi::xml_node parentNode) if (parent->Is(SYSTEM)) { System *system = vrv_cast(parent); assert(system); - unmeasured = new Measure(UNMEASURED); + unmeasured = new Measure(false); m_doc->SetMensuralMusicOnly(true); if (m_doc->IsTranscription() && (m_meiversion == meiVersion_MEIVERSION_2013)) { UpgradeMeasureTo_3_0_0(unmeasured, system); @@ -6834,7 +6809,6 @@ bool MEIInput::ReadNc(Object *parent, pugi::xml_node nc) this->ReadPitchInterface(nc, vrvNc); this->ReadPositionInterface(nc, vrvNc); vrvNc->ReadColor(nc); - vrvNc->ReadCurvatureDirection(nc); vrvNc->ReadIntervalMelodic(nc); vrvNc->ReadNcForm(nc); @@ -8209,14 +8183,12 @@ void MEIInput::UpgradePageTo_5_0(Page *page) PageMilestoneEnd *scoreEnd = new PageMilestoneEnd(score); page->AddChild(scoreEnd); - score->SetEnd(scoreEnd); Mdiv *mdiv = new Mdiv(); page->InsertChild(mdiv, 0); PageMilestoneEnd *mdivEnd = new PageMilestoneEnd(mdiv); page->AddChild(mdivEnd); - mdiv->SetEnd(mdivEnd); } void MEIInput::UpgradePgHeadFootTo_5_0(pugi::xml_node element) diff --git a/src/iopae.cpp b/src/iopae.cpp index 005f1ce0d3d..1e844d31a06 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -2884,7 +2884,7 @@ bool PAEInput::Import(const std::string &input) } // Add a measure at the beginning of the data because there is always at least one measure - Measure *measure = new Measure(MEASURED, 1); + Measure *measure = new Measure(true, 1); // By default there is no end barline on an incipit measure->SetRight(BARRENDITION_invis); m_pae.push_back(pae::Token(0, pae::UNKOWN_POS, measure)); @@ -3403,7 +3403,7 @@ bool PAEInput::ConvertMeasure() // We can now create a new measure but not if we have reached the end of the data if (!token.IsEnd()) { measureCount++; - currentMeasure = new Measure(MEASURED, measureCount); + currentMeasure = new Measure(true, measureCount); currentMeasure->SetRight(BARRENDITION_invis); measureToken->m_object = currentMeasure; } diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 25dd5d9ad77..459d4c505df 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -129,7 +129,6 @@ void LayerElement::Reset() m_drawingFacsX = VRV_UNSET; m_drawingYRel = 0; - m_drawingFacsY = VRV_UNSET; m_drawingXRel = 0; m_drawingCueSize = false; @@ -397,6 +396,14 @@ void LayerElement::SetGraceAlignment(Alignment *graceAlignment) int LayerElement::GetDrawingX() const { + // If this element has a facsimile and we are in facsimile mode, use Facsimile::GetDrawingX + if (this->HasFacs()) { + const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); + assert(doc); + if (doc->IsFacs()) { + return FacsimileInterface::GetDrawingX(); + } + } // Since m_drawingFacsX is the left position, we adjust the XRel accordingly in AdjustXRelForTranscription if (m_drawingFacsX != VRV_UNSET) return m_drawingFacsX + this->GetDrawingXRel(); @@ -437,8 +444,14 @@ int LayerElement::GetDrawingX() const int LayerElement::GetDrawingY() const { - - if (m_drawingFacsY != VRV_UNSET) return m_drawingFacsY + this->GetDrawingYRel(); + // If this element has a facsimile and we are in facsimile mode, use Facsimile::GetDrawingY + if (this->HasFacs()) { + const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); + assert(doc); + if (doc->IsFacs()) { + return FacsimileInterface::GetDrawingY(); + } + } if (m_cachedDrawingY != VRV_UNSET) return m_cachedDrawingY; diff --git a/src/measure.cpp b/src/measure.cpp index d5e48b679f7..7b566400378 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -54,7 +54,7 @@ namespace vrv { static const ClassRegistrar s_factory("measure", MEASURE); -Measure::Measure(MeasureType measureMusic, int logMeasureNb) +Measure::Measure(bool measureMusic, int logMeasureNb) : Object(MEASURE, "measure-") , FacsimileInterface() , AttBarring() @@ -76,7 +76,7 @@ Measure::Measure(MeasureType measureMusic, int logMeasureNb) this->RegisterAttClass(ATT_TYPED); this->RegisterInterface(FacsimileInterface::GetAttClasses(), FacsimileInterface::IsInterface()); - m_measureType = measureMusic; + m_measuredMusic = measureMusic; // We set parent to it because we want to access the parent doc from the aligners m_measureAligner.SetParent(this); @@ -149,7 +149,7 @@ void Measure::Reset() m_rightBarLine.SetForm(this->GetRight()); m_leftBarLine.SetForm(this->GetLeft()); - if (!m_measureType) { + if (!m_measuredMusic) { m_drawingFacsX1 = VRV_UNSET; m_drawingFacsX2 = VRV_UNSET; } @@ -213,6 +213,14 @@ void Measure::AddChildBack(Object *child) int Measure::GetDrawingX() const { + if (!this->IsMeasuredMusic()) { + const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); + assert(system); + if (system->m_drawingFacsY != VRV_UNSET) { + return (system->m_systemLeftMar); + } + } + if (m_drawingFacsX1 != VRV_UNSET) return m_drawingFacsX1; if (m_cachedDrawingX != VRV_UNSET) return m_cachedDrawingX; @@ -345,6 +353,17 @@ int Measure::GetRightBarLineRight() const int Measure::GetWidth() const { + if (!this->IsMeasuredMusic()) { + const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); + assert(system); + if (system->m_drawingFacsY != VRV_UNSET) { + const Page *page = vrv_cast(system->GetFirstAncestor(PAGE)); + assert(page); + // xAbs2 = page->m_pageWidth - system->m_systemRightMar; + return page->m_pageWidth - system->m_systemLeftMar - system->m_systemRightMar; + } + } + if (m_drawingFacsX2 != VRV_UNSET) return (m_drawingFacsX2 - m_drawingFacsX1); assert(m_measureAligner.GetRightAlignment()); diff --git a/src/miscfunctor.cpp b/src/miscfunctor.cpp index f10936c906e..9c3677a2823 100644 --- a/src/miscfunctor.cpp +++ b/src/miscfunctor.cpp @@ -33,7 +33,6 @@ FunctorCode ApplyPPUFactorFunctor::VisitLayerElement(LayerElement *layerElement) if (layerElement->IsScoreDefElement()) return FUNCTOR_SIBLINGS; if (layerElement->m_drawingFacsX != VRV_UNSET) layerElement->m_drawingFacsX /= m_page->GetPPUFactor(); - if (layerElement->m_drawingFacsY != VRV_UNSET) layerElement->m_drawingFacsY /= m_page->GetPPUFactor(); return FUNCTOR_CONTINUE; } diff --git a/src/page.cpp b/src/page.cpp index 614b64180bc..5e3bb7ff276 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -33,7 +33,6 @@ #include "adjustxposfunctor.h" #include "adjustxrelfortranscriptionfunctor.h" #include "adjustyposfunctor.h" -#include "adjustyrelfortranscriptionfunctor.h" #include "alignfunctor.h" #include "bboxdevicecontext.h" #include "cachehorizontallayoutfunctor.h" @@ -271,8 +270,6 @@ void Page::LayOutTranscription(bool force) AdjustXRelForTranscriptionFunctor adjustXRelForTranscription; this->Process(adjustXRelForTranscription); - AdjustYRelForTranscriptionFunctor adjustYRelForTranscription; - this->Process(adjustYRelForTranscription); CalcLedgerLinesFunctor calcLedgerLines(doc); this->Process(calcLedgerLines); diff --git a/src/savefunctor.cpp b/src/savefunctor.cpp index 082b9d9259a..74172c5112e 100644 --- a/src/savefunctor.cpp +++ b/src/savefunctor.cpp @@ -96,12 +96,12 @@ FunctorCode SaveFunctor::VisitMdivEnd(Mdiv *mdiv) FunctorCode SaveFunctor::VisitMeasure(Measure *measure) { - return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMeasureEnd(Measure *measure) { - return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMNum(MNum *mNum) diff --git a/src/staff.cpp b/src/staff.cpp index fc45d680088..28b69219213 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -75,7 +75,6 @@ void Staff::Reset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; - m_drawingRotation = 0.0; ClearLedgerLines(); } @@ -91,13 +90,6 @@ void Staff::CloneReset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; - m_drawingRotation = 0.0; -} - -int Staff::GetDrawingRotationOffsetFor(int x) -{ - int xDiff = x - this->GetDrawingX(); - return int(xDiff * tan(this->GetDrawingRotation() * M_PI / 180.0)); } void Staff::ClearLedgerLines() @@ -142,6 +134,13 @@ int Staff::GetDrawingX() const int Staff::GetDrawingY() const { + if (this->HasFacs()) { + const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); + assert(DOC); + if (doc->IsFacs()) { + return FacsimileInterface::GetDrawingY(); + } + } if (m_drawingFacsY != VRV_UNSET) return m_drawingFacsY; @@ -173,7 +172,7 @@ void Staff::AdjustDrawingStaffSize() if (this->HasFacs()) { Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); assert(doc); - if (doc->IsFacs() || doc->IsNeumeLines()) { + if (doc->IsFacs()) { double rotate = this->GetDrawingRotate(); Zone *zone = this->GetZone(); assert(zone); diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 1cec345599f..1769f1f138d 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -496,8 +496,8 @@ void SvgDeviceContext::StartPage() = StringFormat("0 0 %d %d", this->GetWidth(), this->GetHeight()).c_str(); } else { - m_currentNode.append_attribute("viewBox") = StringFormat("0 0 %d %d", - int(this->GetWidth() * this->GetViewBoxFactor()), int(this->GetContentHeight() * this->GetViewBoxFactor())) + m_currentNode.append_attribute("viewBox") = StringFormat( + "0 0 %d %d", this->GetWidth() * DEFINITION_FACTOR, this->GetContentHeight() * DEFINITION_FACTOR) .c_str(); } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index d79ed929038..2ae7b97c19b 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1525,7 +1525,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) std::swap(height, width); } - double userScale = m_options->m_scale.GetValue() / 100.0; + double userScale = m_view.GetPPUFactor() * m_options->m_scale.GetValue() / 100; assert(userScale != 0.0); if (m_options->m_scaleToPageSize.GetValue()) { @@ -1537,7 +1537,6 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) deviceContext->SetUserScale(userScale, userScale); deviceContext->SetWidth(width); deviceContext->SetHeight(height); - deviceContext->SetViewBoxFactor(m_view.GetPPUFactor()); if (m_doc.IsFacs()) { deviceContext->SetWidth(m_doc.GetFacsimile()->GetMaxX()); diff --git a/src/view_element.cpp b/src/view_element.cpp index a2bba2eefea..3c698c7add7 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -113,7 +113,7 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay this->DrawCustos(dc, element, layer, staff, measure); } else if (element->Is(DIVLINE)) { - this->DrawDivLine(dc, element, layer, staff, measure); + DrawDivLine(dc, element, layer, staff, measure); } else if (element->Is(DOT)) { this->DrawDot(dc, element, layer, staff, measure); @@ -139,9 +139,6 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay else if (element->Is(LIGATURE)) { this->DrawLigature(dc, element, layer, staff, measure); } - else if (element->Is(LIQUESCENT)) { - this->DrawLiquescent(dc, element, layer, staff, measure); - } else if (element->Is(MENSUR)) { this->DrawMensur(dc, element, layer, staff, measure); } @@ -302,6 +299,19 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta y = (accid->GetPlace() == STAFFREL_below) ? y - extend.m_ascent - unit : y + extend.m_descent + unit; } + if (notationType == NOTATIONTYPE_neume) { + int rotateOffset = 0; + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = x - staff->GetDrawingX(); + rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + } + if (accid->HasFacs() && m_doc->IsFacs()) { + y = ToLogicalY(y); + } + y -= rotateOffset; + } + this->DrawSmuflString( dc, x, y, accidStr, HORIZONTALALIGNMENT_center, staff->m_drawingStaffSize, accid->GetDrawingCueSize(), true); @@ -643,8 +653,14 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf } int x, y; - y = staff->GetDrawingY(); - x = element->GetDrawingX(); + if (m_doc->IsFacs() && clef->HasFacs()) { + y = ToLogicalY(staff->GetDrawingY()); + x = clef->GetDrawingX(); + } + else { + y = staff->GetDrawingY(); + x = element->GetDrawingX(); + } char32_t sym = clef->GetClefGlyph(staff->m_drawingNotationType); @@ -655,8 +671,10 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (clef->HasLine()) { y -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - clef->GetLine()); - if (staff->HasDrawingRotation()) { - y -= staff->GetDrawingRotationOffsetFor(x); + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = x - staff->GetDrawingX(); + y -= int(xDiff * tan(deg * M_PI / 180.0)); } } else if (clef->GetShape() == CLEFSHAPE_perc) { @@ -678,6 +696,19 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); + if (m_doc->IsFacs() && element->HasFacs()) { + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + + FacsimileInterface *fi = element->GetFacsimileInterface(); + fi->GetZone()->SetUlx(x); + fi->GetZone()->SetUly(ToDeviceContextY(y)); + fi->GetZone()->SetLrx(x + noteWidth); + fi->GetZone()->SetLry(ToDeviceContextY(y - noteHeight)); + } + // Possibly draw enclosing brackets this->DrawClefEnclosing(dc, clef, staff, sym, x, y); @@ -729,18 +760,17 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St const int sym = custos->GetCustosGlyph(staff->m_drawingNotationType); int x, y; - // For neume notation we ignore the value set in CalcAlignmentPitchPosFunctor - if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { + if (custos->HasFacs() && m_doc->IsFacs()) { x = custos->GetDrawingX(); // Recalculate y from pitch to prevent visual/meaning mismatch Clef *clef = layer->GetClef(element); - y = staff->GetDrawingY(); + y = ToLogicalY(staff->GetDrawingY()); PitchInterface pi; // Neume notation uses C3 for C clef rather than C4. // Take this into account when determining location. // However this doesn't affect the value for F clef. pi.SetPname(PITCHNAME_c); - if (clef->GetShape() == CLEFSHAPE_C) { + if ((staff->m_drawingNotationType == NOTATIONTYPE_neume) && (clef->GetShape() == CLEFSHAPE_C)) { pi.SetOct(3); } else { @@ -759,15 +789,25 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); } - if (staff->HasDrawingRotation()) { - y -= staff->GetDrawingRotationOffsetFor(x); - } - else if (staff->HasDrawingRotation()) { - y -= staff->GetDrawingRotationOffsetFor(x); + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = x - staff->GetDrawingX(); + y -= int(xDiff * tan(deg * M_PI / 180.0)); } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); + if (m_doc->IsFacs() && element->HasFacs()) { + const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / 2); + const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / 1.4); + + FacsimileInterface *fi = element->GetFacsimileInterface(); + fi->GetZone()->SetUlx(x); + fi->GetZone()->SetUly(ToDeviceContextY(y)); + fi->GetZone()->SetLrx(x + noteWidth); + fi->GetZone()->SetLry(ToDeviceContextY(y - noteHeight)); + } + /************ Draw children (accidentals, etc) ************/ // Drawing the children should be done before ending the graphic. Otherwise the SVG tree will not match the MEI one this->DrawLayerChildren(dc, custos, layer, staff, measure); @@ -1728,14 +1768,14 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff Syl *syl = vrv_cast(element); assert(syl); - if (!syl->GetStart() && !(staff->m_drawingNotationType == NOTATIONTYPE_neume)) { + bool isNeume = (staff->m_drawingNotationType == NOTATIONTYPE_neume); + + if (!syl->GetStart() && !isNeume) { LogWarning("Parent note for was not found"); return; } - if (m_doc->IsFacs()) { - syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); - } + syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); dc->StartGraphic(syl, "", syl->GetID()); dc->DeactivateGraphicY(); @@ -1760,7 +1800,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff TextDrawingParams params; params.m_x = syl->GetDrawingX(); params.m_y = syl->GetDrawingY(); - if (m_doc->IsFacs() || m_doc->IsNeumeLines()) { + if (m_doc->IsFacs()) { params.m_width = syl->GetDrawingWidth(); params.m_height = syl->GetDrawingHeight(); } diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 51ea1373dff..971a8bf3304 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -56,105 +56,6 @@ void View::DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, dc->EndGraphic(element, this); } -void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) -{ - assert(dc); - assert(layer); - assert(staff); - assert(measure); - - Liquescent *liquescent = dynamic_cast(element); - assert(liquescent); - - struct drawingParams { - wchar_t fontNo = SMUFL_E990_chantPunctum; - wchar_t fontNoLiq[5] = {}; - float xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - float yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - }; - std::vector params; - params.push_back(drawingParams()); - - dc->StartGraphic(element, "", element->GetID()); - - Clef *clef = layer->GetClef(element); - int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int staffLineNumber = staff->m_drawingLines; - int clefLine = clef->GetLine(); - - Nc *nc = dynamic_cast(element->GetParent()); - assert(liquescent); - - if (nc->GetCurve() == curvatureDirection_CURVE_c) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; - params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; - params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = -1.5; - params.at(0).yOffsetLiq[4] = -1.75; - } - else if (nc->GetCurve() == curvatureDirection_CURVE_a) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; - params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; - params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = 0.5; - params.at(0).yOffsetLiq[4] = 0.75; - } - - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - int noteY, noteX; - int yValue; - if (nc->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); - noteX = nc->GetDrawingX(); - } - else { - noteX = element->GetDrawingX(); - noteY = element->GetDrawingY(); - } - - // Calculating proper y offset based on pname, clef, staff, and staff rotate - int clefYPosition = noteY - (staffSize * (staffLineNumber - clefLine)); - int pitchOffset = 0; - - // The default octave = 3, but the actual octave is calculated by - // taking into account the displacement of the clef - int clefOctave = 3; - if (clef->GetDis() && clef->GetDisPlace()) { - clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); - } - int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotateOffset = 0; - if (staff->HasDrawingRotation()) { - rotateOffset = staff->GetDrawingRotationOffsetFor(noteX); - } - - if (clef->GetShape() == CLEFSHAPE_C) { - pitchOffset = (nc->GetPname() - 1) * (staffSize / 2); - } - else if (clef->GetShape() == CLEFSHAPE_F) { - pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); - } - yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; - - for (auto it = params.begin(); it != params.end(); it++) { - for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { - DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, - it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); - } - } - - dc->EndGraphic(element, this); -} - void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) { assert(dc); @@ -173,8 +74,10 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff struct drawingParams { wchar_t fontNo = SMUFL_E990_chantPunctum; wchar_t fontNoLiq[5] = {}; - float xOffset = 0; - float yOffset = 0; + double xOffset = 0; + double yOffset = 0; + double xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + double yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; }; std::vector params; params.push_back(drawingParams()); @@ -200,6 +103,23 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (nc->GetLigated() == BOOLEAN_true) { int pitchDifference = 0; bool isFirst; + // Check if this is the first or second part of a ligature + // Object *nextSibling = neume->GetChild(position + 1); + // if (nextSibling != NULL) { + // Nc *nextNc = dynamic_cast(nextSibling); + // assert(nextNc); + // if (nextNc->GetLigated() == BOOLEAN_true) { // first part of the ligature + // isFirst = true; + // pitchDifference = nextNc->PitchDifferenceTo(nc); + // params.at(0).yOffset = pitchDifference; + // } + // else { + // isFirst = false; + // } + // } + // else { + // isFirst = false; + // } int ligCount = neume->GetLigatureCount(position); if (ligCount % 2 == 0) { @@ -221,6 +141,14 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } } + // if (!isFirst) { // still need to get pitchDifference + // Nc *lastnc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); + // assert(lastnc); + // pitchDifference = nc->PitchDifferenceTo(lastnc); + // params.at(0).xOffset = -1; + // params.at(0).yOffset = -pitchDifference; + // } + // set the glyph switch (pitchDifference) { case -1: @@ -249,19 +177,40 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; } + else if (nc->GetCurve() == curvatureDirection_CURVE_c) { + params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; + params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; + params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; + params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).xOffsetLiq[4] = 0.8; + params.at(0).yOffsetLiq[0] = -1.5; + params.at(0).yOffsetLiq[4] = -1.75; + } + else if (nc->GetCurve() == curvatureDirection_CURVE_a) { + params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; + params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; + params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; + params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; + params.at(0).xOffsetLiq[4] = 0.8; + params.at(0).yOffsetLiq[0] = 0.5; + params.at(0).yOffsetLiq[4] = 0.75; + } + const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); int noteY, noteX; int yValue; - if (nc->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); + if (nc->HasFacs() && m_doc->IsFacs()) { + noteY = ToLogicalY(staff->GetDrawingY()); noteX = nc->GetDrawingX(); params.at(0).xOffset = 0; } - else if (neume->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); + else if (neume->HasFacs() && m_doc->IsFacs()) { + noteY = ToLogicalY(staff->GetDrawingY()); noteX = neume->GetDrawingX() + position * noteWidth; } else { @@ -279,9 +228,14 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); } int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotationOffset = 0; - if (staff->HasDrawingRotation()) { - rotationOffset = staff->GetDrawingRotationOffsetFor(noteX); + int rotateOffset; + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = noteX - staff->GetDrawingX(); + rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); + } + else { + rotateOffset = 0; } if (nc->HasLoc()) { @@ -294,16 +248,31 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (clef->GetShape() == CLEFSHAPE_F) { pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); } - yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; + yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; } - if (!nc->HasCurve()) { - for (auto it = params.begin(); it != params.end(); it++) { + for (auto it = params.begin(); it != params.end(); it++) { + if (nc->GetCurve() == curvatureDirection_CURVE_a || nc->GetCurve() == curvatureDirection_CURVE_c) { + for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { + DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, + it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); + } + } + else { DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, yValue + it->yOffset * noteHeight, it->fontNo, staff->m_drawingStaffSize, false, true); } } + // adjust facsimile values of element based on where it is rendered if necessary + if (m_doc->IsFacs() && element->HasFacs()) { + FacsimileInterface *fi = element->GetFacsimileInterface(); + fi->GetZone()->SetUlx(noteX); + fi->GetZone()->SetUly(ToDeviceContextY(yValue)); + fi->GetZone()->SetLrx(noteX + noteWidth); + fi->GetZone()->SetLry(ToDeviceContextY(yValue - noteHeight)); + } + // Draw the children this->DrawLayerChildren(dc, nc, layer, staff, measure); @@ -393,6 +362,9 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S DivLine *divLine = dynamic_cast(element); assert(divLine); + // int x = divLine->GetDrawingX(); + // int y = divLine->GetDrawingY(); + dc->StartGraphic(element, "", element->GetID()); int sym = 0; @@ -408,14 +380,29 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S } int x, y; - x = divLine->GetDrawingX(); - y = staff->GetDrawingY(); + if (m_doc->IsFacs() && (divLine->HasFacs())) { + x = divLine->GetDrawingX(); + y = ToLogicalY(staff->GetDrawingY()); + } + else { + x = element->GetDrawingX(); + y = element->GetDrawingY(); + y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + } y -= (m_doc->GetDrawingUnit(staff->m_drawingStaffSize)) * 3; - if (staff->HasDrawingRotation()) { - y -= staff->GetDrawingRotationOffsetFor(x); + int rotateOffset; + if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { + double deg = staff->GetDrawingRotate(); + int xDiff = x - staff->GetDrawingX(); + rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); } + else { + rotateOffset = 0; + } + + y -= rotateOffset; DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); diff --git a/src/view_page.cpp b/src/view_page.cpp index d150f693ed4..51d8b78be5c 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1283,14 +1283,19 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys int j, x1, x2, y1, y2; - x1 = measure->GetDrawingX(); - x2 = x1 + measure->GetWidth(); - y1 = staff->GetDrawingY(); - if (!staff->HasDrawingRotation()) { - y2 = y1; + if (staff->HasFacs() && m_doc->IsFacs()) { + double d = staff->GetDrawingRotate(); + x1 = staff->GetDrawingX(); + x2 = x1 + staff->GetWidth(); + y1 = ToLogicalY(staff->GetDrawingY()); + staff->AdjustDrawingStaffSize(); + y2 = y1 - staff->GetWidth() * tan(d * M_PI / 180.0); } else { - y2 = y1 - measure->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); + x1 = measure->GetDrawingX(); + x2 = x1 + measure->GetWidth(); + y1 = staff->GetDrawingY(); + y2 = y1; } const int lineWidth = m_doc->GetDrawingStaffLineWidth(staff->m_drawingStaffSize); diff --git a/src/view_text.cpp b/src/view_text.cpp index f1f30653f9c..ff281247add 100644 --- a/src/view_text.cpp +++ b/src/view_text.cpp @@ -271,21 +271,21 @@ void View::DrawLyricString( std::u32string syl = U""; std::u32string lyricStr = str; - const int dcX = (params) ? ToDeviceContextX(params->m_x) : VRV_UNSET; - const int dcY = (params) ? ToDeviceContextY(params->m_y) : VRV_UNSET; + const int x = (params) ? params->m_x : VRV_UNSET; + const int y = (params) ? params->m_y : VRV_UNSET; const int width = (params) ? params->m_width : VRV_UNSET; const int height = (params) ? params->m_height : VRV_UNSET; if (m_doc->GetOptions()->m_lyricElision.GetValue() == ELISION_unicode) { std::replace(lyricStr.begin(), lyricStr.end(), U'_', UNICODE_UNDERTIE); - dc->DrawText(UTF32to8(lyricStr), lyricStr, dcX, dcY, width, height); + dc->DrawText(UTF32to8(lyricStr), lyricStr, x, y, width, height); } else { while (lyricStr.compare(syl) != 0) { wroteText = true; auto index = lyricStr.find_first_of(U"_"); syl = lyricStr.substr(0, index); - dc->DrawText(UTF32to8(syl), syl, dcX, dcY, width, height); + dc->DrawText(UTF32to8(syl), syl, x, y, width, height); // no _ if (index == std::string::npos) break; @@ -298,7 +298,7 @@ void View::DrawLyricString( bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(elision); vrvTxt.SetSmuflWithFallback(isFallbackNeeded); dc->SetFont(&vrvTxt); - dc->DrawText(UTF32to8(elision), elision, dcX, dcY, width, height); + dc->DrawText(UTF32to8(elision), elision, x, y, width, height); dc->ResetFont(); // next syllable @@ -310,8 +310,7 @@ void View::DrawLyricString( // This should only be called in facsimile mode where a zone is specified but there is // no text. This draws the bounds of the zone but leaves the space blank. if (!wroteText && params) { - dc->DrawText( - "", U"", ToDeviceContextX(params->m_x), ToDeviceContextY(params->m_y), params->m_width, params->m_height); + dc->DrawText("", U"", params->m_x, params->m_y, params->m_width, params->m_height); } } From e747a319cf5a4d5f9d8ee9d02fac27c7d6d57107 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 15 May 2024 16:22:26 +0200 Subject: [PATCH 150/383] Always initialise ligatures (avoid crash in cmn) --- src/page.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/page.cpp b/src/page.cpp index 5e3bb7ff276..11264de6c5a 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -333,10 +333,8 @@ void Page::ResetAligners() CalcAlignmentPitchPosFunctor calcAlignmentPitchPos(doc); this->Process(calcAlignmentPitchPos); - if (IsMensuralType(doc->m_notationType)) { - CalcLigatureNotePosFunctor calcLigatureNotePos(doc); - this->Process(calcLigatureNotePos); - } + CalcLigatureNotePosFunctor calcLigatureNotePos(doc); + this->Process(calcLigatureNotePos); CalcStemFunctor calcStem(doc); this->Process(calcStem); From dc4b31ece64927e3c852dc733cfaca5d19de53c0 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 15 May 2024 16:23:14 +0200 Subject: [PATCH 151/383] Support `@dots` with mensural duration (mei-all) --- src/calcdotsfunctor.cpp | 4 ---- src/preparedatafunctor.cpp | 22 +++++++++++----------- src/view_element.cpp | 3 +++ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/calcdotsfunctor.cpp b/src/calcdotsfunctor.cpp index b979a50aa66..2be321aa7a9 100644 --- a/src/calcdotsfunctor.cpp +++ b/src/calcdotsfunctor.cpp @@ -60,10 +60,6 @@ FunctorCode CalcDotsFunctor::VisitChord(Chord *chord) FunctorCode CalcDotsFunctor::VisitNote(Note *note) { - // We currently have no dots object with mensural notes - if (note->IsMensuralDur()) { - return FUNCTOR_SIBLINGS; - } if (!note->IsVisible()) { return FUNCTOR_SIBLINGS; } diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index 2d9226da362..fd69f35d01c 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -1161,17 +1161,6 @@ FunctorCode PrepareLayerElementPartsFunctor::VisitNote(Note *note) } } - // We don't care about flags or dots in mensural notes - if (note->IsMensuralDur()) return FUNCTOR_CONTINUE; - - if (currentStem) { - const bool shouldHaveFlag = ((note->GetActualDur() > DUR_4) && !note->IsInBeam() && !note->GetAncestorFTrem() - && !note->IsChordTone() && !note->IsTabGrpNote()); - currentFlag = this->ProcessFlag(currentFlag, currentStem, shouldHaveFlag); - - if (!chord) note->SetDrawingStem(currentStem); - } - /************ dots ***********/ Dots *currentDots = vrv_cast(note->FindDescendantByType(DOTS, 1)); @@ -1182,6 +1171,17 @@ FunctorCode PrepareLayerElementPartsFunctor::VisitNote(Note *note) } currentDots = this->ProcessDots(currentDots, note, shouldHaveDots); + // We don't care about flags in mensural notes + if (note->IsMensuralDur()) return FUNCTOR_CONTINUE; + + if (currentStem) { + const bool shouldHaveFlag = ((note->GetActualDur() > DUR_4) && !note->IsInBeam() && !note->GetAncestorFTrem() + && !note->IsChordTone() && !note->IsTabGrpNote()); + currentFlag = this->ProcessFlag(currentFlag, currentStem, shouldHaveFlag); + + if (!chord) note->SetDrawingStem(currentStem); + } + /************ Prepare the drawing cue size ************/ PrepareCueSizeFunctor prepareCueSize; diff --git a/src/view_element.cpp b/src/view_element.cpp index 3c698c7add7..93735da393d 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1460,6 +1460,9 @@ void View::DrawNote(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (note->IsMensuralDur()) { this->DrawMensuralNote(dc, element, layer, staff, measure); + if (note->FindDescendantByType(DOTS)) { + this->DrawLayerChildren(dc, note, layer, staff, measure); + } return; } if (note->IsTabGrpNote()) { From a5d6bbfae1b53af26a28a65f4b3da585f27ffa15 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 14:42:14 +0200 Subject: [PATCH 152/383] Fix broken lyric position and barline default --- src/measure.cpp | 8 +++----- src/view_element.cpp | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/measure.cpp b/src/measure.cpp index d5e48b679f7..0c02901da1b 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -95,7 +95,7 @@ Measure::Measure(MeasureType measureMusic, int logMeasureNb) this->Reset(); - if (!measureMusic) this->SetRight(BARRENDITION_invis); + if (!this->IsMeasuredMusic()) this->SetRight(BARRENDITION_invis); } Measure::~Measure() @@ -149,10 +149,8 @@ void Measure::Reset() m_rightBarLine.SetForm(this->GetRight()); m_leftBarLine.SetForm(this->GetLeft()); - if (!m_measureType) { - m_drawingFacsX1 = VRV_UNSET; - m_drawingFacsX2 = VRV_UNSET; - } + m_drawingFacsX1 = VRV_UNSET; + m_drawingFacsX2 = VRV_UNSET; m_drawingEnding = NULL; m_hasAlignmentRefWithMultipleLayers = false; diff --git a/src/view_element.cpp b/src/view_element.cpp index a2bba2eefea..0abc4eb7b12 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1733,7 +1733,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff return; } - if (m_doc->IsFacs()) { + if (!m_doc->IsFacs()) { syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); } From d4d4359cd4b61ab8f3e1f026f195f7bea5a7b403 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 14:43:16 +0200 Subject: [PATCH 153/383] Adjust xcode deployment target --- Verovio.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 06ae5b7bc23..9c99a0a38a6 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -5172,7 +5172,7 @@ "$(inherited)", NO_HUMDRUM_SUPPORT, ); - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5184,7 +5184,7 @@ CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; GCC_PREPROCESSOR_DEFINITIONS = NO_HUMDRUM_SUPPORT; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; From 3e7deb0365f3009eccab5eba19e771fcca61b9e5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 15:06:53 +0200 Subject: [PATCH 154/383] update From 83716c04c764ca0397fe7dfd905e9c49b2b9b340 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 15:36:54 +0200 Subject: [PATCH 155/383] update From 35ff94091fbd0459e530a60909068ecd7a502dbf Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 15:53:27 +0200 Subject: [PATCH 156/383] Update ci_build.yml * Change ubuntu to 22.04 for g++11 --- .github/workflows/ci_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index ca515fa61ad..1d75b6b86ff 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -78,7 +78,7 @@ jobs: compiler: g++ version: "10" - - os: ubuntu-20.04 + - os: ubuntu-22.04 compiler: g++ version: "11" From f623004d5dbfceb69edb2cc693704f69a9152a20 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 16 May 2024 10:24:00 -0400 Subject: [PATCH 157/383] Clean up EditorToolkitNeume::Remove() --- src/editortoolkit_neume.cpp | 59 +++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 5509dbbdc26..e7f95b5b82f 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2238,22 +2238,21 @@ bool EditorToolkitNeume::Remove(std::string elementId) m_editInfo.import("message", "Could not get the drawing page."); return false; } - Object *obj = m_doc->GetDrawingPage()->FindDescendantByID(elementId); - assert(obj); - bool result = false; - bool isNeumeOrNc, isNc, isClef, isSyllable; - isNeumeOrNc = (obj->Is(NC) || obj->Is(NEUME)); - isNc = obj->Is(NC); - isClef = obj->Is(CLEF); - isSyllable = obj->Is(SYLLABLE); - Object *parent = obj->GetParent(); + + Object *element = m_doc->GetDrawingPage()->FindDescendantByID(elementId); + assert(element); + Object *parent = element->GetParent(); assert(parent); - m_editInfo.import("uuid", elementId); + + bool result = false; + bool isNc = element->Is(NC); + bool isNeumeOrNc = (element->Is(NEUME) || element->Is(NC)); + // Remove Zone for element (if any) InterfaceComparison ic(INTERFACE_FACSIMILE); ListOfObjects fiChildren; - obj->FindAllDescendantsByComparison(&fiChildren, &ic); - FacsimileInterface *fi = obj->GetFacsimileInterface(); + element->FindAllDescendantsByComparison(&fiChildren, &ic); + FacsimileInterface *fi = element->GetFacsimileInterface(); if (fi != NULL && fi->HasFacs()) { fi->AttachZone(NULL); } @@ -2263,7 +2262,8 @@ bool EditorToolkitNeume::Remove(std::string elementId) fi->AttachZone(NULL); } } - if (isClef) { + + if (element->Is(CLEF)) { // y position of pitched elements (like neumes) is determined by their pitches // so when deleting a clef, the position on a page that a pitch value is associated with could change // so we need to change the pitch value of any elements whose clef is going to change @@ -2285,7 +2285,7 @@ bool EditorToolkitNeume::Remove(std::string elementId) m_doc->GetDrawingPage()->FindAllDescendantsBetween( &elements, &ic, clef, (nextClef != NULL) ? nextClef : m_doc->GetDrawingPage()->GetLast()); - result = parent->DeleteChild(obj); + result = parent->DeleteChild(element); if (!result) { LogError("Failed to delete the desired element (%s)", elementId.c_str()); @@ -2301,9 +2301,14 @@ bool EditorToolkitNeume::Remove(std::string elementId) // removing the current clef, and so the new clef for all of these elements is previousClef pi->AdjustPitchForNewClef(clef, previousClef); } + + m_editInfo.import("uuid", elementId); + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); + return true; } - else if (isSyllable) { - Syllable *syllable = dynamic_cast(obj); + else if (element->Is(SYLLABLE)) { + Syllable *syllable = dynamic_cast(element); assert(syllable); if (syllable->HasPrecedes() || syllable->HasFollows()) { UnlinkSyllable(syllable); @@ -2311,7 +2316,7 @@ bool EditorToolkitNeume::Remove(std::string elementId) } if (!result) { - result = parent->DeleteChild(obj); + result = parent->DeleteChild(element); } if (!result) { @@ -2321,15 +2326,16 @@ bool EditorToolkitNeume::Remove(std::string elementId) m_editInfo.import("message", "Failed to delete the desired element (" + elementId + ")."); return false; } + // Check if this leaves any containers empty and delete them if (isNc) { assert(parent->Is(NEUME)); - obj = parent; + element = parent; parent = parent->GetParent(); - if (obj->FindDescendantByType(NC) == NULL) { + if (element->FindDescendantByType(NC) == NULL) { // Delete the empty neume - std::string neumeId = obj->GetID(); - result &= parent->DeleteChild(obj); + std::string neumeId = element->GetID(); + result &= parent->DeleteChild(element); if (!result) { LogError("Failed to delete empty neume (%s)", neumeId.c_str()); m_editInfo.reset(); @@ -2341,18 +2347,18 @@ bool EditorToolkitNeume::Remove(std::string elementId) } if (isNeumeOrNc) { assert(parent->Is(SYLLABLE)); - obj = parent; + element = parent; parent = parent->GetParent(); - if (obj->FindDescendantByType(NC) == NULL) { + if (element->FindDescendantByType(NC) == NULL) { // Check if it is part of a linked/split syllable and unlink - Syllable *li = dynamic_cast(obj); + Syllable *li = dynamic_cast(element); assert(li); if (li->HasPrecedes() || li->HasFollows()) { UnlinkSyllable(li); } // Delete the syllable empty of neumes - std::string syllableId = obj->GetID(); - result &= parent->DeleteChild(obj); + std::string syllableId = element->GetID(); + result &= parent->DeleteChild(element); if (!result) { LogError("Failed to delete empty syllable (%s)", syllableId.c_str()); m_editInfo.reset(); @@ -2363,6 +2369,7 @@ bool EditorToolkitNeume::Remove(std::string elementId) } } + m_editInfo.import("uuid", elementId); m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; From 67577116da9607cd0caaa901438b6bc5750a3478 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 16 May 2024 12:21:41 -0400 Subject: [PATCH 158/383] Fix staff removing for neume lines Neon issue https://github.com/DDMAL/Neon/issues/1211 --- src/editortoolkit_neume.cpp | 55 ++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index e7f95b5b82f..87e6462f996 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2307,7 +2307,60 @@ bool EditorToolkitNeume::Remove(std::string elementId) m_editInfo.import("message", ""); return true; } - else if (element->Is(SYLLABLE)) { + else if (element->Is(STAFF)) { + Object *page = m_doc->GetDrawingPage(); + Object *system = element->GetFirstAncestor(SYSTEM); + + if (page->GetChildCount(SYSTEM) > 1) { + if (system == page->GetFirst(SYSTEM)) { + // if the target staff is in the first system, + // move pb and section to the next system + Object *nextSystem = page->GetNext(system, SYSTEM); + Object *section = system->FindDescendantByType(SECTION); + Object *pb = system->FindDescendantByType(PB); + assert(pb); + assert(section); + + int sectionIdx = system->GetChildIndex(section); + int pbIdx = system->GetChildIndex(pb); + section = system->DetachChild(sectionIdx); + pb = system->DetachChild(pbIdx); + + nextSystem->InsertChild(section, 0); + nextSystem->InsertChild(pb, 1); + } + else if (system == page->GetLast(SYSTEM)) { + // if the target staff in is the last system, + // move system-milestone-end to the previous system + Object *previousSystem = page->GetPrevious(system, SYSTEM); + Object *milestoneEnd = system->FindDescendantByType(SYSTEM_MILESTONE_END); + assert(milestoneEnd); + + int milestoneEndIdx = system->GetChildIndex(milestoneEnd); + milestoneEnd = system->DetachChild(milestoneEndIdx); + + previousSystem->InsertChild(milestoneEnd, previousSystem->GetChildCount()); + } + } + + // delete system to delete staff + result = page->DeleteChild(system); + + if (!result) { + LogError("Failed to delete the desired element (%s)", elementId.c_str()); + m_editInfo.reset(); + m_editInfo.import("status", "FAILURE"); + m_editInfo.import("message", "Failed to delete the desired element (" + elementId + ")."); + return false; + } + + m_editInfo.import("uuid", elementId); + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); + return true; + } + + if (element->Is(SYLLABLE)) { Syllable *syllable = dynamic_cast(element); assert(syllable); if (syllable->HasPrecedes() || syllable->HasFollows()) { From 2823f7ba9ebc5f02c3a49799bd59b01116bc6955 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 16 May 2024 12:22:39 -0400 Subject: [PATCH 159/383] Sort staves only if has multiple staves --- src/editortoolkit_neume.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 87e6462f996..1882808daaf 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2046,6 +2046,7 @@ bool EditorToolkitNeume::SortStaves() } Object *page = m_doc->GetDrawingPage(); + if (page->GetChildCount(SYSTEM) <= 1) return true; page->StableSort(StaffSort()); From 1ea5a527af6336a98b6aff115dda994c2061d47f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 16 May 2024 20:30:19 +0200 Subject: [PATCH 160/383] Revert "Merge branch 'develop-facsimile-neume-line' into develop-facsimile-neume-line" This reverts commit f16f6706d42481bf60fcb2abb54c2adff1578ddb, reversing changes made to 2823f7ba9ebc5f02c3a49799bd59b01116bc6955. --- .github/workflows/ci_build.yml | 2 +- Verovio.xcodeproj/project.pbxproj | 4 +- .../vrv/adjustyrelfortranscriptionfunctor.h | 56 +++ include/vrv/devicecontext.h | 7 + include/vrv/doc.h | 14 + include/vrv/editortoolkit_neume.h | 25 +- include/vrv/facsimilefunctor.h | 6 +- include/vrv/facsimileinterface.h | 7 + include/vrv/layerelement.h | 1 + include/vrv/measure.h | 14 +- include/vrv/staff.h | 17 + include/vrv/view.h | 1 + include/vrv/vrvdef.h | 8 + src/adjustyrelfortranscriptionfunctor.cpp | 35 ++ src/calcdotsfunctor.cpp | 4 + src/convertfunctor.cpp | 18 +- src/devicecontext.cpp | 5 + src/doc.cpp | 11 +- src/editortoolkit_neume.cpp | 391 +++++++++++------- src/facsimilefunctor.cpp | 75 +++- src/iodarms.cpp | 2 +- src/iohumdrum.cpp | 2 +- src/iomei.cpp | 50 ++- src/iomusxml.cpp | 3 - src/iopae.cpp | 4 +- src/layerelement.cpp | 19 +- src/measure.cpp | 31 +- src/miscfunctor.cpp | 1 + src/page.cpp | 9 +- src/preparedatafunctor.cpp | 22 +- src/savefunctor.cpp | 4 +- src/staff.cpp | 17 +- src/svgdevicecontext.cpp | 4 +- src/toolkit.cpp | 3 +- src/view_element.cpp | 85 +--- src/view_neume.cpp | 118 +----- src/view_page.cpp | 17 +- src/view_text.cpp | 13 +- 38 files changed, 657 insertions(+), 448 deletions(-) create mode 100644 include/vrv/adjustyrelfortranscriptionfunctor.h create mode 100644 src/adjustyrelfortranscriptionfunctor.cpp diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 1d75b6b86ff..ca515fa61ad 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -78,7 +78,7 @@ jobs: compiler: g++ version: "10" - - os: ubuntu-22.04 + - os: ubuntu-20.04 compiler: g++ version: "11" diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 9c99a0a38a6..06ae5b7bc23 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -5172,7 +5172,7 @@ "$(inherited)", NO_HUMDRUM_SUPPORT, ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5184,7 +5184,7 @@ CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; GCC_PREPROCESSOR_DEFINITIONS = NO_HUMDRUM_SUPPORT; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; diff --git a/include/vrv/adjustyrelfortranscriptionfunctor.h b/include/vrv/adjustyrelfortranscriptionfunctor.h new file mode 100644 index 00000000000..af26cbda9e2 --- /dev/null +++ b/include/vrv/adjustyrelfortranscriptionfunctor.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: adjustyrelfortranscriptionfunctor.h +// Author: Yinan Zhou +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ +#define __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ + +#include "functor.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// AdjustYRelForTranscriptionFunctor +//---------------------------------------------------------------------------- + +/** + * This class adjusts the YRel positions taking into account the bounding boxes. + */ +class AdjustYRelForTranscriptionFunctor : public Functor { +public: + /** + * @name Constructors, destructors + */ + ///@{ + AdjustYRelForTranscriptionFunctor(); + virtual ~AdjustYRelForTranscriptionFunctor() = default; + ///@} + + /* + * Abstract base implementation + */ + bool ImplementsEndInterface() const override { return false; } + + /* + * Functor interface + */ + ///@{ + FunctorCode VisitLayerElement(LayerElement *layerElement) override; + ///@} + +protected: + // +private: + // +public: + // +private: + // +}; + +} // namespace vrv + +#endif // __VRV_ADJUSTYRELFORTRANSCRIPTIONFUNCTOR_H__ diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index b319c835a1a..95c62a81208 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -74,6 +74,7 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; + m_viewBoxFactor = (double)DEFINITION_FACTOR; } DeviceContext(ClassId classId) { @@ -89,6 +90,7 @@ class DeviceContext { m_baseWidth = 0; m_baseHeight = 0; m_pushBack = false; + m_viewBoxFactor = (double)DEFINITION_FACTOR; } virtual ~DeviceContext(){}; ClassId GetClassId() const { return m_classId; } @@ -124,12 +126,14 @@ class DeviceContext { m_baseWidth = width; m_baseHeight = height; } + void SetViewBoxFactor(double ppuFactor); int GetWidth() const { return m_width; } int GetHeight() const { return m_height; } int GetContentHeight() const { return m_contentHeight; } double GetUserScaleX() { return m_userScaleX; } double GetUserScaleY() { return m_userScaleY; } std::pair GetBaseSize() const { return std::make_pair(m_baseWidth, m_baseHeight); } + double GetViewBoxFactor() const { return m_viewBoxFactor; } ///@} /** @@ -365,6 +369,9 @@ class DeviceContext { /** stores the scale as requested by the used */ double m_userScaleX; double m_userScaleY; + + /** stores the viewbox factor taking into account the DEFINTION_FACTOR and the PPU */ + double m_viewBoxFactor; }; } // namespace vrv diff --git a/include/vrv/doc.h b/include/vrv/doc.h index 687f6f068e2..48876eec45e 100644 --- a/include/vrv/doc.h +++ b/include/vrv/doc.h @@ -451,6 +451,14 @@ class Doc : public Object { bool IsMensuralMusicOnly() const { return m_isMensuralMusicOnly; } ///@} + /** + * @name Setter for and getter for neume-line flag + */ + ///@{ + void SetNeumeLines(bool isNeumeLines) { m_isNeumeLines = isNeumeLines; } + bool IsNeumeLines() const { return m_isNeumeLines; } + ///@} + /** * @name Setter and getter for facsimile */ @@ -660,6 +668,12 @@ class Doc : public Object { */ bool m_isMensuralMusicOnly; + /** + * A flag to indicate that the document contains neume lines. + * This is a special document type where neume lines are encoded with
+ */ + bool m_isNeumeLines; + /** Page width (MEI scoredef@page.width) - currently not saved */ int m_pageWidth; /** Page height (MEI scoredef@page.height) - currently not saved */ diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index fc148e51488..90dd931c667 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -16,6 +16,7 @@ #include "doc.h" #include "editortoolkit.h" +#include "measure.h" #include "view.h" #include "vrv.h" #include "zone.h" @@ -50,6 +51,8 @@ class EditorToolkitNeume : public EditorToolkit { bool Set(std::string elementId, std::string attrType, std::string attrValue); bool SetText(std::string elementId, const std::string &text); bool SetClef(std::string elementId, std::string shape); + bool SetLiquescent(std::string elementId, std::string shape); + bool SortStaves(); bool Split(std::string elementId, int x); bool SplitNeume(std::string elementId, std::string ncId); bool Remove(std::string elementId); @@ -80,6 +83,7 @@ class EditorToolkitNeume : public EditorToolkit { bool ParseSetAction(jsonxx::Object param, std::string *elementId, std::string *attrType, std::string *attrValue); bool ParseSetTextAction(jsonxx::Object param, std::string *elementId, std::string *text); bool ParseSetClefAction(jsonxx::Object param, std::string *elementId, std::string *shape); + bool ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *shape); bool ParseSplitAction(jsonxx::Object param, std::string *elementId, int *x); bool ParseSplitNeumeAction(jsonxx::Object param, std::string *elementId, std::string *ncId); bool ParseRemoveAction(jsonxx::Object param, std::string *elementId); @@ -178,11 +182,26 @@ struct ClosestNeume { struct StaffSort { // Sort staves left-to-right and top-to-bottom // Sort by y if there is no intersection, by x if there is x intersection is smaller than half length of staff line + + // Update 2024-04: + // Used only in neume lines, + // System->(Measure->Staff) + // Need to sort Measure to sort staff bool operator()(Object *a, Object *b) { - if (!a->GetFacsimileInterface() || !b->GetFacsimileInterface()) return true; - Zone *zoneA = a->GetFacsimileInterface()->GetZone(); - Zone *zoneB = b->GetFacsimileInterface()->GetZone(); + if (!a->Is(SYSTEM) || !b->Is(SYSTEM)) return false; + if (!a->FindDescendantByType(MEASURE) || !b->FindDescendantByType(MEASURE)) return false; + Measure *measureA = dynamic_cast(a->FindDescendantByType(MEASURE)); + Measure *measureB = dynamic_cast(b->FindDescendantByType(MEASURE)); + if (!measureA->IsNeumeLine() || !measureB->IsNeumeLine()) return true; + Object *staffA = a->FindDescendantByType(STAFF); + Object *staffB = b->FindDescendantByType(STAFF); + assert(staffA); + assert(staffB); + Zone *zoneA = staffA->GetFacsimileInterface()->GetZone(); + Zone *zoneB = staffB->GetFacsimileInterface()->GetZone(); + assert(zoneA); + assert(zoneB); int aLowest, bLowest, aHighest, bHighest; diff --git a/include/vrv/facsimilefunctor.h b/include/vrv/facsimilefunctor.h index 1273343626c..7ccf8f3e772 100644 --- a/include/vrv/facsimilefunctor.h +++ b/include/vrv/facsimilefunctor.h @@ -42,7 +42,7 @@ class SyncFromFacsimileFunctor : public Functor { /* * Abstract base implementation */ - bool ImplementsEndInterface() const override { return false; } + bool ImplementsEndInterface() const override { return true; } /* * Functor interface @@ -51,6 +51,7 @@ class SyncFromFacsimileFunctor : public Functor { FunctorCode VisitLayerElement(LayerElement *layerElement) override; FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitPage(Page *page) override; + FunctorCode VisitPageEnd(Page *page) override; FunctorCode VisitPb(Pb *pb) override; FunctorCode VisitSb(Sb *sb) override; FunctorCode VisitStaff(Staff *staff) override; @@ -71,6 +72,9 @@ class SyncFromFacsimileFunctor : public Functor { // Page *m_currentPage; System *m_currentSystem; + Measure *m_currentNeumeLine; + /** map to store the zone corresponding to a staff */ + std::map m_staffZones; }; //---------------------------------------------------------------------------- diff --git a/include/vrv/facsimileinterface.h b/include/vrv/facsimileinterface.h index 7afafef144b..84f46ddb9b8 100644 --- a/include/vrv/facsimileinterface.h +++ b/include/vrv/facsimileinterface.h @@ -58,6 +58,13 @@ class FacsimileInterface : public Interface, public AttFacsimile { Zone *GetZone() { return m_zone; } const Zone *GetZone() const { return m_zone; } ///@} + /// + + /** Get the surface */ + ///@{ + Surface *GetSurface() { return m_surface; } + const Surface *GetSurface() const { return m_surface; } + ///@} //-----------------// // Pseudo functors // diff --git a/include/vrv/layerelement.h b/include/vrv/layerelement.h index cf8a7af9e32..ea8307a9da1 100644 --- a/include/vrv/layerelement.h +++ b/include/vrv/layerelement.h @@ -387,6 +387,7 @@ class LayerElement : public Object, public: /** Absolute position X. This is used for facsimile (transcription) encoding */ int m_drawingFacsX; + int m_drawingFacsY; // This is used only for accid, syl /** * This stores a pointer to the cross-staff (if any) and the appropriate layer * See PrepareCrossStaffFunctor diff --git a/include/vrv/measure.h b/include/vrv/measure.h index 49ab432a0d7..dcf96d15243 100644 --- a/include/vrv/measure.h +++ b/include/vrv/measure.h @@ -53,7 +53,7 @@ class Measure : public Object, * Reset method resets all attribute classes */ ///@{ - Measure(bool measuredMusic = true, int logMeasureNb = -1); + Measure(MeasureType measuredMusic = MEASURED, int logMeasureNb = -1); virtual ~Measure(); Object *Clone() const override { return new Measure(*this); }; void Reset() override; @@ -79,7 +79,12 @@ class Measure : public Object, /** * Return true if measured music (otherwise we have fake measures) */ - bool IsMeasuredMusic() const { return m_measuredMusic; } + bool IsMeasuredMusic() const { return (m_measureType == MEASURED); } + + /** + * Return true if the measure represents a neume (section) line + */ + bool IsNeumeLine() const { return (m_measureType == NEUMELINE); } /** * Get and set the measure index @@ -404,9 +409,10 @@ class Measure : public Object, private: /** - * Indicates measured music (otherwise we have fake measures) + * Indicate measured music (CMN), unmeasured (fake measures for mensural or neumes) or neume lines + * Neume line measure are created from
*/ - bool m_measuredMusic; + MeasureType m_measureType; /** * The unique measure index diff --git a/include/vrv/staff.h b/include/vrv/staff.h index cd68fe3eaa4..92a699a43bf 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -112,6 +112,17 @@ class Staff : public Object, } ///@} + /** + * @name Getters and setters for the rotation. + * Used only with facsimile rendering. + */ + ///@{ + void SetDrawingRotation(double drawingRotation) { m_drawingRotation = drawingRotation; } + double GetDrawingRotation() const { return m_drawingRotation; } + bool HasDrawingRotation() const { return (m_drawingRotation != 0.0); } + int GetDrawingRotationOffsetFor(int x); + ///@} + /** * Delete all the legder line arrays. */ @@ -290,6 +301,12 @@ class Staff : public Object, ArrayOfLedgerLines m_ledgerLinesAboveCue; ArrayOfLedgerLines m_ledgerLinesBelowCue; ///@} + + /** + * The drawing rotation. + * Used only with facsimile rendering + */ + double m_drawingRotation; }; } // namespace vrv diff --git a/include/vrv/view.h b/include/vrv/view.h index 2f59e4c6657..485d1d9b1a9 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -416,6 +416,7 @@ class View { ///@{ void DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); + void DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNeume(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); ///@} diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index 526d3af9fac..bda87157121 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -661,6 +661,14 @@ enum SmuflTextFont { SMUFL_NONE = 0, SMUFL_FONT_SELECTED, SMUFL_FONT_FALLBACK }; enum GraphicID { PRIMARY = 0, SPANNING, SYMBOLREF }; +//---------------------------------------------------------------------------- +// Measure type +//---------------------------------------------------------------------------- + +enum MeasureType { MEASURED = 0, UNMEASURED, NEUMELINE }; + +#define NEUME_LINE_TYPE "neon-neume-line" + //---------------------------------------------------------------------------- // Legacy Wolfgang defines //---------------------------------------------------------------------------- diff --git a/src/adjustyrelfortranscriptionfunctor.cpp b/src/adjustyrelfortranscriptionfunctor.cpp new file mode 100644 index 00000000000..8bcc92402d1 --- /dev/null +++ b/src/adjustyrelfortranscriptionfunctor.cpp @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: adjustyrelfortranscriptionfunctor.cpp +// Author: Yinan Zhou +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "adjustyrelfortranscriptionfunctor.h" + +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// AdjustYRelForTranscriptionFunctor +//---------------------------------------------------------------------------- + +AdjustYRelForTranscriptionFunctor::AdjustYRelForTranscriptionFunctor() : Functor() {} + +FunctorCode AdjustYRelForTranscriptionFunctor::VisitLayerElement(LayerElement *layerElement) +{ + if (layerElement->m_drawingFacsY == VRV_UNSET) return FUNCTOR_CONTINUE; + + if (layerElement->IsScoreDefElement()) return FUNCTOR_SIBLINGS; + + if (!layerElement->HasSelfBB()) return FUNCTOR_CONTINUE; + + layerElement->SetDrawingYRel(-layerElement->GetSelfY1()); + + return FUNCTOR_CONTINUE; +} + +} // namespace vrv diff --git a/src/calcdotsfunctor.cpp b/src/calcdotsfunctor.cpp index 2be321aa7a9..b979a50aa66 100644 --- a/src/calcdotsfunctor.cpp +++ b/src/calcdotsfunctor.cpp @@ -60,6 +60,10 @@ FunctorCode CalcDotsFunctor::VisitChord(Chord *chord) FunctorCode CalcDotsFunctor::VisitNote(Note *note) { + // We currently have no dots object with mensural notes + if (note->IsMensuralDur()) { + return FUNCTOR_SIBLINGS; + } if (!note->IsVisible()) { return FUNCTOR_SIBLINGS; } diff --git a/src/convertfunctor.cpp b/src/convertfunctor.cpp index 61838252781..d80c5db720f 100644 --- a/src/convertfunctor.cpp +++ b/src/convertfunctor.cpp @@ -187,9 +187,12 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) bool nextIsBarline = (next && next->Is(BARLINE)); // See if we create proper measures and what to do with the barLine - bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); + MeasureType convertToMeasured = UNMEASURED; + if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { + convertToMeasured = MEASURED; + } - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { // barLine object will be deleted m_targetMeasure->SetRight(barLine->GetForm()); } @@ -212,7 +215,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitBarLine(BarLine *barLine) // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { m_targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { m_targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1 + m_segmentIdx)); } m_targetSubSystem->AddChild(m_targetMeasure); @@ -277,7 +280,10 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) return FUNCTOR_CONTINUE; } - bool convertToMeasured = m_doc->GetOptions()->m_mensuralToMeasure.GetValue(); + MeasureType convertToMeasured = UNMEASURED; + if (m_doc->GetOptions()->m_mensuralToMeasure.GetValue()) { + convertToMeasured = MEASURED; + } assert(m_targetSystem); assert(m_layerTree); @@ -289,7 +295,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitMeasure(Measure *measure) // Create the first measure segment - problem: we are dropping the section element - we should create a score-based // MEI file instead Measure *targetMeasure = new Measure(convertToMeasured); - if (convertToMeasured) { + if (convertToMeasured == MEASURED) { targetMeasure->SetN(StringFormat("%d", m_segmentTotal + 1)); } m_targetSubSystem->AddChild(targetMeasure); @@ -375,7 +381,7 @@ FunctorCode ConvertToCastOffMensuralFunctor::VisitSyllable(Syllable *syllable) // Make a segment break // First case: add a new measure segment (e.g., first pass) if (m_targetSubSystem->GetChildCount() <= m_segmentIdx) { - m_targetMeasure = new Measure(false); + m_targetMeasure = new Measure(UNMEASURED); m_targetSubSystem->AddChild(m_targetMeasure); // Add a staff with same attributes as in the previous segment m_targetStaff = new Staff(*m_targetStaff); diff --git a/src/devicecontext.cpp b/src/devicecontext.cpp index 09a868e56ab..e734a35919d 100644 --- a/src/devicecontext.cpp +++ b/src/devicecontext.cpp @@ -129,6 +129,11 @@ const Resources *DeviceContext::GetResources(bool showWarning) const return m_resources; } +void DeviceContext::SetViewBoxFactor(double ppuFactor) +{ + m_viewBoxFactor = double(DEFINITION_FACTOR) / ppuFactor; +} + void DeviceContext::SetPen(int color, int width, int style, int dashLength, int gapLength, int lineCap, int lineJoin) { float opacityValue; diff --git a/src/doc.cpp b/src/doc.cpp index 5b13dd476d7..4300e41b8d8 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -132,6 +132,7 @@ void Doc::Reset() m_timemapTempo = 0.0; m_markup = MARKUP_DEFAULT; m_isMensuralMusicOnly = false; + m_isNeumeLines = false; m_isCastOff = false; m_visibleScores.clear(); @@ -1407,6 +1408,8 @@ void Doc::SyncToFacsimileDoc() if (!m_facsimile->FindDescendantByType(SURFACE)) { m_facsimile->AddChild(new Surface()); } + this->ScoreDefSetCurrentDoc(); + m_facsimile->SetType("transcription"); m_facsimile->ClearChildren(); @@ -2128,8 +2131,10 @@ int Doc::GetAdjustedDrawingPageHeight() const { assert(m_drawingPage); + // Take into account the PPU when getting the page height in facsimile if (this->IsTranscription() || this->IsFacs()) { - return m_drawingPage->m_pageHeight / DEFINITION_FACTOR; + const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); + return m_drawingPage->m_pageHeight / factor; } int contentHeight = m_drawingPage->GetContentHeight(); @@ -2140,8 +2145,10 @@ int Doc::GetAdjustedDrawingPageWidth() const { assert(m_drawingPage); + // Take into account the PPU when getting the page width in facsimile if (this->IsTranscription() || this->IsFacs()) { - return m_drawingPage->m_pageWidth / DEFINITION_FACTOR; + const int factor = DEFINITION_FACTOR / m_drawingPage->GetPPUFactor(); + return m_drawingPage->m_pageWidth / factor; } int contentWidth = m_drawingPage->GetContentWidth(); diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 02820ce1754..1882808daaf 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -25,19 +25,21 @@ #include "divline.h" #include "layer.h" #include "liquescent.h" +#include "measure.h" #include "nc.h" #include "neume.h" #include "page.h" #include "rend.h" +#include "sb.h" #include "score.h" #include "staff.h" #include "staffdef.h" #include "surface.h" #include "syl.h" #include "syllable.h" +#include "system.h" #include "text.h" #include "vrv.h" - //-------------------------------------------------------------------------------- namespace vrv { @@ -136,6 +138,13 @@ bool EditorToolkitNeume::ParseEditorAction(const std::string &json_editorAction) } LogWarning("Could not parse the set clef action"); } + else if (action == "setLiquescent") { + std::string elementId, curve; + if (this->ParseSetLiquescentAction(json.get("param"), &elementId, &curve)) { + return this->SetLiquescent(elementId, curve); + } + LogWarning("Could not parse the set liquescent action"); + } else if (action == "remove") { std::string elementId; if (this->ParseRemoveAction(json.get("param"), &elementId)) { @@ -666,11 +675,12 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) if (fi->GetZone() != NULL) zones.insert(fi->GetZone()); } for (auto it = zones.begin(); it != zones.end(); ++it) { - // Transform y to device context (*it)->ShiftByXY(x, -y); } - staff->GetParent()->StableSort(StaffSort()); + SortStaves(); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers } @@ -729,6 +739,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) } Layer *layer = vrv_cast(element->GetFirstAncestor(LAYER)); layer->ReorderByXPos(); // Reflect position order of elements internally (and in the resulting output file) + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", status); m_editInfo.import("message", message); return true; @@ -743,7 +754,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -779,26 +790,27 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Zone *zone = new Zone(); if (elementType == "staff") { - Object *parent; + Object *page = m_doc->GetDrawingPage(); + System *newSystem = new System(); + Sb *newSb = new Sb(); + Measure *newMeasure = new Measure(NEUMELINE); Staff *newStaff; + Layer *newLayer = new Layer(); std::string columnValue; + // Use closest existing staff (if there is one) if (staff) { - parent = staff->GetParent(); - assert(parent); columnValue = staff->GetType(); - int n = parent->GetChildCount() + 1; + int n = page->GetChildCount(SYSTEM) + 1; newStaff = new Staff(n); newStaff->m_drawingStaffDef = staff->m_drawingStaffDef; newStaff->m_drawingNotationType = staff->m_drawingNotationType; newStaff->m_drawingLines = staff->m_drawingLines; } else { - parent = m_doc->GetDrawingPage()->FindDescendantByType(MEASURE); - assert(parent); newStaff = new Staff(1); newStaff->m_drawingStaffDef = vrv_cast( - m_doc->GetCorrespondingScore(parent)->GetScoreDef()->FindDescendantByType(STAFFDEF)); + m_doc->GetCorrespondingScore(page)->GetScoreDef()->FindDescendantByType(STAFFDEF)); newStaff->m_drawingNotationType = NOTATIONTYPE_neume; newStaff->m_drawingLines = 4; } @@ -812,30 +824,18 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in surface->AddChild(zone); newStaff->AttachZone(zone); if (columnValue.length()) newStaff->SetType(columnValue); - Layer *newLayer = new Layer(); + newStaff->AddChild(newLayer); + newMeasure->AddChild(newStaff); + newSystem->AddChild(newSb); + newSystem->AddChild(newMeasure); + newSystem->SetDrawingScoreDef(vrv_cast(m_doc->GetCorrespondingScore(page)->GetScoreDef())); - if (staff) { - // Find index to insert new staff - ListOfObjects staves = parent->FindAllDescendantsByType(STAFF, false); - std::vector stavesVector(staves.begin(), staves.end()); - stavesVector.push_back(newStaff); - StaffSort staffSort; - std::stable_sort(stavesVector.begin(), stavesVector.end(), staffSort); - for (int i = 0; i < (int)staves.size(); ++i) { - if (stavesVector.at(i) == newStaff) { - parent->InsertChild(newStaff, i); - parent->Modify(); - - m_editInfo.import("uuid", newStaff->GetID()); - m_editInfo.import("status", status); - m_editInfo.import("message", message); - - return true; - } - } - } - parent->AddChild(newStaff); + page->InsertAfter(page->GetFirst(SCORE), newSystem); + + SortStaves(); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("uuid", newStaff->GetID()); m_editInfo.import("status", status); @@ -879,20 +879,21 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); // Set up facsimile - zone->SetUlx(ulx); + zone->SetUlx(ulx - offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + offsetX); zone->SetLry(uly + noteHeight); // add syl bounding box if Facs - if (m_doc->GetType() == Facs) { + if (m_doc->HasFacsimile()) { FacsimileInterface *fi = vrv_cast(syl->GetFacsimileInterface()); assert(fi); sylZone = new Zone(); - int staffLry = staff->GetFacsimileInterface()->GetZone()->GetLry(); + int staffLry = staff->GetZone()->GetLry(); // width height and offset can be adjusted int bboxHeight = 175; @@ -903,8 +904,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in int offsetY = 0; if (theta) { double factor = 1.3; - offsetY = (int)((ulx - staff->GetFacsimileInterface()->GetZone()->GetUlx()) * tan(theta * M_PI / 180.0) - / factor); + offsetY = (int)((ulx - staff->GetZone()->GetUlx()) * tan(theta * M_PI / 180.0) / factor); } sylZone->SetUlx(ulx); @@ -945,7 +945,6 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in contour = it->second; } else if (it->first == "curve") { - Liquescent *liquescent = new Liquescent(); curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; if (it->second == "a") { curve = curvatureDirection_CURVE_a; @@ -955,6 +954,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in curve = curvatureDirection_CURVE_c; nc->SetCurve(curve); } + Liquescent *liquescent = new Liquescent(); nc->AddChild(liquescent); } } @@ -993,9 +993,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in // Apply offset due to rotate newUly += (newUlx - ulx) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); - newZone->SetUlx(newUlx); + newZone->SetUlx(newUlx - offsetX); newZone->SetUly(newUly); - newZone->SetLrx(newUlx + noteWidth); + newZone->SetLrx(newUlx + offsetX); newZone->SetLry(newUly + noteHeight); newNc->AttachZone(newZone); @@ -1025,14 +1025,21 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in Clef *clef = new Clef(); data_CLEFSHAPE clefShape = CLEFSHAPE_NONE; + const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int offsetR, offsetL; + for (auto it = attributes.begin(); it != attributes.end(); ++it) { if (it->first == "shape") { if (it->second == "C") { clefShape = CLEFSHAPE_C; + offsetR = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); + offsetL = offsetR; break; } else if (it->second == "F") { clefShape = CLEFSHAPE_F; + offsetR = 0; + offsetL = (int)(staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO / 2); break; } } @@ -1046,17 +1053,16 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } clef->SetShape(clefShape); - const int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int yDiff = -staff->GetDrawingY() + uly; + int yDiff = -staff->GetZone()->GetUly() + uly; yDiff += ((ulx - staff->GetZone()->GetUlx())) * tan(-staff->GetDrawingRotate() * M_PI / 180.0); // Subtract distance due to rotate. int clefLine = staff->m_drawingLines - round((double)yDiff / (double)staffSize); clef->SetLine(clefLine); Zone *zone = new Zone(); - zone->SetUlx(ulx); + zone->SetUlx(ulx - offsetR); zone->SetUly(uly); - zone->SetLrx(ulx + staffSize / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + zone->SetLrx(ulx + offsetL); zone->SetLry(uly + staffSize / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); clef->AttachZone(zone); Surface *surface = dynamic_cast(facsimile->FindDescendantByType(SURFACE)); @@ -1102,13 +1108,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 4); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); if (!AdjustPitchFromPosition(custos)) { @@ -1155,13 +1162,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); @@ -1219,13 +1227,14 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int offsetX = (int)(noteWidth / 2); ulx -= noteWidth / 2; uly -= noteHeight / 2; - zone->SetUlx(ulx); + zone->SetUlx(ulx + offsetX); zone->SetUly(uly); - zone->SetLrx(ulx + noteWidth); + zone->SetLrx(ulx + noteWidth + offsetX); zone->SetLry(uly + noteHeight); layer->ReorderByXPos(); @@ -1239,6 +1248,9 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in return false; } layer->ReorderByXPos(); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", status); m_editInfo.import("message", message); return true; @@ -1252,7 +1264,7 @@ bool EditorToolkitNeume::InsertToSyllable(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1402,7 +1414,7 @@ bool EditorToolkitNeume::MoveOutsideSyllable(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1602,7 +1614,7 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) m_editInfo.import("message", "Could not get drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogError("Drawing page without facsimile"); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Drawing page without facsimile is unsupported."); @@ -1680,6 +1692,8 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) zone->SetLry(uly + offsetY + height); } + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -1771,6 +1785,8 @@ bool EditorToolkitNeume::Merge(std::vector elementIds) m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + // TODO change zones for staff children return true; @@ -1807,10 +1823,7 @@ bool EditorToolkitNeume::Set(std::string elementId, std::string attrType, std::s success = true; else if (AttModule::SetVisual(element, attrType, attrValue)) success = true; - if (success && m_doc->GetType() != Facs) { - m_doc->PrepareData(); - m_doc->GetDrawingPage()->LayOut(true); - } + m_editInfo.import("status", success ? "OK" : "FAILURE"); m_editInfo.import("message", success ? "" : "Could not set attribute '" + attrType + "' to '" + attrValue + "'."); return success; @@ -1876,12 +1889,8 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) std::u32string str = U""; text->SetText(str); syl->AddChild(text); - syllable->AddChild(syl); - Text *textChild = new Text(); - textChild->SetText(wtext); - syl->AddChild(textChild); - if (m_doc->GetType() == Facs) { + if (m_doc->HasFacsimile()) { // Create a default bounding box Zone *zone = new Zone(); int ulx, uly, lrx, lry; @@ -1918,6 +1927,9 @@ bool EditorToolkitNeume::SetText(std::string elementId, const std::string &text) m_editInfo.import("message", "Element type '" + element->GetClassName() + "' is unsupported for SetText."); return false; } + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", success ? status : "FAILURE"); m_editInfo.import("message", success ? message : "SetText method failed."); return success; @@ -1975,10 +1987,50 @@ bool EditorToolkitNeume::SetClef(std::string elementId, std::string shape) pi->AdjustPitchByOffset(shift); } } - if (success && m_doc->GetType() != Facs) { - m_doc->PrepareData(); - m_doc->GetDrawingPage()->LayOut(true); + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); + return true; +} + +bool EditorToolkitNeume::SetLiquescent(std::string elementId, std::string curve) +{ + if (!m_doc->GetDrawingPage()) { + LogError("Could not get the drawing page."); + m_editInfo.import("status", "FAILURE"); + m_editInfo.import("message", "Could not get the drawing page."); + return false; + } + + Nc *nc = vrv_cast(m_doc->GetDrawingPage()->FindDescendantByID(elementId)); + assert(nc); + bool hasLiquscent = nc->GetChildCount(); + + if (curve == "a") { + curvatureDirection_CURVE curve = curvatureDirection_CURVE_a; + nc->SetCurve(curve); + if (!hasLiquscent) { + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); + } + } + else if (curve == "c") { + curvatureDirection_CURVE curve = curvatureDirection_CURVE_c; + nc->SetCurve(curve); + if (!hasLiquscent) { + Liquescent *liquescent = new Liquescent(); + nc->AddChild(liquescent); + } + } + else { + // For unset curve + curvatureDirection_CURVE curve = curvatureDirection_CURVE_NONE; + nc->SetCurve(curve); + if (hasLiquscent) { + Liquescent *liquescent = vrv_cast(nc->FindDescendantByType(LIQUESCENT)); + nc->DeleteChild(liquescent); + } } + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -2116,6 +2168,9 @@ bool EditorToolkitNeume::Split(std::string elementId, int x) } } layer->ClearRelinquishedChildren(); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); m_editInfo.import("uuid", splitStaff->GetID()); @@ -2150,7 +2205,7 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) linkedSyllable->AddChild(syl); // Create default bounding box if facs - if (m_doc->GetType() == Facs) { + if (m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx( @@ -2169,6 +2224,8 @@ void EditorToolkitNeume::UnlinkSyllable(Syllable *syllable) FacsimileInterface *fi = syl->GetFacsimileInterface(); assert(fi); fi->AttachZone(zone); + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); } } } @@ -2380,7 +2437,7 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx m_editInfo.import("message", "Could not get the drawing page."); return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogWarning("Resizing is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Resizing is only available in facsimile mode."); @@ -2409,11 +2466,28 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx zone->SetUly(uly); zone->SetLrx(lrx); zone->SetLry(lry); + double orgRotate = staff->GetDrawingRotation(); if (!isnan(rotate)) { zone->SetRotate(rotate); } zone->Modify(); - staff->GetParent()->StableSort(StaffSort()); + SortStaves(); + + if (staff->HasDrawingRotation()) { + ListOfObjects accids = staff->FindAllDescendantsByType(ACCID); + for (auto it = accids.begin(); it != accids.end(); ++it) { + Accid *accid = dynamic_cast(*it); + FacsimileInterface *fi = accid->GetFacsimileInterface(); + Zone *accidZone = fi->GetZone(); + double rotationOffset = (accid->GetDrawingX() - staff->GetDrawingX()) * tan(rotate * M_PI / 180.0); + if (orgRotate) { + double orgOffset = (accid->GetDrawingX() - staff->GetDrawingX()) * tan(orgRotate * M_PI / 180.0); + rotationOffset -= orgOffset; + } + accidZone->SetUly(accidZone->GetUly() + int(rotationOffset)); + accidZone->SetLry(accidZone->GetLry() + int(rotationOffset)); + } + } } else if (obj->Is(SYL)) { Syl *syl = vrv_cast(obj); @@ -2455,6 +2529,9 @@ bool EditorToolkitNeume::Resize(std::string elementId, int ulx, int uly, int lrx m_editInfo.import("message", "Element of type '" + obj->GetClassName() + "' is unsupported."); return false; } + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; @@ -2689,7 +2766,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e parent->AddChild(syl); // add a default bounding box if you need to - if (m_doc->GetType() == Facs) { + if (m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx(parent->GetFirst(NEUME)->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -2747,7 +2824,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e std::u32string fullString = U""; for (auto it = fullParents.begin(); it != fullParents.end(); ++it) { Syl *syl = dynamic_cast((*it)->FindDescendantByType(SYL)); - if (syl != NULL && m_doc->GetType() == Facs) { + if (syl != NULL && m_doc->HasFacsimile()) { Zone *zone = dynamic_cast(syl->GetFacsimileInterface()->GetZone()); if (fullSyl == NULL) { @@ -2781,7 +2858,7 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e fullText->SetText(fullString); parent->AddChild(fullSyl); - if (m_doc->GetType() == Facs) { + if (m_doc->HasFacsimile()) { Zone *zone = dynamic_cast(fullSyl->GetFacsimileInterface()->GetZone()); zone->SetUlx(ulx); zone->SetUly(uly); @@ -2820,6 +2897,10 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e + obj->GetChildCount(CLEF))) { Object *leftover; while ((leftover = obj->FindDescendantByType(SYL)) != NULL) { + Zone *zone = dynamic_cast(leftover->GetFacsimileInterface()->GetZone()); + if (zone != NULL) { + m_doc->GetFacsimile()->FindDescendantByType(SURFACE)->DeleteChild(zone); + } obj->DeleteChild(leftover); } while ((leftover = obj->FindDescendantByType(DIVLINE)) != NULL) { @@ -2840,6 +2921,8 @@ bool EditorToolkitNeume::Group(std::string groupType, std::vector e secondParent->ReorderByXPos(); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("uuid", parent->GetID()); m_editInfo.import("status", status); m_editInfo.import("message", message); @@ -2873,6 +2956,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector ListOfObjects syllables; // List of syllables used. groupType=neume only. jsonxx::Array uuidArray; + bool breakOnEnd = false; // Check if you can get drawing page if (!m_doc->GetDrawingPage()) { @@ -2947,7 +3031,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } } - if (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { + while (el->Is(ACCID) || el->Is(DIVLINE) || el->Is(CLEF)) { fparent = el->GetFirstAncestor(SYLLABLE); sparent = el->GetFirstAncestor(LAYER); if (fparent && sparent) { @@ -2957,10 +3041,15 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector fparent->ReorderByXPos(); uuidArray << (*it); it = elementIds.erase(it); - if (it == elementIds.end()) break; + if (it == elementIds.end()) { + breakOnEnd = true; + break; + } el = m_doc->GetDrawingPage()->FindDescendantByID(*it); } } + if (breakOnEnd) break; + if (elementIds.begin() == it || firstIsSyl) { // if the element is a syl we want it to stay attached to the first element // we'll still need to initialize all the parents, thus the bool @@ -3062,7 +3151,7 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector newParent->AddChild(syl); // Create default bounding box if facs - if (m_doc->GetType() == Facs) { + if (m_doc->HasFacsimile()) { Zone *zone = new Zone(); zone->SetUlx(el->GetFirst(NC)->GetFacsimileInterface()->GetZone()->GetUlx()); @@ -3116,6 +3205,8 @@ bool EditorToolkitNeume::Ungroup(std::string groupType, std::vector } } + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); m_editInfo.import("uuid", uuidArray); @@ -3283,7 +3374,6 @@ bool EditorToolkitNeume::ChangeGroup(std::string elementId, std::string contour) } zone->SetUlx(newUlx); zone->SetUly(newUly); - ; zone->SetLrx(newLrx); zone->SetLry(newLry); @@ -3301,6 +3391,9 @@ bool EditorToolkitNeume::ChangeGroup(std::string elementId, std::string contour) initialLry = newLry; prevNc = newNc; } + + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("uuid", el->GetID()); m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); @@ -3312,12 +3405,6 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) assert(elementIds.size() == 2); bool success1 = false; bool success2 = false; - - Facsimile *facsimile = m_doc->GetFacsimile(); - assert(facsimile); - Surface *surface = vrv_cast(facsimile->FindDescendantByType(SURFACE)); - assert(surface); - std::string firstNcId = elementIds[0]; std::string secondNcId = elementIds[1]; // Check if you can get drawing page @@ -3343,77 +3430,66 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) return false; } - bool isLigature; + bool isLigature = false; if (firstNc->HasAttribute("ligated", "true") && secondNc->HasAttribute("ligated", "true")) { isLigature = true; } else { - isLigature = false; - Set(firstNc->GetID(), "tilt", ""); - Set(secondNc->GetID(), "tilt", ""); - Set(firstNc->GetID(), "curve", ""); - Set(secondNc->GetID(), "curve", ""); + Set(firstNcId, "tilt", ""); + Set(secondNcId, "tilt", ""); + Set(firstNcId, "curve", ""); + Set(secondNcId, "curve", ""); } - Zone *zone = new Zone(); - // set ligature to false and update zone of second Nc - if (isLigature) { - if (AttModule::SetNeumes(firstNc, "ligated", "false")) success1 = true; - - int ligUlx = firstNc->GetZone()->GetUlx(); - int ligUly = firstNc->GetZone()->GetUly(); - int ligLrx = firstNc->GetZone()->GetLrx(); - int ligLry = firstNc->GetZone()->GetLry(); + Zone *firstNcZone = firstNc->GetZone(); + Zone *secondNcZone = secondNc->GetZone(); - Staff *staff = dynamic_cast(firstNc->GetFirstAncestor(STAFF)); - assert(staff); + int ligUlx = firstNcZone->GetUlx(); + int ligUly = firstNcZone->GetUly(); + int ligLrx = firstNcZone->GetLrx(); + int ligLry = firstNcZone->GetLry(); - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + Staff *staff = dynamic_cast(firstNc->GetFirstAncestor(STAFF)); + assert(staff); + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - zone->SetUlx(ligUlx + noteWidth); - zone->SetUly(ligUly + noteHeight); - zone->SetLrx(ligLrx + noteWidth); - zone->SetLry(ligLry + noteHeight); + // set ligature to false and update zone of second Nc + if (isLigature) { + if (Set(firstNcId, "ligated", "false")) success1 = true; - secondNc->AttachZone(zone); + secondNcZone->SetUlx(ligUlx + noteWidth); + secondNcZone->SetUly(ligUly + noteHeight); + secondNcZone->SetLrx(ligLrx + noteWidth); + secondNcZone->SetLry(ligLry + noteHeight); - if (AttModule::SetNeumes(secondNc, "ligated", "false")) success2 = true; + if (Set(secondNcId, "ligated", "false")) success2 = true; } // set ligature to true and update zones to be the same - else if (!isLigature) { - if (AttModule::SetNeumes(firstNc, "ligated", "true")) success1 = true; - - zone->SetUlx(firstNc->GetZone()->GetUlx()); - zone->SetUly(firstNc->GetZone()->GetUly()); - zone->SetLrx(firstNc->GetZone()->GetLrx()); - zone->SetLry(firstNc->GetZone()->GetLry()); + else { + if (Set(firstNcId, "ligated", "true")) success1 = true; - secondNc->AttachZone(zone); + secondNcZone->SetUlx(ligUlx); + secondNcZone->SetUly(ligUly + noteHeight); + secondNcZone->SetLrx(ligLrx); + secondNcZone->SetLry(ligLry + noteHeight); - if (AttModule::SetNeumes(secondNc, "ligated", "true")) success2 = true; - } - // else { - // LogError("isLigature is invalid!"); - // m_editInfo.import("status", "FAILURE"); - // m_editInfo.import("message", "isLigature value '" + isLigature + "' is invalid."); - // return false; - // } - if (success1 && success2 && m_doc->GetType() != Facs) { - m_doc->PrepareData(); - m_doc->GetDrawingPage()->LayOut(true); + if (Set(secondNcId, "ligated", "true")) success2 = true; } - m_editInfo.import("status", "OK"); - m_editInfo.import("message", ""); + if (!(success1 && success2)) { LogWarning("Unable to update ligature attribute"); m_editInfo.import("message", "Unable to update ligature attribute."); m_editInfo.import("status", "WARNING"); + return false; } - surface->AddChild(zone); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + + m_editInfo.import("status", "OK"); + m_editInfo.import("message", ""); return success1 && success2; } @@ -3426,7 +3502,7 @@ bool EditorToolkitNeume::ChangeStaff(std::string elementId) return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogWarning("Staff re-association is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Staff re-association is only available in facsimile mode."); @@ -3631,7 +3707,7 @@ bool EditorToolkitNeume::ChangeStaffTo(std::string elementId, std::string staffI return false; } - if (m_doc->GetType() != Facs) { + if (!m_doc->HasFacsimile()) { LogWarning("Staff re-association is only available in facsimile mode."); m_editInfo.import("status", "FAILURE"); m_editInfo.import("message", "Staff re-association is only available in facsimile mode."); @@ -3949,6 +4025,21 @@ bool EditorToolkitNeume::ParseSetClefAction(jsonxx::Object param, std::string *e return true; } +bool EditorToolkitNeume::ParseSetLiquescentAction(jsonxx::Object param, std::string *elementId, std::string *curve) +{ + if (!param.has("elementId")) { + LogWarning("Could not parse 'elementId'"); + return false; + } + *elementId = param.get("elementId"); + if (!param.has("curve")) { + LogWarning("Could not parse 'curve'"); + return false; + } + *curve = param.get("curve"); + return true; +} + bool EditorToolkitNeume::ParseRemoveAction(jsonxx::Object param, std::string *elementId) { if (!param.has("elementId")) return false; @@ -4128,15 +4219,12 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - // Use the same pitchDifference equation for both syllables and custos - const int pitchDifference - = round((double)(staff->GetDrawingY() + (2 * staffSize * (staff->m_drawingLines - clef->GetLine())) - - fi->GetZone()->GetUly() - - ((fi->GetZone()->GetUlx() - staff->GetZone()->GetUlx()) - * tan(-staff->GetDrawingRotate() * M_PI / 180.0))) - / (double)(staffSize)); - - pi->AdjustPitchByOffset(pitchDifference); + const int pitchDifference = round( + (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) + - m_view->ToLogicalY(fi->GetZone()->GetUly())) + / staffSize + - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); + pi->AdjustPitchByOffset(-pitchDifference); return true; } @@ -4178,6 +4266,8 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); for (auto it = pitchedChildren.begin(); it != pitchedChildren.end(); ++it) { + if ((*it)->Is(LIQUESCENT)) continue; + FacsimileInterface *fi = (*it)->GetFacsimileInterface(); if (fi == NULL || !fi->HasFacs()) { LogError("Could not adjust pitch: child %s does not have facsimile data", (*it)->GetID().c_str()); @@ -4196,15 +4286,12 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) } pi->SetOct(octave); - // Use the same pitchDifference equation for both syllables and custos - const int pitchDifference - = round((double)(staff->GetDrawingY() + (2 * staffSize * (staff->m_drawingLines - clef->GetLine())) - - fi->GetZone()->GetUly() - - ((fi->GetZone()->GetUlx() - staff->GetZone()->GetUlx()) - * tan(-staff->GetDrawingRotate() * M_PI / 180.0))) - / (double)(staffSize)); - - pi->AdjustPitchByOffset(pitchDifference); + const int pitchDifference = round( + (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) + - m_view->ToLogicalY(fi->GetZone()->GetUly())) + / staffSize + - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); + pi->AdjustPitchByOffset(-pitchDifference); } return true; diff --git a/src/facsimilefunctor.cpp b/src/facsimilefunctor.cpp index 73ec1ef23ca..f70d6804d9c 100644 --- a/src/facsimilefunctor.cpp +++ b/src/facsimilefunctor.cpp @@ -12,6 +12,7 @@ #include "doc.h" #include "layerelement.h" #include "measure.h" +#include "miscfunctor.h" #include "page.h" #include "pb.h" #include "sb.h" @@ -39,42 +40,80 @@ SyncFromFacsimileFunctor::SyncFromFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncFromFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ ACCID, CLEF, CUSTOS, DIVLINE, LIQUESCENT, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; Zone *zone = layerElement->GetZone(); assert(zone); layerElement->m_drawingFacsX = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + if (layerElement->Is({ ACCID, SYL })) { + layerElement->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); + } return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitMeasure(Measure *measure) { - Zone *zone = measure->GetZone(); - assert(zone); - measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); - measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + // neon specific code - measure have no zone, we will use the staff one in VisitStaff + if (measure->IsNeumeLine()) { + m_currentNeumeLine = measure; + } + else { + Zone *zone = measure->GetZone(); + assert(zone); + measure->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + measure->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + } return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitPage(Page *page) { + m_staffZones.clear(); m_currentPage = page; m_doc->SetDrawingPage(m_currentPage->GetIdx()); return FUNCTOR_CONTINUE; } +FunctorCode SyncFromFacsimileFunctor::VisitPageEnd(Page *page) +{ + // Used for adjusting staff size in neon - filled in VisitStaff + if (m_staffZones.empty()) return FUNCTOR_CONTINUE; + + // The staff size is calculated based on the zone height and takes into acocunt the rotation + for (auto &[staff, zone] : m_staffZones) { + double rotate = (zone->HasRotate()) ? zone->GetRotate() : 0.0; + int yDiff + = zone->GetLry() - zone->GetUly() - (zone->GetLrx() - zone->GetUlx()) * tan(abs(rotate) * M_PI / 180.0); + staff->m_drawingStaffSize + = 100 * yDiff / (m_doc->GetOptions()->m_unit.GetValue() * 2 * (staff->m_drawingLines - 1)); + staff->SetDrawingRotation(rotate); + } + + // Since we multiply all values by the DEFINITION_FACTOR, set it as PPU + m_currentPage->SetPPUFactor(DEFINITION_FACTOR); + if (m_currentPage->GetPPUFactor() != 1.0) { + ApplyPPUFactorFunctor applyPPUFactor; + m_currentPage->Process(applyPPUFactor); + m_doc->UpdatePageDrawingSizes(); + } + + return FUNCTOR_CONTINUE; +} + FunctorCode SyncFromFacsimileFunctor::VisitPb(Pb *pb) { // This would happen if we run the functor on data not converted to page-based assert(m_currentPage); Zone *zone = pb->GetZone(); - assert(zone && zone->GetParent()); - Surface *surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; - // Use the parent surface attributes if given + Surface *surface = pb->GetSurface(); + if (!surface && zone && zone->GetParent()) + surface = (zone->GetParent()->Is(SURFACE)) ? vrv_cast(zone->GetParent()) : NULL; + assert(zone || surface); + // Use the (parent) surface attributes if given if (surface && surface->HasLrx() && surface->HasLry()) { m_currentPage->m_pageHeight = surface->GetLry() * DEFINITION_FACTOR; m_currentPage->m_pageWidth = surface->GetLrx() * DEFINITION_FACTOR; @@ -109,12 +148,27 @@ FunctorCode SyncFromFacsimileFunctor::VisitStaff(Staff *staff) assert(zone); staff->m_drawingFacsY = m_view.ToLogicalY(zone->GetUly() * DEFINITION_FACTOR); + // neon specific code - set the position of the pseudo measure (neume line) + if (m_currentNeumeLine) { + m_currentNeumeLine->m_drawingFacsX1 = m_view.ToLogicalX(zone->GetUlx() * DEFINITION_FACTOR); + m_currentNeumeLine->m_drawingFacsX2 = m_view.ToLogicalX(zone->GetLrx() * DEFINITION_FACTOR); + m_staffZones[staff] = zone; + + // The staff slope is going up. The y left position needs to be adjusted accordingly + if (zone->GetRotate() < 0) { + staff->m_drawingFacsY = staff->m_drawingFacsY + + (m_currentNeumeLine->m_drawingFacsX2 - m_currentNeumeLine->m_drawingFacsX1) + * tan(zone->GetRotate() * M_PI / 180.0); + } + } + return FUNCTOR_CONTINUE; } FunctorCode SyncFromFacsimileFunctor::VisitSystem(System *system) { m_currentSystem = system; + m_currentNeumeLine = NULL; return FUNCTOR_CONTINUE; } @@ -136,10 +190,13 @@ SyncToFacsimileFunctor::SyncToFacsimileFunctor(Doc *doc) : Functor() FunctorCode SyncToFacsimileFunctor::VisitLayerElement(LayerElement *layerElement) { - if (!layerElement->Is({ NOTE, REST })) return FUNCTOR_CONTINUE; + if (!layerElement->Is({ ACCID, CLEF, CUSTOS, DIVLINE, LIQUESCENT, NC, NOTE, REST, SYL })) return FUNCTOR_CONTINUE; Zone *zone = this->GetZone(layerElement, layerElement->GetClassName()); zone->SetUlx(m_view.ToDeviceContextX(layerElement->GetDrawingX()) / DEFINITION_FACTOR + m_pageMarginLeft); + if (layerElement->Is({ ACCID, SYL })) { + zone->SetUly(m_view.ToDeviceContextY(layerElement->GetDrawingY()) / DEFINITION_FACTOR + m_pageMarginTop); + } return FUNCTOR_CONTINUE; } diff --git a/src/iodarms.cpp b/src/iodarms.cpp index debf3f3181a..0f31bb84223 100644 --- a/src/iodarms.cpp +++ b/src/iodarms.cpp @@ -473,7 +473,7 @@ bool DarmsInput::Import(const std::string &data_str) score->AddChild(section); m_staff = new Staff(1); - m_measure = new Measure(true, 1); + m_measure = new Measure(MEASURED, 1); m_layer = new Layer(); m_layer->SetN(1); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 4353e83816a..5e7423a1e7b 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -29294,7 +29294,7 @@ void HumdrumInput::setupSystemMeasure(int startline, int endline) } if (hasMensuralStaff(&infile[startline])) { - m_measure = new Measure(false); + m_measure = new Measure(UNMEASURED); } else { m_measure = new Measure(); diff --git a/src/iomei.cpp b/src/iomei.cpp index aa139f99077..5d1e8ae1200 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -445,7 +445,14 @@ bool MEIOutput::WriteObjectInternal(Object *object, bool useCustomScoreDef) this->WriteSymbolTable(m_currentNode, vrv_cast(object)); } else if (object->Is(MEASURE)) { - m_currentNode = m_currentNode.append_child("measure"); + Measure *measure = vrv_cast(object); + assert(measure); + std::string name = "measure"; + if (measure->IsNeumeLine()) { + name = "section"; + measure->SetType(NEUME_LINE_TYPE); + } + m_currentNode = m_currentNode.append_child(name.c_str()); this->WriteMeasure(m_currentNode, vrv_cast(object)); } else if (object->Is(STAFF)) { @@ -1894,7 +1901,7 @@ void MEIOutput::WriteMeasure(pugi::xml_node currentNode, Measure *measure) measure->WritePointing(currentNode); measure->WriteTyped(currentNode); // For now we copy the adjusted value of coord.x1 and coord.x2 to xAbs and xAbs2 respectively - if ((measure->m_drawingFacsX1 != VRV_UNSET) && (measure->m_drawingFacsX2 != VRV_UNSET)) { + if ((measure->m_drawingFacsX1 != VRV_UNSET) && (measure->m_drawingFacsX2 != VRV_UNSET) && !m_doc->IsNeumeLines()) { measure->SetCoordX1(measure->m_drawingFacsX1 / DEFINITION_FACTOR); measure->SetCoordX2(measure->m_drawingFacsX2 / DEFINITION_FACTOR); measure->WriteCoordX1(currentNode); @@ -2226,7 +2233,7 @@ void MEIOutput::WriteStaff(pugi::xml_node currentNode, Staff *staff) staff->WriteVisibility(currentNode); // y position - if (staff->m_drawingFacsY != VRV_UNSET) { + if (staff->m_drawingFacsY != VRV_UNSET && !(m_doc->IsNeumeLines())) { staff->SetCoordY1(staff->m_drawingFacsY / DEFINITION_FACTOR); staff->WriteCoordY1(currentNode); } @@ -2306,7 +2313,7 @@ void MEIOutput::WriteLayerElement(pugi::xml_node currentNode, LayerElement *elem this->WriteLinkingInterface(currentNode, element); element->WriteLabelled(currentNode); element->WriteTyped(currentNode); - if (element->m_drawingFacsX != VRV_UNSET) { + if (element->m_drawingFacsX != VRV_UNSET && !(m_doc->IsNeumeLines())) { element->SetCoordX1(element->m_drawingFacsX / DEFINITION_FACTOR); element->WriteCoordX1(currentNode); } @@ -2703,6 +2710,7 @@ void MEIOutput::WriteNc(pugi::xml_node currentNode, Nc *nc) this->WritePitchInterface(currentNode, nc); this->WritePositionInterface(currentNode, nc); nc->WriteColor(currentNode); + nc->WriteCurvatureDirection(currentNode); nc->WriteIntervalMelodic(currentNode); nc->WriteNcForm(currentNode); } @@ -4394,7 +4402,13 @@ bool MEIInput::ReadScore(Object *parent, pugi::xml_node score) bool MEIInput::ReadSection(Object *parent, pugi::xml_node section) { Section *vrvSection = new Section(); - this->SetMeiID(section, vrvSection); + this->ReadSystemElement(section, vrvSection); + + if (vrvSection->GetType() == NEUME_LINE_TYPE) { + delete vrvSection; + m_doc->SetNeumeLines(true); + return ReadSectionChildren(parent, section); + } vrvSection->ReadNNumberLike(section); vrvSection->ReadSectionVis(section); @@ -4454,8 +4468,13 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) else if (std::string(current.name()) == "staff") { if (!unmeasured) { if (parent->Is(SECTION)) { - unmeasured = new Measure(false); - m_doc->SetMensuralMusicOnly(true); + if (m_doc->IsNeumeLines()) { + unmeasured = new Measure(NEUMELINE); + } + else { + unmeasured = new Measure(UNMEASURED); + m_doc->SetMensuralMusicOnly(true); + } parent->AddChild(unmeasured); } else { @@ -4483,9 +4502,15 @@ bool MEIInput::ReadSectionChildren(Object *parent, pugi::xml_node parentNode) } // New for blank files in neume notation - if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume)) { - unmeasured = new Measure(false); - m_doc->SetMensuralMusicOnly(true); + if (!unmeasured && parent->Is(SECTION) && (m_doc->m_notationType == NOTATIONTYPE_neume) + && !parent->FindDescendantByType(MEASURE)) { + if (m_doc->IsNeumeLines()) { + unmeasured = new Measure(NEUMELINE); + } + else { + unmeasured = new Measure(UNMEASURED); + m_doc->SetMensuralMusicOnly(true); + } parent->AddChild(unmeasured); } return success; @@ -4628,7 +4653,7 @@ bool MEIInput::ReadSystemChildren(Object *parent, pugi::xml_node parentNode) if (parent->Is(SYSTEM)) { System *system = vrv_cast(parent); assert(system); - unmeasured = new Measure(false); + unmeasured = new Measure(UNMEASURED); m_doc->SetMensuralMusicOnly(true); if (m_doc->IsTranscription() && (m_meiversion == meiVersion_MEIVERSION_2013)) { UpgradeMeasureTo_3_0_0(unmeasured, system); @@ -6809,6 +6834,7 @@ bool MEIInput::ReadNc(Object *parent, pugi::xml_node nc) this->ReadPitchInterface(nc, vrvNc); this->ReadPositionInterface(nc, vrvNc); vrvNc->ReadColor(nc); + vrvNc->ReadCurvatureDirection(nc); vrvNc->ReadIntervalMelodic(nc); vrvNc->ReadNcForm(nc); @@ -8183,12 +8209,14 @@ void MEIInput::UpgradePageTo_5_0(Page *page) PageMilestoneEnd *scoreEnd = new PageMilestoneEnd(score); page->AddChild(scoreEnd); + score->SetEnd(scoreEnd); Mdiv *mdiv = new Mdiv(); page->InsertChild(mdiv, 0); PageMilestoneEnd *mdivEnd = new PageMilestoneEnd(mdiv); page->AddChild(mdivEnd); + mdiv->SetEnd(mdivEnd); } void MEIInput::UpgradePgHeadFootTo_5_0(pugi::xml_node element) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index 001017a6f9d..a7bb45ee05c 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -1835,9 +1835,6 @@ void MusicXmlInput::ReadMusicXmlAttributes( section->AddChild(scoreDef); } - else if (time && node.select_node("ancestor::part[(preceding-sibling::part)]")) { - m_meterUnit = time.child("beat-type").text().as_int(); - } pugi::xpath_node measureRepeat = node.select_node("measure-style/measure-repeat"); pugi::xpath_node measureSlash = node.select_node("measure-style/slash"); diff --git a/src/iopae.cpp b/src/iopae.cpp index 1e844d31a06..005f1ce0d3d 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -2884,7 +2884,7 @@ bool PAEInput::Import(const std::string &input) } // Add a measure at the beginning of the data because there is always at least one measure - Measure *measure = new Measure(true, 1); + Measure *measure = new Measure(MEASURED, 1); // By default there is no end barline on an incipit measure->SetRight(BARRENDITION_invis); m_pae.push_back(pae::Token(0, pae::UNKOWN_POS, measure)); @@ -3403,7 +3403,7 @@ bool PAEInput::ConvertMeasure() // We can now create a new measure but not if we have reached the end of the data if (!token.IsEnd()) { measureCount++; - currentMeasure = new Measure(true, measureCount); + currentMeasure = new Measure(MEASURED, measureCount); currentMeasure->SetRight(BARRENDITION_invis); measureToken->m_object = currentMeasure; } diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 459d4c505df..25dd5d9ad77 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -129,6 +129,7 @@ void LayerElement::Reset() m_drawingFacsX = VRV_UNSET; m_drawingYRel = 0; + m_drawingFacsY = VRV_UNSET; m_drawingXRel = 0; m_drawingCueSize = false; @@ -396,14 +397,6 @@ void LayerElement::SetGraceAlignment(Alignment *graceAlignment) int LayerElement::GetDrawingX() const { - // If this element has a facsimile and we are in facsimile mode, use Facsimile::GetDrawingX - if (this->HasFacs()) { - const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); - assert(doc); - if (doc->IsFacs()) { - return FacsimileInterface::GetDrawingX(); - } - } // Since m_drawingFacsX is the left position, we adjust the XRel accordingly in AdjustXRelForTranscription if (m_drawingFacsX != VRV_UNSET) return m_drawingFacsX + this->GetDrawingXRel(); @@ -444,14 +437,8 @@ int LayerElement::GetDrawingX() const int LayerElement::GetDrawingY() const { - // If this element has a facsimile and we are in facsimile mode, use Facsimile::GetDrawingY - if (this->HasFacs()) { - const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); - assert(doc); - if (doc->IsFacs()) { - return FacsimileInterface::GetDrawingY(); - } - } + + if (m_drawingFacsY != VRV_UNSET) return m_drawingFacsY + this->GetDrawingYRel(); if (m_cachedDrawingY != VRV_UNSET) return m_cachedDrawingY; diff --git a/src/measure.cpp b/src/measure.cpp index 322b01ea635..d5e48b679f7 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -54,7 +54,7 @@ namespace vrv { static const ClassRegistrar s_factory("measure", MEASURE); -Measure::Measure(bool measureMusic, int logMeasureNb) +Measure::Measure(MeasureType measureMusic, int logMeasureNb) : Object(MEASURE, "measure-") , FacsimileInterface() , AttBarring() @@ -76,7 +76,7 @@ Measure::Measure(bool measureMusic, int logMeasureNb) this->RegisterAttClass(ATT_TYPED); this->RegisterInterface(FacsimileInterface::GetAttClasses(), FacsimileInterface::IsInterface()); - m_measuredMusic = measureMusic; + m_measureType = measureMusic; // We set parent to it because we want to access the parent doc from the aligners m_measureAligner.SetParent(this); @@ -95,7 +95,7 @@ Measure::Measure(bool measureMusic, int logMeasureNb) this->Reset(); - if (!this->IsMeasuredMusic()) this->SetRight(BARRENDITION_invis); + if (!measureMusic) this->SetRight(BARRENDITION_invis); } Measure::~Measure() @@ -149,8 +149,10 @@ void Measure::Reset() m_rightBarLine.SetForm(this->GetRight()); m_leftBarLine.SetForm(this->GetLeft()); - m_drawingFacsX1 = VRV_UNSET; - m_drawingFacsX2 = VRV_UNSET; + if (!m_measureType) { + m_drawingFacsX1 = VRV_UNSET; + m_drawingFacsX2 = VRV_UNSET; + } m_drawingEnding = NULL; m_hasAlignmentRefWithMultipleLayers = false; @@ -211,14 +213,6 @@ void Measure::AddChildBack(Object *child) int Measure::GetDrawingX() const { - if (!this->IsMeasuredMusic()) { - const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); - assert(system); - if (system->m_drawingFacsY != VRV_UNSET) { - return (system->m_systemLeftMar); - } - } - if (m_drawingFacsX1 != VRV_UNSET) return m_drawingFacsX1; if (m_cachedDrawingX != VRV_UNSET) return m_cachedDrawingX; @@ -351,17 +345,6 @@ int Measure::GetRightBarLineRight() const int Measure::GetWidth() const { - if (!this->IsMeasuredMusic()) { - const System *system = vrv_cast(this->GetFirstAncestor(SYSTEM)); - assert(system); - if (system->m_drawingFacsY != VRV_UNSET) { - const Page *page = vrv_cast(system->GetFirstAncestor(PAGE)); - assert(page); - // xAbs2 = page->m_pageWidth - system->m_systemRightMar; - return page->m_pageWidth - system->m_systemLeftMar - system->m_systemRightMar; - } - } - if (m_drawingFacsX2 != VRV_UNSET) return (m_drawingFacsX2 - m_drawingFacsX1); assert(m_measureAligner.GetRightAlignment()); diff --git a/src/miscfunctor.cpp b/src/miscfunctor.cpp index 9c3677a2823..f10936c906e 100644 --- a/src/miscfunctor.cpp +++ b/src/miscfunctor.cpp @@ -33,6 +33,7 @@ FunctorCode ApplyPPUFactorFunctor::VisitLayerElement(LayerElement *layerElement) if (layerElement->IsScoreDefElement()) return FUNCTOR_SIBLINGS; if (layerElement->m_drawingFacsX != VRV_UNSET) layerElement->m_drawingFacsX /= m_page->GetPPUFactor(); + if (layerElement->m_drawingFacsY != VRV_UNSET) layerElement->m_drawingFacsY /= m_page->GetPPUFactor(); return FUNCTOR_CONTINUE; } diff --git a/src/page.cpp b/src/page.cpp index 11264de6c5a..614b64180bc 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -33,6 +33,7 @@ #include "adjustxposfunctor.h" #include "adjustxrelfortranscriptionfunctor.h" #include "adjustyposfunctor.h" +#include "adjustyrelfortranscriptionfunctor.h" #include "alignfunctor.h" #include "bboxdevicecontext.h" #include "cachehorizontallayoutfunctor.h" @@ -270,6 +271,8 @@ void Page::LayOutTranscription(bool force) AdjustXRelForTranscriptionFunctor adjustXRelForTranscription; this->Process(adjustXRelForTranscription); + AdjustYRelForTranscriptionFunctor adjustYRelForTranscription; + this->Process(adjustYRelForTranscription); CalcLedgerLinesFunctor calcLedgerLines(doc); this->Process(calcLedgerLines); @@ -333,8 +336,10 @@ void Page::ResetAligners() CalcAlignmentPitchPosFunctor calcAlignmentPitchPos(doc); this->Process(calcAlignmentPitchPos); - CalcLigatureNotePosFunctor calcLigatureNotePos(doc); - this->Process(calcLigatureNotePos); + if (IsMensuralType(doc->m_notationType)) { + CalcLigatureNotePosFunctor calcLigatureNotePos(doc); + this->Process(calcLigatureNotePos); + } CalcStemFunctor calcStem(doc); this->Process(calcStem); diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index fd69f35d01c..2d9226da362 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -1161,17 +1161,7 @@ FunctorCode PrepareLayerElementPartsFunctor::VisitNote(Note *note) } } - /************ dots ***********/ - - Dots *currentDots = vrv_cast(note->FindDescendantByType(DOTS, 1)); - - const bool shouldHaveDots = (note->GetDots() > 0); - if (shouldHaveDots && chord && (chord->GetDots() == note->GetDots())) { - LogWarning("Note '%s' with a @dots attribute with the same value as its chord parent", note->GetID().c_str()); - } - currentDots = this->ProcessDots(currentDots, note, shouldHaveDots); - - // We don't care about flags in mensural notes + // We don't care about flags or dots in mensural notes if (note->IsMensuralDur()) return FUNCTOR_CONTINUE; if (currentStem) { @@ -1182,6 +1172,16 @@ FunctorCode PrepareLayerElementPartsFunctor::VisitNote(Note *note) if (!chord) note->SetDrawingStem(currentStem); } + /************ dots ***********/ + + Dots *currentDots = vrv_cast(note->FindDescendantByType(DOTS, 1)); + + const bool shouldHaveDots = (note->GetDots() > 0); + if (shouldHaveDots && chord && (chord->GetDots() == note->GetDots())) { + LogWarning("Note '%s' with a @dots attribute with the same value as its chord parent", note->GetID().c_str()); + } + currentDots = this->ProcessDots(currentDots, note, shouldHaveDots); + /************ Prepare the drawing cue size ************/ PrepareCueSizeFunctor prepareCueSize; diff --git a/src/savefunctor.cpp b/src/savefunctor.cpp index 74172c5112e..082b9d9259a 100644 --- a/src/savefunctor.cpp +++ b/src/savefunctor.cpp @@ -96,12 +96,12 @@ FunctorCode SaveFunctor::VisitMdivEnd(Mdiv *mdiv) FunctorCode SaveFunctor::VisitMeasure(Measure *measure) { - return (measure->IsMeasuredMusic()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObject(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMeasureEnd(Measure *measure) { - return (measure->IsMeasuredMusic()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; + return (measure->IsMeasuredMusic() || measure->IsNeumeLine()) ? this->VisitObjectEnd(measure) : FUNCTOR_CONTINUE; } FunctorCode SaveFunctor::VisitMNum(MNum *mNum) diff --git a/src/staff.cpp b/src/staff.cpp index 12299d1ae39..9360300b579 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -75,6 +75,7 @@ void Staff::Reset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; + m_drawingRotation = 0.0; ClearLedgerLines(); } @@ -90,6 +91,13 @@ void Staff::CloneReset() m_timeSpanningElements.clear(); m_drawingStaffDef = NULL; m_drawingTuning = NULL; + m_drawingRotation = 0.0; +} + +int Staff::GetDrawingRotationOffsetFor(int x) +{ + int xDiff = x - this->GetDrawingX(); + return int(xDiff * tan(this->GetDrawingRotation() * M_PI / 180.0)); } void Staff::ClearLedgerLines() @@ -134,13 +142,6 @@ int Staff::GetDrawingX() const int Staff::GetDrawingY() const { - if (this->HasFacs()) { - const Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); - assert(DOC); - if (doc->IsFacs()) { - return FacsimileInterface::GetDrawingY(); - } - } if (m_drawingFacsY != VRV_UNSET) return m_drawingFacsY; @@ -172,7 +173,7 @@ void Staff::AdjustDrawingStaffSize() if (this->HasFacs()) { Doc *doc = vrv_cast(this->GetFirstAncestor(DOC)); assert(doc); - if (doc->IsFacs()) { + if (doc->IsFacs() || doc->IsNeumeLines()) { double rotate = this->GetDrawingRotate(); Zone *zone = this->GetZone(); assert(zone); diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 1769f1f138d..1cec345599f 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -496,8 +496,8 @@ void SvgDeviceContext::StartPage() = StringFormat("0 0 %d %d", this->GetWidth(), this->GetHeight()).c_str(); } else { - m_currentNode.append_attribute("viewBox") = StringFormat( - "0 0 %d %d", this->GetWidth() * DEFINITION_FACTOR, this->GetContentHeight() * DEFINITION_FACTOR) + m_currentNode.append_attribute("viewBox") = StringFormat("0 0 %d %d", + int(this->GetWidth() * this->GetViewBoxFactor()), int(this->GetContentHeight() * this->GetViewBoxFactor())) .c_str(); } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 2ae7b97c19b..d79ed929038 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1525,7 +1525,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) std::swap(height, width); } - double userScale = m_view.GetPPUFactor() * m_options->m_scale.GetValue() / 100; + double userScale = m_options->m_scale.GetValue() / 100.0; assert(userScale != 0.0); if (m_options->m_scaleToPageSize.GetValue()) { @@ -1537,6 +1537,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) deviceContext->SetUserScale(userScale, userScale); deviceContext->SetWidth(width); deviceContext->SetHeight(height); + deviceContext->SetViewBoxFactor(m_view.GetPPUFactor()); if (m_doc.IsFacs()) { deviceContext->SetWidth(m_doc.GetFacsimile()->GetMaxX()); diff --git a/src/view_element.cpp b/src/view_element.cpp index 0525ffcdff4..a2bba2eefea 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -113,7 +113,7 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay this->DrawCustos(dc, element, layer, staff, measure); } else if (element->Is(DIVLINE)) { - DrawDivLine(dc, element, layer, staff, measure); + this->DrawDivLine(dc, element, layer, staff, measure); } else if (element->Is(DOT)) { this->DrawDot(dc, element, layer, staff, measure); @@ -139,6 +139,9 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay else if (element->Is(LIGATURE)) { this->DrawLigature(dc, element, layer, staff, measure); } + else if (element->Is(LIQUESCENT)) { + this->DrawLiquescent(dc, element, layer, staff, measure); + } else if (element->Is(MENSUR)) { this->DrawMensur(dc, element, layer, staff, measure); } @@ -299,19 +302,6 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta y = (accid->GetPlace() == STAFFREL_below) ? y - extend.m_ascent - unit : y + extend.m_descent + unit; } - if (notationType == NOTATIONTYPE_neume) { - int rotateOffset = 0; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); - } - if (accid->HasFacs() && m_doc->IsFacs()) { - y = ToLogicalY(y); - } - y -= rotateOffset; - } - this->DrawSmuflString( dc, x, y, accidStr, HORIZONTALALIGNMENT_center, staff->m_drawingStaffSize, accid->GetDrawingCueSize(), true); @@ -653,14 +643,8 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf } int x, y; - if (m_doc->IsFacs() && clef->HasFacs()) { - y = ToLogicalY(staff->GetDrawingY()); - x = clef->GetDrawingX(); - } - else { - y = staff->GetDrawingY(); - x = element->GetDrawingX(); - } + y = staff->GetDrawingY(); + x = element->GetDrawingX(); char32_t sym = clef->GetClefGlyph(staff->m_drawingNotationType); @@ -671,10 +655,8 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (clef->HasLine()) { y -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - clef->GetLine()); - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - y -= int(xDiff * tan(deg * M_PI / 180.0)); + if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); } } else if (clef->GetShape() == CLEFSHAPE_perc) { @@ -696,19 +678,6 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); - if (m_doc->IsFacs() && element->HasFacs()) { - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - - FacsimileInterface *fi = element->GetFacsimileInterface(); - fi->GetZone()->SetUlx(x); - fi->GetZone()->SetUly(ToDeviceContextY(y)); - fi->GetZone()->SetLrx(x + noteWidth); - fi->GetZone()->SetLry(ToDeviceContextY(y - noteHeight)); - } - // Possibly draw enclosing brackets this->DrawClefEnclosing(dc, clef, staff, sym, x, y); @@ -760,17 +729,18 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St const int sym = custos->GetCustosGlyph(staff->m_drawingNotationType); int x, y; - if (custos->HasFacs() && m_doc->IsFacs()) { + // For neume notation we ignore the value set in CalcAlignmentPitchPosFunctor + if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { x = custos->GetDrawingX(); // Recalculate y from pitch to prevent visual/meaning mismatch Clef *clef = layer->GetClef(element); - y = ToLogicalY(staff->GetDrawingY()); + y = staff->GetDrawingY(); PitchInterface pi; // Neume notation uses C3 for C clef rather than C4. // Take this into account when determining location. // However this doesn't affect the value for F clef. pi.SetPname(PITCHNAME_c); - if ((staff->m_drawingNotationType == NOTATIONTYPE_neume) && (clef->GetShape() == CLEFSHAPE_C)) { + if (clef->GetShape() == CLEFSHAPE_C) { pi.SetOct(3); } else { @@ -789,25 +759,15 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); } - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - y -= int(xDiff * tan(deg * M_PI / 180.0)); + if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); + } + else if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); - if (m_doc->IsFacs() && element->HasFacs()) { - const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / 2); - const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / 1.4); - - FacsimileInterface *fi = element->GetFacsimileInterface(); - fi->GetZone()->SetUlx(x); - fi->GetZone()->SetUly(ToDeviceContextY(y)); - fi->GetZone()->SetLrx(x + noteWidth); - fi->GetZone()->SetLry(ToDeviceContextY(y - noteHeight)); - } - /************ Draw children (accidentals, etc) ************/ // Drawing the children should be done before ending the graphic. Otherwise the SVG tree will not match the MEI one this->DrawLayerChildren(dc, custos, layer, staff, measure); @@ -1460,9 +1420,6 @@ void View::DrawNote(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (note->IsMensuralDur()) { this->DrawMensuralNote(dc, element, layer, staff, measure); - if (note->FindDescendantByType(DOTS)) { - this->DrawLayerChildren(dc, note, layer, staff, measure); - } return; } if (note->IsTabGrpNote()) { @@ -1771,14 +1728,12 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff Syl *syl = vrv_cast(element); assert(syl); - bool isNeume = (staff->m_drawingNotationType == NOTATIONTYPE_neume); - - if (!syl->GetStart() && !isNeume) { + if (!syl->GetStart() && !(staff->m_drawingNotationType == NOTATIONTYPE_neume)) { LogWarning("Parent note for was not found"); return; } - if (!m_doc->IsFacs()) { + if (m_doc->IsFacs()) { syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); } @@ -1805,7 +1760,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff TextDrawingParams params; params.m_x = syl->GetDrawingX(); params.m_y = syl->GetDrawingY(); - if (m_doc->IsFacs()) { + if (m_doc->IsFacs() || m_doc->IsNeumeLines()) { params.m_width = syl->GetDrawingWidth(); params.m_height = syl->GetDrawingHeight(); } diff --git a/src/view_neume.cpp b/src/view_neume.cpp index de32dbed592..b332c29a859 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -169,10 +169,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff struct drawingParams { wchar_t fontNo = SMUFL_E990_chantPunctum; wchar_t fontNoLiq[5] = {}; - double xOffset = 0; - double yOffset = 0; - double xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - double yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; + float xOffset = 0; + float yOffset = 0; }; std::vector params; params.push_back(drawingParams()); @@ -198,23 +196,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (nc->GetLigated() == BOOLEAN_true) { int pitchDifference = 0; bool isFirst; - // Check if this is the first or second part of a ligature - // Object *nextSibling = neume->GetChild(position + 1); - // if (nextSibling != NULL) { - // Nc *nextNc = dynamic_cast(nextSibling); - // assert(nextNc); - // if (nextNc->GetLigated() == BOOLEAN_true) { // first part of the ligature - // isFirst = true; - // pitchDifference = nextNc->PitchDifferenceTo(nc); - // params.at(0).yOffset = pitchDifference; - // } - // else { - // isFirst = false; - // } - // } - // else { - // isFirst = false; - // } int ligCount = neume->GetLigatureCount(position); if (ligCount % 2 == 0) { @@ -236,14 +217,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } } - // if (!isFirst) { // still need to get pitchDifference - // Nc *lastnc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); - // assert(lastnc); - // pitchDifference = nc->PitchDifferenceTo(lastnc); - // params.at(0).xOffset = -1; - // params.at(0).yOffset = -pitchDifference; - // } - // set the glyph switch (pitchDifference) { case -1: @@ -272,40 +245,19 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; } - else if (nc->GetCurve() == curvatureDirection_CURVE_c) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; - params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; - params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = -1.5; - params.at(0).yOffsetLiq[4] = -1.75; - } - else if (nc->GetCurve() == curvatureDirection_CURVE_a) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; - params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; - params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = 0.5; - params.at(0).yOffsetLiq[4] = 0.75; - } - const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); int noteY, noteX; int yValue; - if (nc->HasFacs() && m_doc->IsFacs()) { - noteY = ToLogicalY(staff->GetDrawingY()); + if (nc->HasFacs() && m_doc->IsNeumeLines()) { + noteY = staff->GetDrawingY(); noteX = nc->GetDrawingX(); params.at(0).xOffset = 0; } - else if (neume->HasFacs() && m_doc->IsFacs()) { - noteY = ToLogicalY(staff->GetDrawingY()); + else if (neume->HasFacs() && m_doc->IsNeumeLines()) { + noteY = staff->GetDrawingY(); noteX = neume->GetDrawingX() + position * noteWidth; } else { @@ -323,14 +275,9 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); } int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotateOffset; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = noteX - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); - } - else { - rotateOffset = 0; + int rotationOffset = 0; + if (staff->HasDrawingRotation()) { + rotationOffset = staff->GetDrawingRotationOffsetFor(noteX); } if (nc->HasLoc()) { @@ -343,31 +290,16 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff else if (clef->GetShape() == CLEFSHAPE_F) { pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); } - yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; + yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; } - for (auto it = params.begin(); it != params.end(); it++) { - if (nc->GetCurve() == curvatureDirection_CURVE_a || nc->GetCurve() == curvatureDirection_CURVE_c) { - for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { - DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, - it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); - } - } - else { + if (!nc->HasCurve()) { + for (auto it = params.begin(); it != params.end(); it++) { DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, yValue + it->yOffset * noteHeight, it->fontNo, staff->m_drawingStaffSize, false, true); } } - // adjust facsimile values of element based on where it is rendered if necessary - if (m_doc->IsFacs() && element->HasFacs()) { - FacsimileInterface *fi = element->GetFacsimileInterface(); - fi->GetZone()->SetUlx(noteX); - fi->GetZone()->SetUly(ToDeviceContextY(yValue)); - fi->GetZone()->SetLrx(noteX + noteWidth); - fi->GetZone()->SetLry(ToDeviceContextY(yValue - noteHeight)); - } - // Draw the children this->DrawLayerChildren(dc, nc, layer, staff, measure); @@ -457,9 +389,6 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S DivLine *divLine = dynamic_cast(element); assert(divLine); - // int x = divLine->GetDrawingX(); - // int y = divLine->GetDrawingY(); - dc->StartGraphic(element, "", element->GetID()); int sym = 0; @@ -475,30 +404,15 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S } int x, y; - if (m_doc->IsFacs() && (divLine->HasFacs())) { - x = divLine->GetDrawingX(); - y = ToLogicalY(staff->GetDrawingY()); - } - else { - x = element->GetDrawingX(); - y = element->GetDrawingY(); - y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - } + x = divLine->GetDrawingX(); + y = staff->GetDrawingY(); y -= (m_doc->GetDrawingUnit(staff->m_drawingStaffSize)) * 3; - int rotateOffset; - if (m_doc->IsFacs() && (staff->GetDrawingRotate() != 0)) { - double deg = staff->GetDrawingRotate(); - int xDiff = x - staff->GetDrawingX(); - rotateOffset = int(xDiff * tan(deg * M_PI / 180.0)); - } - else { - rotateOffset = 0; + if (staff->HasDrawingRotation()) { + y -= staff->GetDrawingRotationOffsetFor(x); } - y -= rotateOffset; - DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); dc->EndGraphic(element, this); diff --git a/src/view_page.cpp b/src/view_page.cpp index 51d8b78be5c..d150f693ed4 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1283,19 +1283,14 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys int j, x1, x2, y1, y2; - if (staff->HasFacs() && m_doc->IsFacs()) { - double d = staff->GetDrawingRotate(); - x1 = staff->GetDrawingX(); - x2 = x1 + staff->GetWidth(); - y1 = ToLogicalY(staff->GetDrawingY()); - staff->AdjustDrawingStaffSize(); - y2 = y1 - staff->GetWidth() * tan(d * M_PI / 180.0); + x1 = measure->GetDrawingX(); + x2 = x1 + measure->GetWidth(); + y1 = staff->GetDrawingY(); + if (!staff->HasDrawingRotation()) { + y2 = y1; } else { - x1 = measure->GetDrawingX(); - x2 = x1 + measure->GetWidth(); - y1 = staff->GetDrawingY(); - y2 = y1; + y2 = y1 - measure->GetWidth() * tan(staff->GetDrawingRotation() * M_PI / 180.0); } const int lineWidth = m_doc->GetDrawingStaffLineWidth(staff->m_drawingStaffSize); diff --git a/src/view_text.cpp b/src/view_text.cpp index ff281247add..f1f30653f9c 100644 --- a/src/view_text.cpp +++ b/src/view_text.cpp @@ -271,21 +271,21 @@ void View::DrawLyricString( std::u32string syl = U""; std::u32string lyricStr = str; - const int x = (params) ? params->m_x : VRV_UNSET; - const int y = (params) ? params->m_y : VRV_UNSET; + const int dcX = (params) ? ToDeviceContextX(params->m_x) : VRV_UNSET; + const int dcY = (params) ? ToDeviceContextY(params->m_y) : VRV_UNSET; const int width = (params) ? params->m_width : VRV_UNSET; const int height = (params) ? params->m_height : VRV_UNSET; if (m_doc->GetOptions()->m_lyricElision.GetValue() == ELISION_unicode) { std::replace(lyricStr.begin(), lyricStr.end(), U'_', UNICODE_UNDERTIE); - dc->DrawText(UTF32to8(lyricStr), lyricStr, x, y, width, height); + dc->DrawText(UTF32to8(lyricStr), lyricStr, dcX, dcY, width, height); } else { while (lyricStr.compare(syl) != 0) { wroteText = true; auto index = lyricStr.find_first_of(U"_"); syl = lyricStr.substr(0, index); - dc->DrawText(UTF32to8(syl), syl, x, y, width, height); + dc->DrawText(UTF32to8(syl), syl, dcX, dcY, width, height); // no _ if (index == std::string::npos) break; @@ -298,7 +298,7 @@ void View::DrawLyricString( bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(elision); vrvTxt.SetSmuflWithFallback(isFallbackNeeded); dc->SetFont(&vrvTxt); - dc->DrawText(UTF32to8(elision), elision, x, y, width, height); + dc->DrawText(UTF32to8(elision), elision, dcX, dcY, width, height); dc->ResetFont(); // next syllable @@ -310,7 +310,8 @@ void View::DrawLyricString( // This should only be called in facsimile mode where a zone is specified but there is // no text. This draws the bounds of the zone but leaves the space blank. if (!wroteText && params) { - dc->DrawText("", U"", params->m_x, params->m_y, params->m_width, params->m_height); + dc->DrawText( + "", U"", ToDeviceContextX(params->m_x), ToDeviceContextY(params->m_y), params->m_width, params->m_height); } } From 3a67568413efa9b67771074d92a3fa884baac4a8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 17 May 2024 07:59:25 +0200 Subject: [PATCH 161/383] Bring back changes lost in revert .github/workflows/ci_build.yml https://github.com/rism-digital/verovio/commit/35ff94091fbd0459e530a60909068ecd7a502dbf Verovio.xcodeproj/project.pbxproj https://github.com/rism-digital/verovio/commit/d4d4359cd4b61ab8f3e1f026f195f7bea5a7b403 https://github.com/rism-digital/verovio/commit/bc7bda5908a75f542da71e6bc4dca1dbed21f989 src/iomusxml.cpp src/page.cpp https://github.com/rism-digital/verovio/commit/e747a319cf5a4d5f9d8ee9d02fac27c7d6d57107 src/measure.cpp src/view_element.cpp https://github.com/rism-digital/verovio/commit/a7d07215d577bedaf23a8aceceb07d1b7432bb22 src/calcdotsfunctor.cpp src/preparedatafunctor.cpp src/view_element.cpp https://github.com/rism-digital/verovio/commit/dc4b31ece64927e3c852dc733cfaca5d19de53c0 --- .github/workflows/ci_build.yml | 2 +- Verovio.xcodeproj/project.pbxproj | 4 ++-- src/calcdotsfunctor.cpp | 4 ---- src/iomusxml.cpp | 3 +++ src/measure.cpp | 8 +++----- src/page.cpp | 6 ++---- src/preparedatafunctor.cpp | 22 +++++++++++----------- src/view_element.cpp | 5 ++++- 8 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index ca515fa61ad..1d75b6b86ff 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -78,7 +78,7 @@ jobs: compiler: g++ version: "10" - - os: ubuntu-20.04 + - os: ubuntu-22.04 compiler: g++ version: "11" diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 06ae5b7bc23..9c99a0a38a6 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -5172,7 +5172,7 @@ "$(inherited)", NO_HUMDRUM_SUPPORT, ); - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5184,7 +5184,7 @@ CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; GCC_PREPROCESSOR_DEFINITIONS = NO_HUMDRUM_SUPPORT; - MACOSX_DEPLOYMENT_TARGET = ""; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; diff --git a/src/calcdotsfunctor.cpp b/src/calcdotsfunctor.cpp index b979a50aa66..2be321aa7a9 100644 --- a/src/calcdotsfunctor.cpp +++ b/src/calcdotsfunctor.cpp @@ -60,10 +60,6 @@ FunctorCode CalcDotsFunctor::VisitChord(Chord *chord) FunctorCode CalcDotsFunctor::VisitNote(Note *note) { - // We currently have no dots object with mensural notes - if (note->IsMensuralDur()) { - return FUNCTOR_SIBLINGS; - } if (!note->IsVisible()) { return FUNCTOR_SIBLINGS; } diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index a7bb45ee05c..001017a6f9d 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -1835,6 +1835,9 @@ void MusicXmlInput::ReadMusicXmlAttributes( section->AddChild(scoreDef); } + else if (time && node.select_node("ancestor::part[(preceding-sibling::part)]")) { + m_meterUnit = time.child("beat-type").text().as_int(); + } pugi::xpath_node measureRepeat = node.select_node("measure-style/measure-repeat"); pugi::xpath_node measureSlash = node.select_node("measure-style/slash"); diff --git a/src/measure.cpp b/src/measure.cpp index d5e48b679f7..0c02901da1b 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -95,7 +95,7 @@ Measure::Measure(MeasureType measureMusic, int logMeasureNb) this->Reset(); - if (!measureMusic) this->SetRight(BARRENDITION_invis); + if (!this->IsMeasuredMusic()) this->SetRight(BARRENDITION_invis); } Measure::~Measure() @@ -149,10 +149,8 @@ void Measure::Reset() m_rightBarLine.SetForm(this->GetRight()); m_leftBarLine.SetForm(this->GetLeft()); - if (!m_measureType) { - m_drawingFacsX1 = VRV_UNSET; - m_drawingFacsX2 = VRV_UNSET; - } + m_drawingFacsX1 = VRV_UNSET; + m_drawingFacsX2 = VRV_UNSET; m_drawingEnding = NULL; m_hasAlignmentRefWithMultipleLayers = false; diff --git a/src/page.cpp b/src/page.cpp index 614b64180bc..6e4d3f30109 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -336,10 +336,8 @@ void Page::ResetAligners() CalcAlignmentPitchPosFunctor calcAlignmentPitchPos(doc); this->Process(calcAlignmentPitchPos); - if (IsMensuralType(doc->m_notationType)) { - CalcLigatureNotePosFunctor calcLigatureNotePos(doc); - this->Process(calcLigatureNotePos); - } + CalcLigatureNotePosFunctor calcLigatureNotePos(doc); + this->Process(calcLigatureNotePos); CalcStemFunctor calcStem(doc); this->Process(calcStem); diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index 2d9226da362..fd69f35d01c 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -1161,17 +1161,6 @@ FunctorCode PrepareLayerElementPartsFunctor::VisitNote(Note *note) } } - // We don't care about flags or dots in mensural notes - if (note->IsMensuralDur()) return FUNCTOR_CONTINUE; - - if (currentStem) { - const bool shouldHaveFlag = ((note->GetActualDur() > DUR_4) && !note->IsInBeam() && !note->GetAncestorFTrem() - && !note->IsChordTone() && !note->IsTabGrpNote()); - currentFlag = this->ProcessFlag(currentFlag, currentStem, shouldHaveFlag); - - if (!chord) note->SetDrawingStem(currentStem); - } - /************ dots ***********/ Dots *currentDots = vrv_cast(note->FindDescendantByType(DOTS, 1)); @@ -1182,6 +1171,17 @@ FunctorCode PrepareLayerElementPartsFunctor::VisitNote(Note *note) } currentDots = this->ProcessDots(currentDots, note, shouldHaveDots); + // We don't care about flags in mensural notes + if (note->IsMensuralDur()) return FUNCTOR_CONTINUE; + + if (currentStem) { + const bool shouldHaveFlag = ((note->GetActualDur() > DUR_4) && !note->IsInBeam() && !note->GetAncestorFTrem() + && !note->IsChordTone() && !note->IsTabGrpNote()); + currentFlag = this->ProcessFlag(currentFlag, currentStem, shouldHaveFlag); + + if (!chord) note->SetDrawingStem(currentStem); + } + /************ Prepare the drawing cue size ************/ PrepareCueSizeFunctor prepareCueSize; diff --git a/src/view_element.cpp b/src/view_element.cpp index a2bba2eefea..f734bc04b07 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1420,6 +1420,9 @@ void View::DrawNote(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (note->IsMensuralDur()) { this->DrawMensuralNote(dc, element, layer, staff, measure); + if (note->FindDescendantByType(DOTS)) { + this->DrawLayerChildren(dc, note, layer, staff, measure); + } return; } if (note->IsTabGrpNote()) { @@ -1733,7 +1736,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff return; } - if (m_doc->IsFacs()) { + if (!m_doc->IsFacs()) { syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); } From 27aaa985033fde42427a28a6e682f37dfd21e22c Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 17 May 2024 06:23:33 -0400 Subject: [PATCH 162/383] Implement additional content validation * graceGrp and beam should not be empty --- include/vrv/iopae.h | 11 +++++++++- src/iopae.cpp | 49 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index b02f0620adc..b007c4cb341 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -496,6 +496,8 @@ namespace pae { bool IsSpace(); /** Return true is the token has to be ignore during parsing */ bool IsVoid(); + /** Set the object as being inserted in the MEI tree */ + void SetInTree(); /* Helper to the a lowercase version of the Object classname (if any) */ std::string GetName(); @@ -504,6 +506,8 @@ namespace pae { char m_char; /** The Object to be added to the tree */ Object *m_object; + /** The object added to the tree */ + Object *m_treeObject; /** the input char preserved for debugging purposes */ char m_inputChar; /** the position in the original input string for debuggin purposes */ @@ -654,7 +658,6 @@ class PAEInput : public Input { /** * Some additional checked to be performed one the MEI tree has been build. - * Unimplemented */ bool CheckContentPostBuild(); @@ -663,6 +666,12 @@ class PAEInput : public Input { */ void RemoveContainerToken(Object *object); + /** + * Return the token corresponding to an object in the tree. + * Return NULL if not found. + */ + pae::Token *GetTokenForTreeObject(Object *object); + /** * @name Some logging methods specific to the PAE parser */ diff --git a/src/iopae.cpp b/src/iopae.cpp index 005f1ce0d3d..8ae1261b98f 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -2296,7 +2296,8 @@ enum { ERR_062_LIGATURE_NOTE_AFTER, ERR_063_LIGATURE_PITCH, ERR_064_LIGATURE_DURATION, - ERR_065_MREST_INVALID_MEASURE + ERR_065_MREST_INVALID_MEASURE, + ERR_066_EMPTY_CONTAINER }; // clang-format off @@ -2365,7 +2366,8 @@ const std::map PAEInput::s_errCodes{ { ERR_062_LIGATURE_NOTE_AFTER, "To indicate a ligature, a '+' must be followed by a note." }, { ERR_063_LIGATURE_PITCH, "A ligature cannot have two consecutive notes with the same pitch." }, { ERR_064_LIGATURE_DURATION, "The duration in a ligature cannot be shorter than a semibreve." }, - { ERR_065_MREST_INVALID_MEASURE, "A measure with a measure rest cannot include anything else." } + { ERR_065_MREST_INVALID_MEASURE, "A measure with a measure rest cannot include anything else." }, + { ERR_066_EMPTY_CONTAINER, "A grace group or a beam cannot be empty." } }; // clang-format on @@ -2419,6 +2421,7 @@ namespace pae { m_inputChar = c; m_position = position; m_object = object; + m_treeObject = NULL; m_isError = false; } @@ -2457,6 +2460,12 @@ namespace pae { return name; } + void Token::SetInTree() + { + m_treeObject = m_object; + m_object = NULL; + } + } // namespace pae PAEInput::PAEInput(Doc *doc) : Input(doc) @@ -2966,8 +2975,6 @@ bool PAEInput::Parse() if (success) success = this->CheckHierarchy(); - LogDebugTokens(); - if (m_pedanticMode && !success) { this->ClearTokenObjects(); return false; @@ -3046,7 +3053,7 @@ bool PAEInput::Parse() if (token.Is(MEASURE)) { currentMeasure = vrv_cast(token.m_object); assert(currentMeasure); - token.m_object = NULL; + token.SetInTree(); section->AddChild(currentMeasure); Staff *staff = new Staff(1); @@ -3092,7 +3099,7 @@ bool PAEInput::Parse() } } // Object are own by the scoreDef - token.m_object = NULL; + token.SetInTree(); continue; } else if (token.m_object->IsLayerElement()) { @@ -3100,7 +3107,7 @@ bool PAEInput::Parse() LayerElement *element = vrv_cast(token.m_object); assert(element); // The object is either a container end, or will be added to the layerElementContainers.back() - token.m_object = NULL; + token.SetInTree(); // For a container end, no object to add to the doc. if (token.m_char == pae::CONTAINER_END) { @@ -3144,12 +3151,14 @@ bool PAEInput::Parse() tie->SetTstamp2({ 0, tstamp2 }); } } - token.m_object = NULL; + token.SetInTree(); } } CheckContentPostBuild(); + LogDebugTokens(); + // We should have no object left, just in case they need to be delete. this->ClearTokenObjects(); @@ -4728,6 +4737,22 @@ bool PAEInput::CheckContentPostBuild() // * graceGrp should not be empty // * keySig / meterSig change more than once in a measure + ClassIdsComparison comparison({ BEAM, GRACEGRP }); + ClassIdsComparison noteOrRest({ NOTE, REST }); + ListOfObjects containers; + m_doc->FindAllDescendantsByComparison(&containers, &comparison); + for (auto &container : containers) { + ListOfObjects notesOrRests; + container->FindAllDescendantsByComparison(¬esOrRests, ¬eOrRest); + if ((int)notesOrRests.size() < 1) { + pae::Token *token = this->GetTokenForTreeObject(container); + if (token) { + LogPAE(ERR_066_EMPTY_CONTAINER, *token); + if (m_pedanticMode) return false; + } + } + } + return true; } @@ -4750,6 +4775,14 @@ void PAEInput::RemoveContainerToken(Object *object) } } +pae::Token *PAEInput::GetTokenForTreeObject(Object *object) +{ + for (pae::Token &token : m_pae) { + if (token.m_treeObject == object) return &token; + } + return NULL; +} + bool PAEInput::ParseKeySig(KeySig *keySig, const std::string &paeStr, pae::Token &token) { assert(keySig); From ae582fc8c1ed34f9a8cead7de936ed3d98ffa769 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 20 May 2024 09:48:29 -0400 Subject: [PATCH 163/383] Skip syl->SetDrawingYRel() for neume lines --- src/view_element.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index f734bc04b07..0f4f90636f2 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1736,7 +1736,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff return; } - if (!m_doc->IsFacs()) { + if (!m_doc->IsFacs() && !m_doc->IsNeumeLines()) { syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); } From 925d69acd16330b0ae84170079905aa787f44ec0 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 20 May 2024 10:23:23 -0400 Subject: [PATCH 164/383] Skip Measure SetRight() for neume line --- src/measure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/measure.cpp b/src/measure.cpp index 0c02901da1b..13347082ca6 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -95,7 +95,7 @@ Measure::Measure(MeasureType measureMusic, int logMeasureNb) this->Reset(); - if (!this->IsMeasuredMusic()) this->SetRight(BARRENDITION_invis); + if (!this->IsMeasuredMusic() && !this->IsNeumeLine()) this->SetRight(BARRENDITION_invis); } Measure::~Measure() From 2becb48187118facb43e699b0b704d4e1b594b75 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 20 May 2024 12:16:28 -0400 Subject: [PATCH 165/383] Fix staff removing and sorting Neon issue https://github.com/DDMAL/Neon/issues/1212 --- src/editortoolkit_neume.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 1882808daaf..a1891a25e2e 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -2060,13 +2060,10 @@ bool EditorToolkitNeume::SortStaves() Object *pbParent = pb->GetParent(); Object *milestoneEndParent = milestoneEnd->GetParent(); Object *sectionParent = section->GetParent(); - int pbIdx = pbParent->GetChildIndex(pb); - int milestoneEndIdx = milestoneEndParent->GetChildIndex(milestoneEnd); - int sectionIdx = sectionParent->GetChildIndex(section); - pb = pbParent->DetachChild(pbIdx); - milestoneEnd = milestoneEndParent->DetachChild(milestoneEndIdx); - section = sectionParent->DetachChild(sectionIdx); + pb = pbParent->DetachChild(pb->GetIdx()); + milestoneEnd = milestoneEndParent->DetachChild(milestoneEnd->GetIdx()); + section = sectionParent->DetachChild(section->GetIdx()); Object *firstSystem = page->GetFirst(SYSTEM); Object *lastSystem = page->GetLast(SYSTEM); @@ -2322,10 +2319,8 @@ bool EditorToolkitNeume::Remove(std::string elementId) assert(pb); assert(section); - int sectionIdx = system->GetChildIndex(section); - int pbIdx = system->GetChildIndex(pb); - section = system->DetachChild(sectionIdx); - pb = system->DetachChild(pbIdx); + section = system->DetachChild(section->GetIdx()); + pb = system->DetachChild(pb->GetIdx()); nextSystem->InsertChild(section, 0); nextSystem->InsertChild(pb, 1); From de33c730836b009292ea00a05812428f96700257 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 22 May 2024 12:04:45 -0400 Subject: [PATCH 166/383] Update octaves for affected custodes Refs: https://github.com/DDMAL/Neon/issues/1205 --- src/editortoolkit_neume.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index a1891a25e2e..39a192f97ec 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1588,19 +1588,26 @@ bool EditorToolkitNeume::DisplaceClefOctave(std::string elementId, std::string d clef->SetDisPlace(octaveDis > 0 ? STAFFREL_basic_above : STAFFREL_basic_below); } - // Set new octaves for affected neume components + // Set new octaves for affected neume components and custodes ClassIdComparison equalsClef(CLEF); Clef *nextClef = dynamic_cast(page->FindNextChild(&equalsClef, clef)); ClassIdComparison equalsNcs(NC); ListOfObjects ncs; page->FindAllDescendantsBetween(&ncs, &equalsNcs, clef, nextClef); - std::for_each(ncs.begin(), ncs.end(), [&](Object *ncObj) { Nc *nc = dynamic_cast(ncObj); nc->SetOct(nc->GetOct() + move); }); + ClassIdComparison equalsCustodes(CUSTOS); + ListOfObjects custodes; + page->FindAllDescendantsBetween(&custodes, &equalsCustodes, clef, nextClef); + std::for_each(custodes.begin(), custodes.end(), [&](Object *custosObj) { + Custos *custos = dynamic_cast(custosObj); + custos->SetOct(custos->GetOct() + move); + }); + m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); return true; From d64db413803ce7b19b4d39cdf0d0c0f5c25ba44a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 23 May 2024 12:27:09 -0500 Subject: [PATCH 167/383] Rename ZipFileReader::Load to LoadBytes --- include/vrv/filereader.h | 2 +- src/filereader.cpp | 6 +++--- src/toolkit.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/vrv/filereader.h b/include/vrv/filereader.h index ea17d92de86..80e631aa6bc 100644 --- a/include/vrv/filereader.h +++ b/include/vrv/filereader.h @@ -53,7 +53,7 @@ class ZipFileReader { /** * Load a vector into memory */ - bool Load(const std::vector &bytes); + bool LoadBytes(const std::vector &bytes); /** * Check if the archive contains the file diff --git a/src/filereader.cpp b/src/filereader.cpp index 2ae6dfc9659..c6ac259d1fa 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -54,7 +54,7 @@ bool ZipFileReader::Load(const std::string &filename) data = data.substr(data.find("base64,") + 7); } std::vector bytes = Base64Decode(data); - return this->Load(bytes); + return this->LoadBytes(bytes); #else std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); if (!fin.is_open()) { @@ -74,11 +74,11 @@ bool ZipFileReader::Load(const std::string &filename) while (fin.read((char *)&buffer, sizeof(unsigned char))) { bytes.push_back(buffer); } - return this->Load(bytes); + return this->LoadBytes(bytes); #endif } -bool ZipFileReader::Load(const std::vector &bytes) +bool ZipFileReader::LoadBytes(const std::vector &bytes) { this->Reset(); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index d79ed929038..3248a9faf76 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -436,7 +436,7 @@ bool Toolkit::LoadZipData(const std::vector &bytes) { #ifndef NO_MXL_SUPPORT ZipFileReader zipFileReader; - zipFileReader.Load(bytes); + zipFileReader.LoadBytes(bytes); const std::string metaInf = "META-INF/container.xml"; if (!zipFileReader.HasFile(metaInf)) { From aaceb38256724d8e26c3724f93bcc34cd5562fd5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sat, 25 May 2024 04:29:26 -0500 Subject: [PATCH 168/383] Add HEAPU8 to the wasm module * Fixed with @pe-ro at ORD at 4.28 AM waiting for coffee * Fixes 3687 --- emscripten/buildToolkit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emscripten/buildToolkit b/emscripten/buildToolkit index afde9dd2b76..7c9262bc169 100755 --- a/emscripten/buildToolkit +++ b/emscripten/buildToolkit @@ -230,7 +230,7 @@ $exports .= "'_malloc',"; $exports .= "'_free'"; $exports .= "]\""; -my $extra_exports = "-s EXPORTED_RUNTIME_METHODS='[\"cwrap\"]'"; +my $extra_exports = "-s EXPORTED_RUNTIME_METHODS='[\"cwrap\", \"HEAPU8\"]'"; my $modularize = $modularizeQ ? "-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME=\"'createVerovioModule'\"" : ""; From c3b4c2b226283a2f9f34a2e29110d8387539c97d Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:15:22 +0200 Subject: [PATCH 169/383] read liquescent color --- src/iomei.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/iomei.cpp b/src/iomei.cpp index 5d1e8ae1200..c869914302e 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -6672,6 +6672,8 @@ bool MEIInput::ReadLiquescent(Object *parent, pugi::xml_node liquescent) this->ReadLayerElement(liquescent, vrvLiquescent); this->ReadPositionInterface(liquescent, vrvLiquescent); + vrvLiquescent->ReadColor(liquescent); + parent->AddChild(vrvLiquescent); this->ReadUnsupportedAttr(liquescent, vrvLiquescent); return true; From 44c4fca18cd8e14048519a457bcc39cfde65e460 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:16:08 +0200 Subject: [PATCH 170/383] add quilisma class --- include/vrv/quilisma.h | 56 ++++++++++++++++++++++++++++++++++++++++++ src/quilisma.cpp | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 include/vrv/quilisma.h create mode 100644 src/quilisma.cpp diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h new file mode 100644 index 00000000000..4bafb203068 --- /dev/null +++ b/include/vrv/quilisma.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: quilisma.h +// Author: Klaus Rettinghaus +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_quilisma_H__ +#define __VRV_quilisma_H__ + +#include "atts_analytical.h" +#include "atts_shared.h" +#include "layerelement.h" +#include "pitchinterface.h" +#include "positioninterface.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// quilisma +//---------------------------------------------------------------------------- + +class Quilisma : public LayerElement, public PitchInterface, public PositionInterface, public AttColor { +public: + /** + * @name Constructors, destructors, and other standard methods + * Reset method resets all attribute classes + */ + ///@{ + Quilisma(); + virtual ~Quilisma(); + virtual Object *Clone() const { return new Quilisma(*this); } + virtual void Reset(); + virtual std::string GetClassName() const { return "quilisma"; } + ///@} + + /** + * @name Getter to interfaces + */ + ///@{ + virtual PitchInterface *GetPitchInterface() { return dynamic_cast(this); } + ///@} + + /** Override the method since alignment is required */ + virtual bool HasToBeAligned() const { return true; } + +private: + // +public: + // +private: +}; + +} // namespace vrv + +#endif diff --git a/src/quilisma.cpp b/src/quilisma.cpp new file mode 100644 index 00000000000..ede1337a734 --- /dev/null +++ b/src/quilisma.cpp @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: quilisma.cpp +// Author: Klaus Rettinghaus +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "quilisma.h" + +//---------------------------------------------------------------------------- + +#include + +//---------------------------------------------------------------------------- + +#include "doc.h" +#include "vrv.h" + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// Quilisma +//---------------------------------------------------------------------------- + +Quilisma::Quilisma() : LayerElement(QUILISMA, "quilisma-"), PitchInterface(), PositionInterface(), AttColor() +{ + RegisterInterface(PitchInterface::GetAttClasses(), PitchInterface::IsInterface()); + RegisterInterface(PositionInterface::GetAttClasses(), PositionInterface::IsInterface()); + RegisterAttClass(ATT_COLOR); + + Reset(); +} + +Quilisma::~Quilisma() {} + +void Quilisma::Reset() +{ + LayerElement::Reset(); + PitchInterface::Reset(); + PositionInterface::Reset(); + ResetColor(); +} + +} // namespace vrv From 43e5681ac44df39f7053f4c36250ba3f41489c4c Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:17:30 +0200 Subject: [PATCH 171/383] r/w quilisma --- include/vrv/iomei.h | 3 +++ src/iomei.cpp | 62 +++++++++++++++++++++++++++++++++++---------- src/nc.cpp | 4 +++ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/include/vrv/iomei.h b/include/vrv/iomei.h index 12f530bdacf..491f4c1977a 100644 --- a/include/vrv/iomei.h +++ b/include/vrv/iomei.h @@ -121,6 +121,7 @@ class Plica; class PlistInterface; class PositionInterface; class Proport; +class Quilisma; class Rdg; class Ref; class Reg; @@ -412,6 +413,7 @@ class MEIOutput : public Output { void WriteNote(pugi::xml_node currentNode, Note *note); void WritePlica(pugi::xml_node currentNode, Plica *plica); void WriteProport(pugi::xml_node currentNode, Proport *proport); + void WriteQuilisma(pugi::xml_node currentNode, Quilisma *quilisma); void WriteRest(pugi::xml_node currentNode, Rest *rest); void WriteSpace(pugi::xml_node currentNode, Space *space); void WriteStem(pugi::xml_node currentNode, Stem *stem); @@ -723,6 +725,7 @@ class MEIInput : public Input { bool ReadNote(Object *parent, pugi::xml_node note); bool ReadPlica(Object *parent, pugi::xml_node plica); bool ReadProport(Object *parent, pugi::xml_node proport); + bool ReadQuilisma(Object *parent, pugi::xml_node quilisma); bool ReadRest(Object *parent, pugi::xml_node rest); bool ReadSpace(Object *parent, pugi::xml_node space); bool ReadStem(Object *parent, pugi::xml_node stem); diff --git a/src/iomei.cpp b/src/iomei.cpp index c869914302e..1ac959de1f1 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -109,6 +109,7 @@ #include "pitchinflection.h" #include "plica.h" #include "proport.h" +#include "quilisma.h" #include "rdg.h" #include "ref.h" #include "reg.h" @@ -719,6 +720,10 @@ bool MEIOutput::WriteObjectInternal(Object *object, bool useCustomScoreDef) m_currentNode = m_currentNode.append_child("proport"); this->WriteProport(m_currentNode, vrv_cast(object)); } + else if (object->Is(QUILISMA)) { + m_currentNode = m_currentNode.append_child("quilisma"); + this->WriteQuilisma(m_currentNode, vrv_cast(object)); + } else if (object->Is(REST)) { m_currentNode = m_currentNode.append_child("rest"); this->WriteRest(m_currentNode, vrv_cast(object)); @@ -2748,20 +2753,6 @@ void MEIOutput::WriteNote(pugi::xml_node currentNode, Note *note) note->WriteVisibility(currentNode); } -void MEIOutput::WriteRest(pugi::xml_node currentNode, Rest *rest) -{ - assert(rest); - - this->WriteLayerElement(currentNode, rest); - this->WriteDurationInterface(currentNode, rest); - this->WritePositionInterface(currentNode, rest); - rest->WriteColor(currentNode); - rest->WriteCue(currentNode); - rest->WriteExtSymAuth(currentNode); - rest->WriteExtSymNames(currentNode); - rest->WriteRestVisMensural(currentNode); -} - void MEIOutput::WritePlica(pugi::xml_node currentNode, Plica *plica) { assert(plica); @@ -2777,6 +2768,29 @@ void MEIOutput::WriteProport(pugi::xml_node currentNode, Proport *proport) this->WriteLayerElement(currentNode, proport); } +void MEIOutput::WriteQuilisma(pugi::xml_node currentNode, Quilisma *quilisma) +{ + assert(quilisma); + + this->WriteLayerElement(currentNode, quilisma); + this->WritePitchInterface(currentNode, quilisma); + quilisma->WriteColor(currentNode); +} + +void MEIOutput::WriteRest(pugi::xml_node currentNode, Rest *rest) +{ + assert(rest); + + this->WriteLayerElement(currentNode, rest); + this->WriteDurationInterface(currentNode, rest); + this->WritePositionInterface(currentNode, rest); + rest->WriteColor(currentNode); + rest->WriteCue(currentNode); + rest->WriteExtSymAuth(currentNode); + rest->WriteExtSymNames(currentNode); + rest->WriteRestVisMensural(currentNode); +} + void MEIOutput::WriteSpace(pugi::xml_node currentNode, Space *space) { assert(space); @@ -3708,6 +3722,9 @@ bool MEIInput::IsAllowed(std::string element, Object *filterParent) if (element == "liquescent") { return true; } + else if (element == "quilisma") { + return true; + } else { return false; } @@ -6255,6 +6272,9 @@ bool MEIInput::ReadLayerChildren(Object *parent, pugi::xml_node parentNode, Obje else if (elementName == "note") { success = this->ReadNote(parent, xmlElement); } + else if (elementName == "quilisma") { + success = this->ReadQuilisma(parent, xmlElement); + } else if (elementName == "rest") { success = this->ReadRest(parent, xmlElement); } @@ -6958,6 +6978,20 @@ bool MEIInput::ReadProport(Object *parent, pugi::xml_node proport) return true; } +bool MEIInput::ReadQuilisma(Object *parent, pugi::xml_node quilisma) +{ + Quilisma *vrvQuilisma = new Quilisma(); + this->ReadLayerElement(quilisma, vrvQuilisma); + this->ReadPositionInterface(quilisma, vrvQuilisma); + + vrvQuilisma->ReadColor(quilisma); + + parent->AddChild(vrvQuilisma); + this->ReadUnsupportedAttr(quilisma, vrvQuilisma); + + return true; +} + bool MEIInput::ReadSpace(Object *parent, pugi::xml_node space) { Space *vrvSpace = new Space(); diff --git a/src/nc.cpp b/src/nc.cpp index 6afdfd1dfc3..a43977c65a5 100644 --- a/src/nc.cpp +++ b/src/nc.cpp @@ -18,6 +18,7 @@ #include "elementpart.h" #include "functor.h" #include "liquescent.h" +#include "quilisma.h" #include "staff.h" #include "vrv.h" @@ -90,6 +91,9 @@ bool Nc::IsSupportedChild(Object *child) if (child->Is(LIQUESCENT)) { assert(dynamic_cast(child)); } + else if (child->Is(QUILISMA)) { + assert(dynamic_cast(child)); + } else { return false; } From 94c0e224bfa8a953aeb979834c44aeba7e911296 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:18:12 +0200 Subject: [PATCH 172/383] add quilisma to xcode --- Verovio.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 9c99a0a38a6..801818c16b5 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -1376,6 +1376,7 @@ BD87768627CE8A1A005B97EA /* layerdef.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD87768227CE8A11005B97EA /* layerdef.cpp */; }; BD87768727CE8A21005B97EA /* layerdef.h in Headers */ = {isa = PBXBuildFile; fileRef = BD87768127CE89FA005B97EA /* layerdef.h */; }; BD87768827CE8A21005B97EA /* layerdef.h in Headers */ = {isa = PBXBuildFile; fileRef = BD87768127CE89FA005B97EA /* layerdef.h */; }; + BD96F7CD2C04A708001CFF6F /* quilisma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7CB2C04A708001CFF6F /* quilisma.cpp */; }; BDA81C21268B38760065B802 /* metersiggrp.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA81C20268B386C0065B802 /* metersiggrp.h */; }; BDA81C22268B38770065B802 /* metersiggrp.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA81C20268B386C0065B802 /* metersiggrp.h */; }; BDA81C24268B38A10065B802 /* metersiggrp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BDA81C23268B38A10065B802 /* metersiggrp.cpp */; }; @@ -2181,6 +2182,8 @@ BD2E4D992875881B00B04350 /* stem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stem.h; path = include/vrv/stem.h; sourceTree = ""; }; BD87768127CE89FA005B97EA /* layerdef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = layerdef.h; path = include/vrv/layerdef.h; sourceTree = ""; }; BD87768227CE8A11005B97EA /* layerdef.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = layerdef.cpp; path = src/layerdef.cpp; sourceTree = ""; }; + BD96F7CB2C04A708001CFF6F /* quilisma.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = quilisma.cpp; path = src/quilisma.cpp; sourceTree = ""; }; + BD96F7CE2C04A76F001CFF6F /* quilisma.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = quilisma.h; path = include/vrv/quilisma.h; sourceTree = ""; }; BDA81C20268B386C0065B802 /* metersiggrp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metersiggrp.h; path = include/vrv/metersiggrp.h; sourceTree = ""; }; BDA81C23268B38A10065B802 /* metersiggrp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = metersiggrp.cpp; path = src/metersiggrp.cpp; sourceTree = ""; }; BDC366C52576AF9300E4D826 /* grpsym.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = grpsym.cpp; path = src/grpsym.cpp; sourceTree = ""; }; @@ -2991,6 +2994,8 @@ 4D674B3E255F40AC008AEF4C /* plica.h */, 1579B3421B15033100B16F5C /* proport.cpp */, 1579B3411B15031D00B16F5C /* proport.h */, + BD96F7CB2C04A708001CFF6F /* quilisma.cpp */, + BD96F7CE2C04A76F001CFF6F /* quilisma.h */, 8F086ED1188539540037FD8E /* rest.cpp */, 8F59292818854BF800FE51AD /* rest.h */, 4DB3072E1AC9ED2500EE0982 /* space.cpp */, @@ -4399,6 +4404,7 @@ 4DEC4D8221C804E000D1D273 /* app.cpp in Sources */, 4DB787632022F0B700394520 /* jsonxx.cc in Sources */, 4DACCA132990F2E600B55913 /* att.cpp in Sources */, + BD96F7CD2C04A708001CFF6F /* quilisma.cpp in Sources */, 4DEEDE641E617C930087E8BC /* elementpart.cpp in Sources */, 4D95D4F61D71866200B2B856 /* controlelement.cpp in Sources */, BDC366C72576AF9300E4D826 /* grpsym.cpp in Sources */, From b48cdb2f1a211bb3d6eeaefd20986a49d29144a7 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:20:49 +0200 Subject: [PATCH 173/383] add Quilisma --- include/vrv/vrvdef.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index bda87157121..a52658ce513 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -240,6 +240,7 @@ enum ClassId : uint16_t { NEUME, PLICA, PROPORT, + QUILISMA, REST, SPACE, STEM, From e2f92d6114959c1bfc03cb52e8c0a46021f3a59d Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:28:25 +0200 Subject: [PATCH 174/383] add oriscus class --- Verovio.xcodeproj/project.pbxproj | 26 ++++++++++++++ include/vrv/oriscus.h | 56 +++++++++++++++++++++++++++++++ include/vrv/vrvdef.h | 1 + src/oriscus.cpp | 46 +++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 include/vrv/oriscus.h create mode 100644 src/oriscus.cpp diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 801818c16b5..d443aa757d3 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -1377,6 +1377,17 @@ BD87768727CE8A21005B97EA /* layerdef.h in Headers */ = {isa = PBXBuildFile; fileRef = BD87768127CE89FA005B97EA /* layerdef.h */; }; BD87768827CE8A21005B97EA /* layerdef.h in Headers */ = {isa = PBXBuildFile; fileRef = BD87768127CE89FA005B97EA /* layerdef.h */; }; BD96F7CD2C04A708001CFF6F /* quilisma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7CB2C04A708001CFF6F /* quilisma.cpp */; }; + BD96F7D12C04B297001CFF6F /* oriscus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7D02C04B297001CFF6F /* oriscus.cpp */; }; + BD96F7D22C04B2B2001CFF6F /* quilisma.h in Headers */ = {isa = PBXBuildFile; fileRef = BD96F7CE2C04A76F001CFF6F /* quilisma.h */; }; + BD96F7D32C04B2B3001CFF6F /* quilisma.h in Headers */ = {isa = PBXBuildFile; fileRef = BD96F7CE2C04A76F001CFF6F /* quilisma.h */; }; + BD96F7D42C04B2B6001CFF6F /* quilisma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7CB2C04A708001CFF6F /* quilisma.cpp */; }; + BD96F7D52C04B2B6001CFF6F /* quilisma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7CB2C04A708001CFF6F /* quilisma.cpp */; }; + BD96F7D62C04B2B7001CFF6F /* quilisma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7CB2C04A708001CFF6F /* quilisma.cpp */; }; + BD96F7D72C04B2EE001CFF6F /* oriscus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7D02C04B297001CFF6F /* oriscus.cpp */; }; + BD96F7D82C04B2EE001CFF6F /* oriscus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7D02C04B297001CFF6F /* oriscus.cpp */; }; + BD96F7D92C04B2EF001CFF6F /* oriscus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD96F7D02C04B297001CFF6F /* oriscus.cpp */; }; + BD96F7DA2C04B2F2001CFF6F /* oriscus.h in Headers */ = {isa = PBXBuildFile; fileRef = BD96F7CF2C04B26D001CFF6F /* oriscus.h */; }; + BD96F7DB2C04B2F2001CFF6F /* oriscus.h in Headers */ = {isa = PBXBuildFile; fileRef = BD96F7CF2C04B26D001CFF6F /* oriscus.h */; }; BDA81C21268B38760065B802 /* metersiggrp.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA81C20268B386C0065B802 /* metersiggrp.h */; }; BDA81C22268B38770065B802 /* metersiggrp.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA81C20268B386C0065B802 /* metersiggrp.h */; }; BDA81C24268B38A10065B802 /* metersiggrp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BDA81C23268B38A10065B802 /* metersiggrp.cpp */; }; @@ -2184,6 +2195,8 @@ BD87768227CE8A11005B97EA /* layerdef.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = layerdef.cpp; path = src/layerdef.cpp; sourceTree = ""; }; BD96F7CB2C04A708001CFF6F /* quilisma.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = quilisma.cpp; path = src/quilisma.cpp; sourceTree = ""; }; BD96F7CE2C04A76F001CFF6F /* quilisma.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = quilisma.h; path = include/vrv/quilisma.h; sourceTree = ""; }; + BD96F7CF2C04B26D001CFF6F /* oriscus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = oriscus.h; path = include/vrv/oriscus.h; sourceTree = ""; }; + BD96F7D02C04B297001CFF6F /* oriscus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = oriscus.cpp; path = src/oriscus.cpp; sourceTree = ""; }; BDA81C20268B386C0065B802 /* metersiggrp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metersiggrp.h; path = include/vrv/metersiggrp.h; sourceTree = ""; }; BDA81C23268B38A10065B802 /* metersiggrp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = metersiggrp.cpp; path = src/metersiggrp.cpp; sourceTree = ""; }; BDC366C52576AF9300E4D826 /* grpsym.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = grpsym.cpp; path = src/grpsym.cpp; sourceTree = ""; }; @@ -2990,6 +3003,8 @@ 4D766EF420ACAD41006875D8 /* neume.h */, 8F086ECC188539540037FD8E /* note.cpp */, 8F59292318854BF800FE51AD /* note.h */, + BD96F7D02C04B297001CFF6F /* oriscus.cpp */, + BD96F7CF2C04B26D001CFF6F /* oriscus.h */, 4D674B45255F40B7008AEF4C /* plica.cpp */, 4D674B3E255F40AC008AEF4C /* plica.h */, 1579B3421B15033100B16F5C /* proport.cpp */, @@ -3190,6 +3205,7 @@ 8F59293918854BF800FE51AD /* clef.h in Headers */, 4DA0EADB22BB77AF00A7EBEB /* editortoolkit_neume.h in Headers */, BD6E5C41290007CE0039B0F1 /* graphic.h in Headers */, + BD96F7DA2C04B2F2001CFF6F /* oriscus.h in Headers */, 4DB3D89A1F7C326800B5FC2B /* fb.h in Headers */, 4D1D733E1A1D08CD001E08F6 /* glyph.h in Headers */, E7908E9F298582090004C1F9 /* alignfunctor.h in Headers */, @@ -3312,6 +3328,7 @@ E7A1640A29AF344B0099BD6A /* adjustharmgrpsspacingfunctor.h in Headers */, E7770F8329D0D9F600A9BECF /* adjustslursfunctor.h in Headers */, 4DB3D8F11F83D1AA00B5FC2B /* fig.h in Headers */, + BD96F7D22C04B2B2001CFF6F /* quilisma.h in Headers */, 4DBDD67B2939E1D7009EC466 /* symboltable.h in Headers */, 4DACC9402990ED2600B55913 /* libmei.h in Headers */, 4D89F90C201771A700A4D336 /* num.h in Headers */, @@ -3504,6 +3521,7 @@ BB4C4B1422A932C8001F6AF0 /* section.h in Headers */, BB4C4B8022A932DF001F6AF0 /* fb.h in Headers */, BB4C4AF622A932BC001F6AF0 /* ref.h in Headers */, + BD96F7DB2C04B2F2001CFF6F /* oriscus.h in Headers */, BB4C4B6022A932D7001F6AF0 /* mrest.h in Headers */, 4D723AF525E8DB0B0062E0A2 /* zip_file.hpp in Headers */, E765675A28BBFBA400BC6490 /* functorinterface.h in Headers */, @@ -3615,6 +3633,7 @@ 4DFD83012A38399C00A3E20B /* repeatmark.h in Headers */, BB4C4B7622A932D7001F6AF0 /* syl.h in Headers */, E73E86262A069C640089DF74 /* transposefunctor.h in Headers */, + BD96F7D32C04B2B3001CFF6F /* quilisma.h in Headers */, BB4C4AEA22A932BC001F6AF0 /* del.h in Headers */, BB4C4B3422A932CF001F6AF0 /* tempo.h in Headers */, 4DACC9E12990F29A00B55913 /* attconverter.h in Headers */, @@ -3933,6 +3952,7 @@ 4D1694001E3A44F300569BF4 /* toolkit.cpp in Sources */, 4DACC99B2990F29A00B55913 /* atts_header.cpp in Sources */, 4DACC9A32990F29A00B55913 /* atts_cmnornaments.cpp in Sources */, + BD96F7D72C04B2EE001CFF6F /* oriscus.cpp in Sources */, 4DEC4D9F21C81E9400D1D273 /* orig.cpp in Sources */, E722106828F85981002CD6E9 /* findlayerelementsfunctor.cpp in Sources */, 4D1694011E3A44F300569BF4 /* MidiEvent.cpp in Sources */, @@ -4104,6 +4124,7 @@ 4D3C3F0D294B89AF009993E6 /* ornam.cpp in Sources */, 4DACC9972990F29A00B55913 /* atts_facsimile.cpp in Sources */, E7231E0629B64B33000A2BF3 /* adjustxoverflowfunctor.cpp in Sources */, + BD96F7D42C04B2B6001CFF6F /* quilisma.cpp in Sources */, 4D16943C1E3A44F300569BF4 /* view_beam.cpp in Sources */, E7BCFFBA281298620012513D /* resources.cpp in Sources */, 40D45EC3204EEAFE009C1EC9 /* instrdef.cpp in Sources */, @@ -4425,6 +4446,7 @@ 4D1EB6A12A2A40B400AF2F98 /* textlayoutelement.cpp in Sources */, 4D09D3ED1EA8AD8500A420E6 /* horizontalaligner.cpp in Sources */, 4DEC4DA221C81EB300D1D273 /* rdg.cpp in Sources */, + BD96F7D12C04B297001CFF6F /* oriscus.cpp in Sources */, 4D9A9C19199F561200028D93 /* verse.cpp in Sources */, E76046C328D496B400C36204 /* calcledgerlinesfunctor.cpp in Sources */, E76A9D4A29A74E4B0044682D /* adjustdotsfunctor.cpp in Sources */, @@ -4503,6 +4525,7 @@ 8F3DD36018854B390051330C /* view_beam.cpp in Sources */, 4DACC99C2990F29A00B55913 /* atts_header.cpp in Sources */, 4DACC9A42990F29A00B55913 /* atts_cmnornaments.cpp in Sources */, + BD96F7D82C04B2EE001CFF6F /* oriscus.cpp in Sources */, 8F3DD36218854B390051330C /* view_element.cpp in Sources */, 4DB3D8E11F83D15900B5FC2B /* chord.cpp in Sources */, 40C2E4252052A6FA0003625F /* sb.cpp in Sources */, @@ -4674,6 +4697,7 @@ 4DA0EAEC22BB77C300A7EBEB /* editortoolkit_neume.cpp in Sources */, 8F3DD31E18854AFB0051330C /* bboxdevicecontext.cpp in Sources */, 4DACC9982990F29A00B55913 /* atts_facsimile.cpp in Sources */, + BD96F7D52C04B2B6001CFF6F /* quilisma.cpp in Sources */, E7231E0729B64B33000A2BF3 /* adjustxoverflowfunctor.cpp in Sources */, 4DB3D8F31F83D1C600B5FC2B /* scoredefinterface.cpp in Sources */, 4D2461DD246BE2E8002BBCCD /* expansionmap.cpp in Sources */, @@ -4788,6 +4812,7 @@ BB4C4BBC22A932FC001F6AF0 /* MidiEventList.cpp in Sources */, 4DACC99D2990F29A00B55913 /* atts_header.cpp in Sources */, 4DACC9A52990F29A00B55913 /* atts_cmnornaments.cpp in Sources */, + BD96F7D92C04B2EF001CFF6F /* oriscus.cpp in Sources */, BB4C4B9122A932DF001F6AF0 /* textelement.cpp in Sources */, BB4C4B0922A932C3001F6AF0 /* pghead.cpp in Sources */, E722106728F856C4002CD6E9 /* findlayerelementsfunctor.cpp in Sources */, @@ -4959,6 +4984,7 @@ 4DACC9992990F29A00B55913 /* atts_facsimile.cpp in Sources */, BB4C4AB922A932A6001F6AF0 /* iopae.cpp in Sources */, E7231E0829B64B34000A2BF3 /* adjustxoverflowfunctor.cpp in Sources */, + BD96F7D62C04B2B7001CFF6F /* quilisma.cpp in Sources */, BB4C4ABB22A932B6001F6AF0 /* instrdef.cpp in Sources */, BB4C4AB722A932A6001F6AF0 /* iomusxml.cpp in Sources */, 4DACC9EF2990F29A00B55913 /* atts_shared.cpp in Sources */, diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h new file mode 100644 index 00000000000..5cc62f5d5d4 --- /dev/null +++ b/include/vrv/oriscus.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: oriscus.h +// Author: Klaus Rettinghaus +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_oriscus_H__ +#define __VRV_oriscus_H__ + +#include "atts_analytical.h" +#include "atts_shared.h" +#include "layerelement.h" +#include "pitchinterface.h" +#include "positioninterface.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// oriscus +//---------------------------------------------------------------------------- + +class Oriscus : public LayerElement, public PitchInterface, public PositionInterface, public AttColor { +public: + /** + * @name Constructors, destructors, and other standard methods + * Reset method resets all attribute classes + */ + ///@{ + Oriscus(); + virtual ~Oriscus(); + virtual Object *Clone() const { return new Oriscus(*this); } + virtual void Reset(); + virtual std::string GetClassName() const { return "oriscus"; } + ///@} + + /** + * @name Getter to interfaces + */ + ///@{ + virtual PitchInterface *GetPitchInterface() { return dynamic_cast(this); } + ///@} + + /** Override the method since alignment is required */ + virtual bool HasToBeAligned() const { return true; } + +private: + // +public: + // +private: +}; + +} // namespace vrv + +#endif diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index a52658ce513..a5c6019e334 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -238,6 +238,7 @@ enum ClassId : uint16_t { NC, NOTE, NEUME, + ORISCUS, PLICA, PROPORT, QUILISMA, diff --git a/src/oriscus.cpp b/src/oriscus.cpp new file mode 100644 index 00000000000..7d44ad7ff00 --- /dev/null +++ b/src/oriscus.cpp @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: oriscus.cpp +// Author: Klaus Rettinghaus +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "oriscus.h" + +//---------------------------------------------------------------------------- + +#include + +//---------------------------------------------------------------------------- + +#include "doc.h" +#include "vrv.h" + +//------------/Users/rettinghaus/git/verovio---------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// Oriscus +//---------------------------------------------------------------------------- + +Oriscus::Oriscus() : LayerElement(ORISCUS, "oriscus-"), PitchInterface(), PositionInterface(), AttColor() +{ + RegisterInterface(PitchInterface::GetAttClasses(), PitchInterface::IsInterface()); + RegisterInterface(PositionInterface::GetAttClasses(), PositionInterface::IsInterface()); + RegisterAttClass(ATT_COLOR); + + Reset(); +} + +Oriscus::~Oriscus() {} + +void Oriscus::Reset() +{ + LayerElement::Reset(); + PitchInterface::Reset(); + PositionInterface::Reset(); + ResetColor(); +} + +} // namespace vrv From 0dade7c7bebf355cee8c5c95ebcd44ccb8808929 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:34:30 +0200 Subject: [PATCH 175/383] write liquescent color --- include/vrv/iomei.h | 3 +++ src/iomei.cpp | 1 + src/nc.cpp | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/include/vrv/iomei.h b/include/vrv/iomei.h index 491f4c1977a..79caf6480f4 100644 --- a/include/vrv/iomei.h +++ b/include/vrv/iomei.h @@ -105,6 +105,7 @@ class Note; class Num; class Octave; class Orig; +class Oriscus; class Ornam; class Page; class PageElement; @@ -411,6 +412,7 @@ class MEIOutput : public Output { void WriteNc(pugi::xml_node currentNode, Nc *nc); void WriteNeume(pugi::xml_node currentNode, Neume *neume); void WriteNote(pugi::xml_node currentNode, Note *note); + void WriteOriscus(pugi::xml_node currentNode, Oriscus *oriscus); void WritePlica(pugi::xml_node currentNode, Plica *plica); void WriteProport(pugi::xml_node currentNode, Proport *proport); void WriteQuilisma(pugi::xml_node currentNode, Quilisma *quilisma); @@ -723,6 +725,7 @@ class MEIInput : public Input { bool ReadNc(Object *parent, pugi::xml_node nc); bool ReadNeume(Object *parent, pugi::xml_node note); bool ReadNote(Object *parent, pugi::xml_node note); + bool ReadOriscus(Object *parent, pugi::xml_node oriscus); bool ReadPlica(Object *parent, pugi::xml_node plica); bool ReadProport(Object *parent, pugi::xml_node proport); bool ReadQuilisma(Object *parent, pugi::xml_node quilisma); diff --git a/src/iomei.cpp b/src/iomei.cpp index 1ac959de1f1..0b2d374506e 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2585,6 +2585,7 @@ void MEIOutput::WriteLiquescent(pugi::xml_node currentNode, Liquescent *liquesce WriteLayerElement(currentNode, liquescent); WritePositionInterface(currentNode, liquescent); + liquescent->WriteColor(currentNode); } void MEIOutput::WriteMensur(pugi::xml_node currentNode, Mensur *mensur) diff --git a/src/nc.cpp b/src/nc.cpp index a43977c65a5..49b999e4302 100644 --- a/src/nc.cpp +++ b/src/nc.cpp @@ -18,6 +18,7 @@ #include "elementpart.h" #include "functor.h" #include "liquescent.h" +#include "oriscus.h" #include "quilisma.h" #include "staff.h" #include "vrv.h" @@ -91,6 +92,9 @@ bool Nc::IsSupportedChild(Object *child) if (child->Is(LIQUESCENT)) { assert(dynamic_cast(child)); } + else if (child->Is(ORISCUS)) { + assert(dynamic_cast(child)); + } else if (child->Is(QUILISMA)) { assert(dynamic_cast(child)); } From b7be7bab552d24e23555ece36a6aaccf0277a65f Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:40:15 +0200 Subject: [PATCH 176/383] r/w oriscus --- src/iomei.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/iomei.cpp b/src/iomei.cpp index 0b2d374506e..f99761a73b4 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -97,6 +97,7 @@ #include "num.h" #include "octave.h" #include "orig.h" +#include "oriscus.h" #include "ornam.h" #include "page.h" #include "pagemilestone.h" @@ -712,6 +713,10 @@ bool MEIOutput::WriteObjectInternal(Object *object, bool useCustomScoreDef) m_currentNode = m_currentNode.append_child("note"); this->WriteNote(m_currentNode, vrv_cast(object)); } + else if (object->Is(ORISCUS)) { + m_currentNode = m_currentNode.append_child("oriscus"); + this->WriteOriscus(m_currentNode, vrv_cast(object)); + } else if (object->Is(PLICA)) { m_currentNode = m_currentNode.append_child("plica"); this->WritePlica(m_currentNode, vrv_cast(object)); @@ -2754,6 +2759,15 @@ void MEIOutput::WriteNote(pugi::xml_node currentNode, Note *note) note->WriteVisibility(currentNode); } +void MEIOutput::WriteOriscus(pugi::xml_node currentNode, Oriscus *oriscus) +{ + assert(oriscus); + + this->WriteLayerElement(currentNode, oriscus); + this->WritePitchInterface(currentNode, oriscus); + oriscus->WriteColor(currentNode); +} + void MEIOutput::WritePlica(pugi::xml_node currentNode, Plica *plica) { assert(plica); @@ -3723,6 +3737,9 @@ bool MEIInput::IsAllowed(std::string element, Object *filterParent) if (element == "liquescent") { return true; } + else if (element == "oriscus") { + return true; + } else if (element == "quilisma") { return true; } @@ -6273,6 +6290,9 @@ bool MEIInput::ReadLayerChildren(Object *parent, pugi::xml_node parentNode, Obje else if (elementName == "note") { success = this->ReadNote(parent, xmlElement); } + else if (elementName == "oriscus") { + success = this->ReadOriscus(parent, xmlElement); + } else if (elementName == "quilisma") { success = this->ReadQuilisma(parent, xmlElement); } @@ -6979,6 +6999,20 @@ bool MEIInput::ReadProport(Object *parent, pugi::xml_node proport) return true; } +bool MEIInput::ReadOriscus(Object *parent, pugi::xml_node oriscus) +{ + Oriscus *vrvOriscus = new Oriscus(); + this->ReadLayerElement(oriscus, vrvOriscus); + this->ReadPositionInterface(oriscus, vrvOriscus); + + vrvOriscus->ReadColor(oriscus); + + parent->AddChild(vrvOriscus); + this->ReadUnsupportedAttr(oriscus, vrvOriscus); + + return true; +} + bool MEIInput::ReadQuilisma(Object *parent, pugi::xml_node quilisma) { Quilisma *vrvQuilisma = new Quilisma(); From e50860d680c0269dd5caeb0f8f2366d16613fb60 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 27 May 2024 14:46:31 +0200 Subject: [PATCH 177/383] formatting --- src/iomei.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/iomei.cpp b/src/iomei.cpp index f99761a73b4..21c07f794d0 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -7004,12 +7004,12 @@ bool MEIInput::ReadOriscus(Object *parent, pugi::xml_node oriscus) Oriscus *vrvOriscus = new Oriscus(); this->ReadLayerElement(oriscus, vrvOriscus); this->ReadPositionInterface(oriscus, vrvOriscus); - + vrvOriscus->ReadColor(oriscus); parent->AddChild(vrvOriscus); this->ReadUnsupportedAttr(oriscus, vrvOriscus); - + return true; } @@ -7018,12 +7018,12 @@ bool MEIInput::ReadQuilisma(Object *parent, pugi::xml_node quilisma) Quilisma *vrvQuilisma = new Quilisma(); this->ReadLayerElement(quilisma, vrvQuilisma); this->ReadPositionInterface(quilisma, vrvQuilisma); - + vrvQuilisma->ReadColor(quilisma); parent->AddChild(vrvQuilisma); this->ReadUnsupportedAttr(quilisma, vrvQuilisma); - + return true; } From dc4c8c2f528062a5d93dd70223f7df72209c1d48 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 27 May 2024 15:43:15 +0200 Subject: [PATCH 178/383] Forbid the use of repeated pattern in following measures. Closes #3694 --- src/iopae.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iopae.cpp b/src/iopae.cpp index 8ae1261b98f..a45efef5873 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -3494,8 +3494,8 @@ bool PAEInput::ConvertRepeatedFigure() --token; status = pae::FIGURE_REPEAT; } - // End of repetitions - this does not include the end of a measure - else if (!this->Was(*token, pae::MEASURE)) { + // End of repetitions + else { // Make sure we repeated the figure at least once (is this too pedantic?) if (status == pae::FIGURE_END) { LogPAE(ERR_010_REP_UNUSED, *figureToken); From a122fa541135347478a54c19cfd441a05e4d6c75 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 27 May 2024 17:03:18 +0200 Subject: [PATCH 179/383] Add end of line at the end of PAE data. Closes #3632 --- include/vrv/iopae.h | 1 + src/iopae.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index b007c4cb341..9af974e0468 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -102,6 +102,7 @@ class PAEOutput : public Output { */ ///@{ void WriteMdiv(Mdiv *mDiv); + void WriteMdivEnd(Mdiv *mDiv); void WriteScoreDef(ScoreDef *scoreDef); void WriteStaffDef(StaffDef *staffDef); void WriteMeasure(Measure *measure); diff --git a/src/iopae.cpp b/src/iopae.cpp index a45efef5873..44b8e94cb81 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -162,7 +162,10 @@ bool PAEOutput::WriteObject(Object *object) bool PAEOutput::WriteObjectEnd(Object *object) { - if (object->Is(MEASURE)) { + if (object->Is(MDIV)) { + this->WriteMdivEnd(vrv_cast(object)); + } + else if (object->Is(MEASURE)) { this->WriteMeasureEnd(vrv_cast(object)); } else if (object->Is(BEAM)) { @@ -182,6 +185,13 @@ void PAEOutput::WriteMdiv(Mdiv *mdiv) m_streamStringOutput << "@data:"; } +void PAEOutput::WriteMdivEnd(Mdiv *mdiv) +{ + assert(mdiv); + + m_streamStringOutput << "\n"; +} + void PAEOutput::WriteScoreDef(ScoreDef *scoreDef) {} void PAEOutput::WriteStaffDef(StaffDef *staffDef) From 7829847fa6cd16afcabdb8b4b916ae167d8c2e2f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 28 May 2024 09:39:51 +0200 Subject: [PATCH 180/383] Add a call to PrepareData before cast off mensural Fixes #3645 --- src/iohumdrum.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 5e7423a1e7b..43a152a9bc1 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -31610,6 +31610,7 @@ void HumdrumInput::finalizeDocument(Doc *doc) doc->ConvertMarkupDoc(); if (m_mens) { + doc->PrepareData(); doc->SetMensuralMusicOnly(true); doc->m_notationType = NOTATIONTYPE_mensural; doc->ConvertToCastOffMensuralDoc(true); From 11e68b017f5891635e00db97f4a415dc60e7215c Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sun, 2 Jun 2024 14:36:45 +0200 Subject: [PATCH 181/383] fix path and spelling in readme --- libmei/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libmei/README.md b/libmei/README.md index 75f39ad2a8b..e3b8c4d7fdf 100644 --- a/libmei/README.md +++ b/libmei/README.md @@ -11,9 +11,9 @@ This is a modified version that is used for generating C++ code for Verovio. 2. each attribute has a C++ type deduced from the MEI schema or given in a separated datatypes configuration file, 3. it uses the MEI page-based customization by default. - License ------- + LibMEI is released under the MIT license. Compilation & Usage @@ -32,14 +32,15 @@ This allows you to run Python with all the necessary dependencies for running th To generate the code, activate the poetry environment and, from the `libmei` directory, run: - $> python3 tools/parseschema2.py ./mei/develop/mei-verovio_compiled.odd + $> python3 tools/parseschema2.py ./mei/mei-verovio_compiled.odd Where the positional argument points to an ODD file for which you wish to generate code. Other options are: + * `-c`: A path to a YML config file. If not specified this will look for a file called "config.yml" - in your current working directory. (provided by default, but you can make your own if you - have custom requirements.) + in your current working directory. (provided by default, but you can make your own if you + have custom requirements.) Config file ----------- @@ -53,7 +54,7 @@ addons_dir: "../addons" # path to an optional addons directory elements: true|false # whether code for element handling should be generated namespace: "vrv" # the namespace to use in generated CPP code datatypes: "./datatypes.yml" # path to a datatypes mapping file -# path to an MEI basic ODD file. If not provided then the meibasic.h file will not +# path to an MEI Basic ODD file. If not provided then the meibasic.h file will not # be written. (This contains a map of the elements and attributes allowed in MEI Basic # which is then used by Verovio to ensure the "full" MEI output is stripped when the user # requests "Basic" output.) @@ -61,8 +62,8 @@ basic_odd: "../mei/develop/mei-basic_compiled.odd" ``` For the `basic_odd` option, if provided the generator will generate a map of notes and allowed -attributes within the MEI basic ODD file and write it to a file called `meibasic.h`. +attributes within the MEI Basic ODD file and write it to a file called `meibasic.h`. If the `addons_dir` is provided, the files in that directory will be copied to the output directory. The files will also have the namespace in the addons replaced with the value provided in the -`namespace` option. \ No newline at end of file +`namespace` option. From b3e229aee5438a04a5ddf528fa11c7c6642516d8 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sun, 2 Jun 2024 14:37:02 +0200 Subject: [PATCH 182/383] Update poetry.lock --- libmei/poetry.lock | 270 ++++++++++++++++++++++++--------------------- 1 file changed, 147 insertions(+), 123 deletions(-) diff --git a/libmei/poetry.lock b/libmei/poetry.lock index 659fbf9c64a..eb9bea4e236 100644 --- a/libmei/poetry.lock +++ b/libmei/poetry.lock @@ -1,146 +1,170 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "lxml" -version = "4.9.2" +version = "4.9.4" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ - {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, - {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, - {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, - {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, - {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, - {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, - {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, - {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, - {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, - {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, - {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, - {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, - {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, - {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, - {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, - {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, - {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, - {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, - {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, - {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, - {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, - {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, - {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, - {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, - {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, - {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, - {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, - {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, - {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, - {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, - {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, - {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, - {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, - {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, - {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, - {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, - {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, - {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, - {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, - {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, - {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, - {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, - {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, - {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, - {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, - {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, - {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, - {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, - {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, - {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, - {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, - {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, - {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, - {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, - {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, - {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, - {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, - {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, - {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, - {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, - {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, - {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, - {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, - {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, - {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, - {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, - {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, - {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, - {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, - {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, - {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, - {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, - {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, - {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, - {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, - {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, - {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, + {file = "lxml-4.9.4-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e214025e23db238805a600f1f37bf9f9a15413c7bf5f9d6ae194f84980c78722"}, + {file = "lxml-4.9.4-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec53a09aee61d45e7dbe7e91252ff0491b6b5fee3d85b2d45b173d8ab453efc1"}, + {file = "lxml-4.9.4-cp27-cp27m-win32.whl", hash = "sha256:7d1d6c9e74c70ddf524e3c09d9dc0522aba9370708c2cb58680ea40174800013"}, + {file = "lxml-4.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:cb53669442895763e61df5c995f0e8361b61662f26c1b04ee82899c2789c8f69"}, + {file = "lxml-4.9.4-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:647bfe88b1997d7ae8d45dabc7c868d8cb0c8412a6e730a7651050b8c7289cf2"}, + {file = "lxml-4.9.4-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4d973729ce04784906a19108054e1fd476bc85279a403ea1a72fdb051c76fa48"}, + {file = "lxml-4.9.4-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:056a17eaaf3da87a05523472ae84246f87ac2f29a53306466c22e60282e54ff8"}, + {file = "lxml-4.9.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:aaa5c173a26960fe67daa69aa93d6d6a1cd714a6eb13802d4e4bd1d24a530644"}, + {file = "lxml-4.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:647459b23594f370c1c01768edaa0ba0959afc39caeeb793b43158bb9bb6a663"}, + {file = "lxml-4.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bdd9abccd0927673cffe601d2c6cdad1c9321bf3437a2f507d6b037ef91ea307"}, + {file = "lxml-4.9.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:00e91573183ad273e242db5585b52670eddf92bacad095ce25c1e682da14ed91"}, + {file = "lxml-4.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a602ed9bd2c7d85bd58592c28e101bd9ff9c718fbde06545a70945ffd5d11868"}, + {file = "lxml-4.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:de362ac8bc962408ad8fae28f3967ce1a262b5d63ab8cefb42662566737f1dc7"}, + {file = "lxml-4.9.4-cp310-cp310-win32.whl", hash = "sha256:33714fcf5af4ff7e70a49731a7cc8fd9ce910b9ac194f66eaa18c3cc0a4c02be"}, + {file = "lxml-4.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:d3caa09e613ece43ac292fbed513a4bce170681a447d25ffcbc1b647d45a39c5"}, + {file = "lxml-4.9.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:359a8b09d712df27849e0bcb62c6a3404e780b274b0b7e4c39a88826d1926c28"}, + {file = "lxml-4.9.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:43498ea734ccdfb92e1886dfedaebeb81178a241d39a79d5351ba2b671bff2b2"}, + {file = "lxml-4.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4855161013dfb2b762e02b3f4d4a21cc7c6aec13c69e3bffbf5022b3e708dd97"}, + {file = "lxml-4.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c71b5b860c5215fdbaa56f715bc218e45a98477f816b46cfde4a84d25b13274e"}, + {file = "lxml-4.9.4-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9a2b5915c333e4364367140443b59f09feae42184459b913f0f41b9fed55794a"}, + {file = "lxml-4.9.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d82411dbf4d3127b6cde7da0f9373e37ad3a43e89ef374965465928f01c2b979"}, + {file = "lxml-4.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:273473d34462ae6e97c0f4e517bd1bf9588aa67a1d47d93f760a1282640e24ac"}, + {file = "lxml-4.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:389d2b2e543b27962990ab529ac6720c3dded588cc6d0f6557eec153305a3622"}, + {file = "lxml-4.9.4-cp311-cp311-win32.whl", hash = "sha256:8aecb5a7f6f7f8fe9cac0bcadd39efaca8bbf8d1bf242e9f175cbe4c925116c3"}, + {file = "lxml-4.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:c7721a3ef41591341388bb2265395ce522aba52f969d33dacd822da8f018aff8"}, + {file = "lxml-4.9.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:dbcb2dc07308453db428a95a4d03259bd8caea97d7f0776842299f2d00c72fc8"}, + {file = "lxml-4.9.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:01bf1df1db327e748dcb152d17389cf6d0a8c5d533ef9bab781e9d5037619229"}, + {file = "lxml-4.9.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e8f9f93a23634cfafbad6e46ad7d09e0f4a25a2400e4a64b1b7b7c0fbaa06d9d"}, + {file = "lxml-4.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3f3f00a9061605725df1816f5713d10cd94636347ed651abdbc75828df302b20"}, + {file = "lxml-4.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:953dd5481bd6252bd480d6ec431f61d7d87fdcbbb71b0d2bdcfc6ae00bb6fb10"}, + {file = "lxml-4.9.4-cp312-cp312-win32.whl", hash = "sha256:266f655d1baff9c47b52f529b5f6bec33f66042f65f7c56adde3fcf2ed62ae8b"}, + {file = "lxml-4.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:f1faee2a831fe249e1bae9cbc68d3cd8a30f7e37851deee4d7962b17c410dd56"}, + {file = "lxml-4.9.4-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23d891e5bdc12e2e506e7d225d6aa929e0a0368c9916c1fddefab88166e98b20"}, + {file = "lxml-4.9.4-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e96a1788f24d03e8d61679f9881a883ecdf9c445a38f9ae3f3f193ab6c591c66"}, + {file = "lxml-4.9.4-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:5557461f83bb7cc718bc9ee1f7156d50e31747e5b38d79cf40f79ab1447afd2d"}, + {file = "lxml-4.9.4-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:fdb325b7fba1e2c40b9b1db407f85642e32404131c08480dd652110fc908561b"}, + {file = "lxml-4.9.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d74d4a3c4b8f7a1f676cedf8e84bcc57705a6d7925e6daef7a1e54ae543a197"}, + {file = "lxml-4.9.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ac7674d1638df129d9cb4503d20ffc3922bd463c865ef3cb412f2c926108e9a4"}, + {file = "lxml-4.9.4-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:ddd92e18b783aeb86ad2132d84a4b795fc5ec612e3545c1b687e7747e66e2b53"}, + {file = "lxml-4.9.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bd9ac6e44f2db368ef8986f3989a4cad3de4cd55dbdda536e253000c801bcc7"}, + {file = "lxml-4.9.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bc354b1393dce46026ab13075f77b30e40b61b1a53e852e99d3cc5dd1af4bc85"}, + {file = "lxml-4.9.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f836f39678cb47c9541f04d8ed4545719dc31ad850bf1832d6b4171e30d65d23"}, + {file = "lxml-4.9.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:9c131447768ed7bc05a02553d939e7f0e807e533441901dd504e217b76307745"}, + {file = "lxml-4.9.4-cp36-cp36m-win32.whl", hash = "sha256:bafa65e3acae612a7799ada439bd202403414ebe23f52e5b17f6ffc2eb98c2be"}, + {file = "lxml-4.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6197c3f3c0b960ad033b9b7d611db11285bb461fc6b802c1dd50d04ad715c225"}, + {file = "lxml-4.9.4-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:7b378847a09d6bd46047f5f3599cdc64fcb4cc5a5a2dd0a2af610361fbe77b16"}, + {file = "lxml-4.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:1343df4e2e6e51182aad12162b23b0a4b3fd77f17527a78c53f0f23573663545"}, + {file = "lxml-4.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6dbdacf5752fbd78ccdb434698230c4f0f95df7dd956d5f205b5ed6911a1367c"}, + {file = "lxml-4.9.4-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:506becdf2ecaebaf7f7995f776394fcc8bd8a78022772de66677c84fb02dd33d"}, + {file = "lxml-4.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca8e44b5ba3edb682ea4e6185b49661fc22b230cf811b9c13963c9f982d1d964"}, + {file = "lxml-4.9.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9d9d5726474cbbef279fd709008f91a49c4f758bec9c062dfbba88eab00e3ff9"}, + {file = "lxml-4.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bbdd69e20fe2943b51e2841fc1e6a3c1de460d630f65bde12452d8c97209464d"}, + {file = "lxml-4.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8671622256a0859f5089cbe0ce4693c2af407bc053dcc99aadff7f5310b4aa02"}, + {file = "lxml-4.9.4-cp37-cp37m-win32.whl", hash = "sha256:dd4fda67f5faaef4f9ee5383435048ee3e11ad996901225ad7615bc92245bc8e"}, + {file = "lxml-4.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6bee9c2e501d835f91460b2c904bc359f8433e96799f5c2ff20feebd9bb1e590"}, + {file = "lxml-4.9.4-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:1f10f250430a4caf84115b1e0f23f3615566ca2369d1962f82bef40dd99cd81a"}, + {file = "lxml-4.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b505f2bbff50d261176e67be24e8909e54b5d9d08b12d4946344066d66b3e43"}, + {file = "lxml-4.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1449f9451cd53e0fd0a7ec2ff5ede4686add13ac7a7bfa6988ff6d75cff3ebe2"}, + {file = "lxml-4.9.4-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:4ece9cca4cd1c8ba889bfa67eae7f21d0d1a2e715b4d5045395113361e8c533d"}, + {file = "lxml-4.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59bb5979f9941c61e907ee571732219fa4774d5a18f3fa5ff2df963f5dfaa6bc"}, + {file = "lxml-4.9.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b1980dbcaad634fe78e710c8587383e6e3f61dbe146bcbfd13a9c8ab2d7b1192"}, + {file = "lxml-4.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9ae6c3363261021144121427b1552b29e7b59de9d6a75bf51e03bc072efb3c37"}, + {file = "lxml-4.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bcee502c649fa6351b44bb014b98c09cb00982a475a1912a9881ca28ab4f9cd9"}, + {file = "lxml-4.9.4-cp38-cp38-win32.whl", hash = "sha256:a8edae5253efa75c2fc79a90068fe540b197d1c7ab5803b800fccfe240eed33c"}, + {file = "lxml-4.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:701847a7aaefef121c5c0d855b2affa5f9bd45196ef00266724a80e439220e46"}, + {file = "lxml-4.9.4-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:f610d980e3fccf4394ab3806de6065682982f3d27c12d4ce3ee46a8183d64a6a"}, + {file = "lxml-4.9.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:aa9b5abd07f71b081a33115d9758ef6077924082055005808f68feccb27616bd"}, + {file = "lxml-4.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:365005e8b0718ea6d64b374423e870648ab47c3a905356ab6e5a5ff03962b9a9"}, + {file = "lxml-4.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:16b9ec51cc2feab009e800f2c6327338d6ee4e752c76e95a35c4465e80390ccd"}, + {file = "lxml-4.9.4-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:a905affe76f1802edcac554e3ccf68188bea16546071d7583fb1b693f9cf756b"}, + {file = "lxml-4.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd814847901df6e8de13ce69b84c31fc9b3fb591224d6762d0b256d510cbf382"}, + {file = "lxml-4.9.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91bbf398ac8bb7d65a5a52127407c05f75a18d7015a270fdd94bbcb04e65d573"}, + {file = "lxml-4.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f99768232f036b4776ce419d3244a04fe83784bce871b16d2c2e984c7fcea847"}, + {file = "lxml-4.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bb5bd6212eb0edfd1e8f254585290ea1dadc3687dd8fd5e2fd9a87c31915cdab"}, + {file = "lxml-4.9.4-cp39-cp39-win32.whl", hash = "sha256:88f7c383071981c74ec1998ba9b437659e4fd02a3c4a4d3efc16774eb108d0ec"}, + {file = "lxml-4.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:936e8880cc00f839aa4173f94466a8406a96ddce814651075f95837316369899"}, + {file = "lxml-4.9.4-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:f6c35b2f87c004270fa2e703b872fcc984d714d430b305145c39d53074e1ffe0"}, + {file = "lxml-4.9.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:606d445feeb0856c2b424405236a01c71af7c97e5fe42fbc778634faef2b47e4"}, + {file = "lxml-4.9.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1bdcbebd4e13446a14de4dd1825f1e778e099f17f79718b4aeaf2403624b0f7"}, + {file = "lxml-4.9.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0a08c89b23117049ba171bf51d2f9c5f3abf507d65d016d6e0fa2f37e18c0fc5"}, + {file = "lxml-4.9.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:232fd30903d3123be4c435fb5159938c6225ee8607b635a4d3fca847003134ba"}, + {file = "lxml-4.9.4-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:231142459d32779b209aa4b4d460b175cadd604fed856f25c1571a9d78114771"}, + {file = "lxml-4.9.4-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:520486f27f1d4ce9654154b4494cf9307b495527f3a2908ad4cb48e4f7ed7ef7"}, + {file = "lxml-4.9.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:562778586949be7e0d7435fcb24aca4810913771f845d99145a6cee64d5b67ca"}, + {file = "lxml-4.9.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a9e7c6d89c77bb2770c9491d988f26a4b161d05c8ca58f63fb1f1b6b9a74be45"}, + {file = "lxml-4.9.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:786d6b57026e7e04d184313c1359ac3d68002c33e4b1042ca58c362f1d09ff58"}, + {file = "lxml-4.9.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95ae6c5a196e2f239150aa4a479967351df7f44800c93e5a975ec726fef005e2"}, + {file = "lxml-4.9.4-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:9b556596c49fa1232b0fff4b0e69b9d4083a502e60e404b44341e2f8fb7187f5"}, + {file = "lxml-4.9.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:cc02c06e9e320869d7d1bd323df6dd4281e78ac2e7f8526835d3d48c69060683"}, + {file = "lxml-4.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:857d6565f9aa3464764c2cb6a2e3c2e75e1970e877c188f4aeae45954a314e0c"}, + {file = "lxml-4.9.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c42ae7e010d7d6bc51875d768110c10e8a59494855c3d4c348b068f5fb81fdcd"}, + {file = "lxml-4.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f10250bb190fb0742e3e1958dd5c100524c2cc5096c67c8da51233f7448dc137"}, + {file = "lxml-4.9.4.tar.gz", hash = "sha256:b1541e50b78e15fa06a2670157a1962ef06591d4c998b998047fff5e3236880e"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.7)"] +source = ["Cython (==0.29.37)"] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [metadata] From 36b5b7f6011f1d701081f9fce5c36a6015ecb451 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Mon, 29 Jan 2024 21:04:32 +0100 Subject: [PATCH 183/383] Run forward twice --- src/doc.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/doc.cpp b/src/doc.cpp index e6c4313ba15..5f36ed7264a 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -609,18 +609,16 @@ void Doc::PrepareData() // Try to match all spanning elements (slur, tie, etc) by processing backwards PrepareTimeSpanningFunctor prepareTimeSpanning; - prepareTimeSpanning.SetDirection(BACKWARD); this->Process(prepareTimeSpanning); prepareTimeSpanning.SetDataCollectionCompleted(); - // First we try backwards because normally the spanning elements are at the end of - // the measure. However, in some case, one (or both) end points will appear afterwards - // in the encoding. For these, the previous iteration will not have resolved the link and - // the spanning elements will remain in the timeSpanningElements array. We try again forwards - // but this time without filling the list (that is only will the remaining elements) + // First we try a forward pass which should collect most of the spanning elements. + // However, in some cases, one (or both) end points might appear a few measures + // before the spanning element in the encoding. For these, the previous iteration will not have resolved the link + // and the spanning elements will remain in the timeSpanningElements array. We try again forwards but this time + // without filling the list (that is only resolving remaining elements). const ListOfSpanningInterOwnerPairs &interfaceOwnerPairs = prepareTimeSpanning.GetInterfaceOwnerPairs(); if (!interfaceOwnerPairs.empty()) { - prepareTimeSpanning.SetDirection(FORWARD); this->Process(prepareTimeSpanning); } From dd66716074eaa64e9578b375aeec133d0b835271 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Tue, 30 Jan 2024 22:05:17 +0100 Subject: [PATCH 184/383] Collect all time spanning descendants on first measure visit --- include/vrv/preparedatafunctor.h | 7 +++- src/preparedatafunctor.cpp | 67 +++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/include/vrv/preparedatafunctor.h b/include/vrv/preparedatafunctor.h index 793d1e66c0e..1a185a7ba7c 100644 --- a/include/vrv/preparedatafunctor.h +++ b/include/vrv/preparedatafunctor.h @@ -493,18 +493,23 @@ class PrepareTimeSpanningFunctor : public Functor, public CollectAndProcess { FunctorCode VisitF(F *f) override; FunctorCode VisitFloatingObject(FloatingObject *floatingObject) override; FunctorCode VisitLayerElement(LayerElement *layerElement) override; + FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitMeasureEnd(Measure *measure) override; ///@} protected: // private: - // + // Delegates to the pseudo functor of the interface + FunctorCode CallPseudoFunctor(Object *timeSpanningObject); + public: // private: // The interface list that holds the current elements to match ListOfSpanningInterOwnerPairs m_timeSpanningInterfaces; + // Indicates whether we currently traverse a measure + bool m_insideMeasure; }; //---------------------------------------------------------------------------- diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index 98b7b07152e..2d9226da362 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -650,7 +650,10 @@ FunctorCode PrepareTimePointingFunctor::VisitMeasureEnd(Measure *measure) // PrepareTimeSpanningFunctor //---------------------------------------------------------------------------- -PrepareTimeSpanningFunctor::PrepareTimeSpanningFunctor() : Functor(), CollectAndProcess() {} +PrepareTimeSpanningFunctor::PrepareTimeSpanningFunctor() : Functor(), CollectAndProcess() +{ + m_insideMeasure = false; +} void PrepareTimeSpanningFunctor::InsertInterfaceOwnerPair(Object *owner, TimeSpanningInterface *interface) { @@ -659,19 +662,16 @@ void PrepareTimeSpanningFunctor::InsertInterfaceOwnerPair(Object *owner, TimeSpa FunctorCode PrepareTimeSpanningFunctor::VisitF(F *f) { - // Pass it to the pseudo functor of the interface - TimeSpanningInterface *interface = f->GetTimeSpanningInterface(); - assert(interface); - return interface->InterfacePrepareTimeSpanning(*this, f); + if (!m_insideMeasure) { + return this->CallPseudoFunctor(f); + } + return FUNCTOR_CONTINUE; } FunctorCode PrepareTimeSpanningFunctor::VisitFloatingObject(FloatingObject *floatingObject) { - // Pass it to the pseudo functor of the interface - if (floatingObject->HasInterface(INTERFACE_TIME_SPANNING)) { - TimeSpanningInterface *interface = floatingObject->GetTimeSpanningInterface(); - assert(interface); - return interface->InterfacePrepareTimeSpanning(*this, floatingObject); + if (!m_insideMeasure && floatingObject->HasInterface(INTERFACE_TIME_SPANNING)) { + return this->CallPseudoFunctor(floatingObject); } return FUNCTOR_CONTINUE; } @@ -699,28 +699,49 @@ FunctorCode PrepareTimeSpanningFunctor::VisitLayerElement(LayerElement *layerEle return FUNCTOR_CONTINUE; } -FunctorCode PrepareTimeSpanningFunctor::VisitMeasureEnd(Measure *measure) +FunctorCode PrepareTimeSpanningFunctor::VisitMeasure(Measure *measure) { - if (this->IsProcessingData()) { - return FUNCTOR_CONTINUE; + if (this->IsCollectingData()) { + ListOfObjects timeSpanningObjects; + InterfaceComparison ic(INTERFACE_TIME_SPANNING); + measure->FindAllDescendantsByComparison(&timeSpanningObjects, &ic); + for (Object *object : timeSpanningObjects) { + this->CallPseudoFunctor(object); + } } + m_insideMeasure = true; - ListOfSpanningInterOwnerPairs::iterator iter = m_timeSpanningInterfaces.begin(); - while (iter != m_timeSpanningInterfaces.end()) { - // At the end of the measure (going backward) we remove elements for which we do not need to match the end (for - // now). Eventually, we could consider them, for example if we want to display their spanning or for improved - // midi output - if (iter->second->GetClassId() == HARM) { - iter = m_timeSpanningInterfaces.erase(iter); - } - else { - ++iter; + return FUNCTOR_CONTINUE; +} + +FunctorCode PrepareTimeSpanningFunctor::VisitMeasureEnd(Measure *measure) +{ + if (this->IsCollectingData()) { + ListOfSpanningInterOwnerPairs::iterator iter = m_timeSpanningInterfaces.begin(); + while (iter != m_timeSpanningInterfaces.end()) { + // At the end of the measure we remove elements for which we do not need to match the end (for now). + // Eventually, we could consider them, for example if we want to display their spanning or for + // improved midi output + if (iter->second->GetClassId() == HARM) { + iter = m_timeSpanningInterfaces.erase(iter); + } + else { + ++iter; + } } } + m_insideMeasure = false; return FUNCTOR_CONTINUE; } +FunctorCode PrepareTimeSpanningFunctor::CallPseudoFunctor(Object *timeSpanningObject) +{ + TimeSpanningInterface *interface = timeSpanningObject->GetTimeSpanningInterface(); + assert(interface); + return interface->InterfacePrepareTimeSpanning(*this, timeSpanningObject); +} + //---------------------------------------------------------------------------- // PrepareTimestampsFunctor //---------------------------------------------------------------------------- From bd671e98cb3c5d6a812d0f1aef92fadbb03f26b9 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Wed, 8 Nov 2023 12:32:47 +0100 Subject: [PATCH 185/383] shorten trill extender --- src/view_control.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/view_control.cpp b/src/view_control.cpp index e491dda4abb..9a0065f0ce9 100644 --- a/src/view_control.cpp +++ b/src/view_control.cpp @@ -1063,7 +1063,10 @@ void View::DrawTrillExtension( } // Adjust the x2 for endid - if (!trill->GetEnd()->Is(TIMESTAMP_ATTR)) x2 -= trill->GetEnd()->GetDrawingRadius(m_doc); + if (!trill->GetEnd()->Is(TIMESTAMP_ATTR)) { + x2 -= trill->GetEnd()->GetDrawingRadius(m_doc); + } + x2 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); int length = x2 - x1; Point orig(x1, y); From f86bd2eeace5fb602b5eef0770654b2cd8bf5802 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 6 Feb 2024 12:58:56 +0100 Subject: [PATCH 186/383] Fix missing virtual qualifier for Option::IsArgumentRequire --- include/vrv/options.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/vrv/options.h b/include/vrv/options.h index 2cb3eff0844..42f59ce1b77 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -122,7 +122,7 @@ class Option { void SetShortOption(char shortOption, bool isCmdOnly); char GetShortOption() const { return m_shortOption; } bool IsCmdOnly() const { return m_isCmdOnly; } - bool IsArgumentRequired() const { return true; } + virtual bool IsArgumentRequired() const { return true; } /** * Return a JSON object for the option @@ -187,7 +187,7 @@ class OptionBool : public Option { bool GetDefault() const { return m_defaultValue; } bool SetValue(bool value); - bool IsArgumentRequired() const { return false; } + bool IsArgumentRequired() const override { return false; } private: // From c2b36dc3ed97d5e6ca8e50de57b82e13c24a47b0 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 7 Feb 2024 10:22:55 +0100 Subject: [PATCH 187/383] Remove citation.cff for now until Zenodo pulls contributor names [skip-ci] --- CITATION.cff | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff deleted file mode 100644 index 02a863d452f..00000000000 --- a/CITATION.cff +++ /dev/null @@ -1,29 +0,0 @@ -cff-version: 1.2.0 -title: Verovio -message: 'If you use this software, please cite it as below.' -type: software -repository-code: 'https://github.com/rism-digital/verovio' -url: 'https://www.verovio.org' -repository: 'https://github.com/rism-digital/verovio.org' -abstract: >- - Verovio is a fast, portable and lightweight open-source - library for engraving Music Encoding Initiative (MEI) - music scores into SVG. -license: LGPL-3.0 -date-released: '2023-12-15' - -preferred-citation: - type: conference-paper - authors: - - given-names: Laurent - family-names: Pugin - - given-names: Rodolfo - family-names: Zitellini - - given-names: Perry - family-names: Roland - collection-title: "Proceedings of the 15th International Society for Music Information Retrieval Conference (ISMIR 2014)" - month: 10 - start: 107 # First page number - end: 112 # Last page number - title: "Verovio: A Library for Engraving MEI Music Notation into SVG" - year: 2014 From d6fb1e204ac0dd31bb7a054c2b4e1727354bf967 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 22 Jan 2024 08:07:00 +0100 Subject: [PATCH 188/383] Add fontname support for clef and meter symbols. Add extra fonts support. --- include/vrv/clef.h | 1 + include/vrv/metersig.h | 3 ++ include/vrv/options.h | 1 + include/vrv/resources.h | 43 +++++++++++++---- include/vrv/svgdevicecontext.h | 28 +++++++++-- include/vrv/toolkit.h | 2 +- include/vrv/view.h | 3 ++ src/clef.cpp | 3 ++ src/iomei.cpp | 3 ++ src/metersig.cpp | 26 +++++++++- src/options.cpp | 4 ++ src/resources.cpp | 88 ++++++++++++++++++++++++---------- src/svgdevicecontext.cpp | 50 +++++++++++++++---- src/toolkit.cpp | 18 ++++--- src/view_element.cpp | 18 +++++-- src/view_graph.cpp | 19 ++++++++ 16 files changed, 251 insertions(+), 59 deletions(-) diff --git a/include/vrv/clef.h b/include/vrv/clef.h index 9dcd9e635b7..bc16c6fdc42 100644 --- a/include/vrv/clef.h +++ b/include/vrv/clef.h @@ -35,6 +35,7 @@ class Clef : public LayerElement, public AttOctave, public AttOctaveDisplacement, public AttStaffIdent, + public AttTypography, public AttVisibility { public: /** diff --git a/include/vrv/metersig.h b/include/vrv/metersig.h index df024fd5149..610458329b1 100644 --- a/include/vrv/metersig.h +++ b/include/vrv/metersig.h @@ -8,6 +8,7 @@ #ifndef __VRV_METERSIG_H__ #define __VRV_METERSIG_H__ +#include "atts_externalsymbols.h" #include "atts_shared.h" #include "atts_visual.h" #include "layerelement.h" @@ -25,8 +26,10 @@ class ScoreDefInterface; */ class MeterSig : public LayerElement, public AttEnclosingChars, + public AttExtSymNames, public AttMeterSigLog, public AttMeterSigVis, + public AttTypography, public AttVisibility { public: /** diff --git a/include/vrv/options.h b/include/vrv/options.h index 42f59ce1b77..3ba1c0a7827 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -689,6 +689,7 @@ class Options { OptionDbl m_extenderLineMinSpace; OptionDbl m_fingeringScale; OptionString m_font; + OptionArray m_addCustomFont; OptionDbl m_graceFactor; OptionBool m_graceRhythmAlign; OptionBool m_graceRightAlign; diff --git a/include/vrv/resources.h b/include/vrv/resources.h index 597ae6b6545..bd3e3594c8a 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -56,12 +56,13 @@ class Resources { */ ///@{ /** Init the SMufL music and text fonts */ - bool InitFonts(); + bool InitFonts(const std::vector &extraFonts, const std::string &defaultFont); /** Init the text font (bounding boxes and ASCII only) */ bool InitTextFont(const std::string &fontName, const StyleAttributes &style); /** Select a particular font */ - bool SetFont(const std::string &fontName); - std::string GetCurrentFontName() const { return m_fontName; } + bool SetCurrentFont(const std::string &fontName, bool allowLoading = false); + std::string GetCurrentFont() const { return m_currentFontName; } + bool IsFontLoaded(const std::string &fontName) const { return m_loadedFonts.find(fontName) != m_loadedFonts.end(); } ///@} /** @@ -89,6 +90,8 @@ class Resources { void SelectTextFont(data_FONTWEIGHT fontWeight, data_FONTSTYLE fontStyle) const; /** Returns the glyph (if exists) for the text font (bounding box and ASCII only) */ const Glyph *GetTextGlyph(char32_t code) const; + /** Returns true if the specified font is loaded and it contains the requested glyph */ + bool FontHasGlyphAvailable(const std::string &fontName, char32_t smuflCode) const; ///@} /** @@ -98,15 +101,35 @@ class Resources { static char32_t GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar); private: - bool LoadFont(const std::string &fontName, bool withFallback = true); + class LoadedFont { + public: + // LoadedFont() {}; + LoadedFont( + const std::string &name, const std::string &path, const GlyphTable &glyphTable, bool useFallback = true) + : m_name(name), m_path(path), m_glyphTable(glyphTable), m_useFallback(useFallback){}; + ~LoadedFont(){}; + const std::string GetName() const { return m_name; }; + const std::string GetPath() const { return m_path; }; + const GlyphTable &GetGlyphTable() const { return m_glyphTable; }; + bool useFallback() const { return m_useFallback; }; + + private: + std::string m_name; + /** The path to the resources directory (e.g., for the svg/ subdirectory with fonts as XML */ + std::string m_path; + /** The loaded SMuFL font */ + GlyphTable m_glyphTable; + /** If the font have a fallback when a glyph is not present **/ + const bool m_useFallback; + }; + + bool LoadFont(const std::string &fontName, bool withFallback = true, bool buildNameTable = false); + const GlyphTable &GetCurrentGlyphTable() const { return m_loadedFonts.at(m_currentFontName).GetGlyphTable(); }; -private: - /** The font name of the font that is currently loaded */ - std::string m_fontName; - /** The path to the resources directory (e.g., for the svg/ subdirectory with fonts as XML */ std::string m_path; - /** The loaded SMuFL font */ - GlyphTable m_fontGlyphTable; + std::string m_defaultFontName; + std::map m_loadedFonts; + std::string m_currentFontName; /** A text font used for bounding box calculations */ GlyphTextMap m_textFont; mutable StyleAttributes m_currentStyle; diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index 0e7b43e4afd..b69471e7e37 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -29,6 +29,7 @@ class Resources; namespace vrv { + //---------------------------------------------------------------------------- // SvgDeviceContext //---------------------------------------------------------------------------- @@ -328,9 +329,28 @@ class SvgDeviceContext : public DeviceContext { bool m_committed; // did we flushed the file? int m_originX, m_originY; - // holds the list of glyphs from the smufl font used so far - // they will be added at the end of the file as - std::set m_smuflGlyphs; + // Here we hold references to all different glyphs used so far, + // including any glyph for the same code but from different fonts. + // They will be added at the end of the file as . + // With multiple font support we need to keep track of: + // a) the path to the glyph (to check if is has been already added) + // b) the id assigned to glyphs on the (that is has been consumed by the already rendered elements) + // To keep things as similar as possible to previous versions we generate ids with as uuuu-ss (where uuuu is the Smulf code + // for the glyph and ss the per-session suffix) for most of the cases (single font usage). When the same glyph has been used + // from several fonts we use uuuu-n-ss where n indicates the collision count . Maybe we don't need to + // keep this pattern and can simplify this. + class GlyphRef { + public: + GlyphRef(const Glyph *glyph, int idx, const std::string &postfix); + const Glyph* GetGlyph() const { return m_glyph; }; + const std::string& GetRefId() const { return m_refId; }; + private: + const Glyph* m_glyph; + std::string m_refId; + }; + const std::string InsertGlyphRef(const Glyph *glyph); + std::map m_smuflGlyphs; + std::map m_glyphCodesCounter; // pugixml data pugi::xml_document m_svgDoc; @@ -358,7 +378,7 @@ class SvgDeviceContext : public DeviceContext { bool m_removeXlink; // indentation value (-1 for tabs) int m_indent; - // prefix to be added to font glyphs + // postfix to be added to font glyphs std::string m_glyphPostfixId; // embedding of the smufl text font option_SMUFLTEXTFONT m_smuflTextFont; diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 268175e3217..14a5499baeb 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -77,7 +77,7 @@ class Toolkit { std::string GetResourcePath() const; /** - * Set the resource path for the Toolkit instance. + * Set the resource path for the Toolkit instance and any extra fonts * * This method needs to be called if the constructor had initFont=false or if the resource path * needs to be changed. diff --git a/include/vrv/view.h b/include/vrv/view.h index 2c5473db68d..7f8c3750def 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -567,6 +567,9 @@ class View { DeviceContext *dc, int y1, SegmentedLine &line, int width, int dashLength = 0, int gapLength = 0); void DrawSmuflCode( DeviceContext *dc, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph = false); + int DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, + int staffSize, bool dimin, bool setBBGlyph = false); + void DrawThickBezierCurve( DeviceContext *dc, Point bezier[4], int thickness, int staffSize, int penWidth, int penStyle = AxSOLID); void DrawPartFilledRectangle(DeviceContext *dc, int x1, int y1, int x2, int y2, int fillSection); diff --git a/src/clef.cpp b/src/clef.cpp index d14a60822dc..3e73f0cc14d 100644 --- a/src/clef.cpp +++ b/src/clef.cpp @@ -41,6 +41,7 @@ Clef::Clef() , AttOctave() , AttOctaveDisplacement() , AttStaffIdent() + , AttTypography() , AttVisibility() { this->RegisterAttClass(ATT_CLEFLOG); @@ -53,6 +54,7 @@ Clef::Clef() this->RegisterAttClass(ATT_OCTAVE); this->RegisterAttClass(ATT_OCTAVEDISPLACEMENT); this->RegisterAttClass(ATT_STAFFIDENT); + this->RegisterAttClass(ATT_TYPOGRAPHY); this->RegisterAttClass(ATT_VISIBILITY); this->Reset(); @@ -73,6 +75,7 @@ void Clef::Reset() this->ResetOctave(); this->ResetOctaveDisplacement(); this->ResetStaffIdent(); + this->ResetTypography(); this->ResetVisibility(); } diff --git a/src/iomei.cpp b/src/iomei.cpp index 1cac00367ae..ad9afa02d92 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -6473,6 +6473,7 @@ bool MEIInput::ReadClef(Object *parent, pugi::xml_node clef) vrvClef->ReadOctave(clef); vrvClef->ReadOctaveDisplacement(clef); vrvClef->ReadStaffIdent(clef); + vrvClef->ReadTypography(clef); vrvClef->ReadVisibility(clef); parent->AddChild(vrvClef); @@ -6689,8 +6690,10 @@ bool MEIInput::ReadMeterSig(Object *parent, pugi::xml_node meterSig) } vrvMeterSig->ReadEnclosingChars(meterSig); + vrvMeterSig->ReadExtSymNames(meterSig); vrvMeterSig->ReadMeterSigLog(meterSig); vrvMeterSig->ReadMeterSigVis(meterSig); + vrvMeterSig->ReadTypography(meterSig); vrvMeterSig->ReadVisibility(meterSig); parent->AddChild(vrvMeterSig); diff --git a/src/metersig.cpp b/src/metersig.cpp index e27022f9d79..ccd59b25e93 100644 --- a/src/metersig.cpp +++ b/src/metersig.cpp @@ -16,6 +16,7 @@ //---------------------------------------------------------------------------- #include "functor.h" +#include "resources.h" #include "scoredefinterface.h" #include "smufl.h" #include "vrv.h" @@ -29,11 +30,19 @@ namespace vrv { static const ClassRegistrar s_factory("meterSig", METERSIG); MeterSig::MeterSig() - : LayerElement(METERSIG, "msig-"), AttEnclosingChars(), AttMeterSigLog(), AttMeterSigVis(), AttVisibility() + : LayerElement(METERSIG, "msig-") + , AttEnclosingChars() + , AttExtSymNames() + , AttMeterSigLog() + , AttMeterSigVis() + , AttTypography() + , AttVisibility() { this->RegisterAttClass(ATT_ENCLOSINGCHARS); + this->RegisterAttClass(ATT_EXTSYMNAMES); this->RegisterAttClass(ATT_METERSIGLOG); this->RegisterAttClass(ATT_METERSIGVIS); + this->RegisterAttClass(ATT_TYPOGRAPHY); this->RegisterAttClass(ATT_VISIBILITY); this->Reset(); @@ -45,8 +54,10 @@ void MeterSig::Reset() { LayerElement::Reset(); this->ResetEnclosingChars(); + this->ResetExtSymNames(); this->ResetMeterSigLog(); this->ResetMeterSigVis(); + this->ResetTypography(); this->ResetVisibility(); } @@ -97,6 +108,19 @@ int MeterSig::GetTotalCount() const char32_t MeterSig::GetSymbolGlyph() const { char32_t glyph = 0; + const Resources *resources = this->GetDocResources(); + + // If there is glyph.num, prioritize it + if (this->HasGlyphNum()) { + glyph = this->GetGlyphNum(); + if (NULL != resources->GetGlyph(glyph)) return glyph; + } + // If there is glyph.name (second priority) + else if (this->HasGlyphName()) { + glyph = resources->GetGlyphCode(this->GetGlyphName()); + if (NULL != resources->GetGlyph(glyph)) return glyph; + } + switch (this->GetSym()) { case METERSIGN_common: glyph = SMUFL_E08A_timeSigCommon; break; case METERSIGN_cut: glyph = SMUFL_E08B_timeSigCutCommon; break; diff --git a/src/options.cpp b/src/options.cpp index b634bf9924b..afd5e00d881 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1290,6 +1290,10 @@ Options::Options() m_font.Init("Leipzig"); this->Register(&m_font, "font", &m_generalLayout); + m_addCustomFont.SetInfo("Add custom font", "Add a custom music font"); + m_addCustomFont.Init(); + this->Register(&m_addCustomFont, "addCustomFont", &m_generalLayout); + m_graceFactor.SetInfo("Grace factor", "The grace size ratio numerator"); m_graceFactor.Init(0.75, 0.5, 1.0); this->Register(&m_graceFactor, "graceFactor", &m_generalLayout); diff --git a/src/resources.cpp b/src/resources.cpp index 114821fbe0a..1730742b5a0 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -50,19 +50,27 @@ Resources::Resources() m_currentStyle = k_defaultStyle; } -bool Resources::InitFonts() +bool Resources::InitFonts(const std::vector &extraFonts, const std::string &defaultFont) { - // We will need to rethink this for adding the option to add custom fonts - // Font Bravura first since it is expected to have always all symbols - if (!LoadFont("Bravura", false)) LogError("Bravura font could not be loaded."); - // The Leipzig as the default font - if (!LoadFont("Leipzig", false)) LogError("Leipzig font could not be loaded."); + // We need to rethink this for handling multiple fonts in an optimal way - if (m_fontGlyphTable.size() < SMUFL_COUNT) { - LogError("Expected %d default SMuFL glyphs but could load only %d.", SMUFL_COUNT, m_fontGlyphTable.size()); - return false; + // Font Bravura first. As it is expected to have always all symbols we build the code -> name table from it + if (!LoadFont("Bravura", false, true)) LogError("Bravura font could not be loaded."); + // Leipzig is our initial default font + if (!LoadFont("Leipzig", false)) LogError("Leipzig font could not be loaded."); + // options supplied fonts + for (const std::string &font : extraFonts) { + if (!LoadFont(font, true)) LogError("Option supplied font %s could not be loaded.", font.c_str()); + } + // and the default font provided in options, if it is not on: of the previous + if (!defaultFont.empty() && !IsFontLoaded(defaultFont)) { + if (!LoadFont(defaultFont, false)) + LogError("%s default font could not be loaded. Fallballing to Leipzig", defaultFont.c_str()); } + m_defaultFontName = IsFontLoaded(defaultFont) ? defaultFont : "Leipzig"; + m_currentFontName = m_defaultFontName; + struct TextFontInfo_type { const StyleAttributes m_style; const std::string m_fileName; @@ -86,19 +94,29 @@ bool Resources::InitFonts() return true; } -bool Resources::SetFont(const std::string &fontName) +bool Resources::SetCurrentFont(const std::string &fontName, bool allowLoading) { - return LoadFont(fontName); + if (IsFontLoaded(fontName)) { + m_currentFontName = fontName; + return true; + } + else if (allowLoading && LoadFont(fontName)) { + m_currentFontName = fontName; + return true; + } + else { + return false; + } } const Glyph *Resources::GetGlyph(char32_t smuflCode) const { - return m_fontGlyphTable.count(smuflCode) ? &m_fontGlyphTable.at(smuflCode) : NULL; + return GetCurrentGlyphTable().count(smuflCode) ? &GetCurrentGlyphTable().at(smuflCode) : NULL; } const Glyph *Resources::GetGlyph(const std::string &smuflName) const { - return m_glyphNameTable.count(smuflName) ? &m_fontGlyphTable.at(m_glyphNameTable.at(smuflName)) : NULL; + return m_glyphNameTable.count(smuflName) ? &GetCurrentGlyphTable().at(m_glyphNameTable.at(smuflName)) : NULL; } char32_t Resources::GetGlyphCode(const std::string &smuflName) const @@ -108,13 +126,31 @@ char32_t Resources::GetGlyphCode(const std::string &smuflName) const bool Resources::IsSmuflFallbackNeeded(const std::u32string &text) const { + if (!m_loadedFonts.at(m_currentFontName).useFallback()) { + return false; + } for (char32_t c : text) { const Glyph *glyph = this->GetGlyph(c); - if (glyph && glyph->GetFallback()) return true; + if (glyph == NULL) return true; } return false; } +bool Resources::FontHasGlyphAvailable(const std::string &fontName, char32_t smuflCode) const +{ + if (!IsFontLoaded(fontName)) { + return false; + } + + const GlyphTable &table = m_loadedFonts.at(fontName).GetGlyphTable(); + if (table.find(smuflCode) != table.end()) { + return true; + } + else { + return false; + } +} + void Resources::SelectTextFont(data_FONTWEIGHT fontWeight, data_FONTSTYLE fontStyle) const { if (fontWeight == FONTWEIGHT_NONE) { @@ -158,7 +194,7 @@ char32_t Resources::GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar) return smuflChar; } -bool Resources::LoadFont(const std::string &fontName, bool withFallback) +bool Resources::LoadFont(const std::string &fontName, bool withFallback, bool buildNameTable) { pugi::xml_document doc; const std::string filename = Resources::GetPath() + "/" + fontName + ".xml"; @@ -174,11 +210,7 @@ bool Resources::LoadFont(const std::string &fontName, bool withFallback) return false; } - if (withFallback) { - for (auto &glyph : m_fontGlyphTable) { - glyph.second.SetFallback(true); - } - } + GlyphTable glyphTable; const int unitsPerEm = atoi(root.attribute("units-per-em").value()); @@ -211,12 +243,20 @@ bool Resources::LoadFont(const std::string &fontName, bool withFallback) } const char32_t smuflCode = (char32_t)strtol(c_attribute.value(), NULL, 16); - glyph.SetFallback(false); - m_fontGlyphTable[smuflCode] = glyph; - m_glyphNameTable[n_attribute.value()] = smuflCode; + glyphTable[smuflCode] = glyph; + if (buildNameTable) { + m_glyphNameTable[n_attribute.value()] = smuflCode; + } + } + + if (buildNameTable && glyphTable.size() < SMUFL_COUNT) { + LogError("Expected %d default SMuFL glyphs but could load only %d.", SMUFL_COUNT, glyphTable.size()); + return false; } - m_fontName = fontName; + m_loadedFonts.insert(std::pair( + fontName, Resources::LoadedFont(fontName, m_path, glyphTable, withFallback))); + return true; } diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 4451b067d8c..8dc8f4184e6 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -84,6 +84,40 @@ bool SvgDeviceContext::CopyFileToStream(const std::string &filename, std::ostrea return true; } +SvgDeviceContext::GlyphRef::GlyphRef(const Glyph *glyph, int idx, const std::string &postfix) : m_glyph(glyph) +{ + if (idx == 0) { + m_refId = StringFormat("%s-%s", glyph->GetCodeStr().c_str(), postfix.c_str()); + } + else { + m_refId = StringFormat("%s-%d-%s", glyph->GetCodeStr().c_str(), idx, postfix.c_str()); + } +}; + +const std::string SvgDeviceContext::InsertGlyphRef(const Glyph *glyph) +{ + const std::string code = glyph->GetCodeStr(); + const std::string path = glyph->GetPath(); + + if (m_smuflGlyphs.find(path) != m_smuflGlyphs.end()) { + return m_smuflGlyphs.at(path).GetRefId(); + } + + int count; + if (m_glyphCodesCounter.find(code) == m_glyphCodesCounter.end()) { + count = 0; + } + else { + count = m_glyphCodesCounter[(code)]; + } + GlyphRef ref(glyph, count, m_glyphPostfixId); + const std::string id = ref.GetRefId(); + m_smuflGlyphs.insert(std::pair(path, ref)); + m_glyphCodesCounter[code] = count + 1; + + return id; +} + void SvgDeviceContext::IncludeTextFont(const std::string &fontname, const Resources *resources) { assert(resources); @@ -95,7 +129,7 @@ void SvgDeviceContext::IncludeTextFont(const std::string &fontname, const Resour std::ifstream cssFontFile(cssFontPath); if (!cssFontFile.is_open()) { LogWarning("The CSS font for '%s' could not be loaded and will not be embedded in the SVG", - resources->GetCurrentFontName().c_str()); + resources->GetCurrentFont().c_str()); } else { std::stringstream cssFontStream; @@ -156,7 +190,7 @@ void SvgDeviceContext::Commit(bool xml_declaration) const Resources *resources = this->GetResources(true); // include the selected font if (m_vrvTextFont && resources) { - this->IncludeTextFont(resources->GetCurrentFontName(), resources); + this->IncludeTextFont(resources->GetCurrentFont(), resources); } // include the Leipzig fallback font if (m_vrvTextFontFallback && resources) { @@ -171,15 +205,14 @@ void SvgDeviceContext::Commit(bool xml_declaration) pugi::xml_document sourceDoc; // for each needed glyph - for (const Glyph *smuflGlyph : m_smuflGlyphs) { + for (const std::pair entry : m_smuflGlyphs) { // load the XML file that contains it as a pugi::xml_document - std::ifstream source(smuflGlyph->GetPath()); + std::ifstream source(entry.first); sourceDoc.load(source); // copy all the nodes inside into the master document for (pugi::xml_node child = sourceDoc.first_child(); child; child = child.next_sibling()) { - std::string id = StringFormat("%s-%s", child.attribute("id").value(), m_glyphPostfixId.c_str()); - child.attribute("id").set_value(id.c_str()); + child.attribute("id").set_value(entry.second.GetRefId().c_str()); defs.append_copy(child); } } @@ -1020,12 +1053,11 @@ void SvgDeviceContext::DrawMusicText(const std::u32string &text, int x, int y, b } // Add the glyph to the array for the - m_smuflGlyphs.insert(glyph); + const std::string id = InsertGlyphRef(glyph); // Write the char in the SVG pugi::xml_node useChild = AddChild("use"); - useChild.append_attribute(hrefAttrib.c_str()) - = StringFormat("#%s-%s", glyph->GetCodeStr().c_str(), m_glyphPostfixId.c_str()).c_str(); + useChild.append_attribute(hrefAttrib.c_str()) = StringFormat("#%s", id.c_str()).c_str(); useChild.append_attribute("x") = x; useChild.append_attribute("y") = y; useChild.append_attribute("height") = StringFormat("%dpx", m_fontStack.top()->GetPointSize()).c_str(); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 4821605cf8b..cfe9e1bd5b3 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -72,13 +72,13 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; + m_options = m_doc.GetOptions(); + if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); - resources.InitFonts(); + resources.InitFonts(m_options->m_addCustomFont.GetValue(), m_options->m_font.GetValue()); } - m_options = m_doc.GetOptions(); - m_editorToolkit = NULL; #ifndef NO_RUNTIME @@ -117,13 +117,13 @@ bool Toolkit::SetResourcePath(const std::string &path) { Resources &resources = m_doc.GetResourcesForModification(); resources.SetPath(path); - return resources.InitFonts(); + return resources.InitFonts(m_options->m_addCustomFont.GetValue(), m_options->m_font.GetValue()); } bool Toolkit::SetFont(const std::string &fontName) { Resources &resources = m_doc.GetResourcesForModification(); - const bool ok = resources.SetFont(fontName); + const bool ok = resources.SetCurrentFont(fontName, true); if (!ok) LogWarning("Font '%s' could not be loaded", fontName.c_str()); return ok; } @@ -1129,7 +1129,13 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) m_options->Sync(); // Forcing font resource to be reset if the font is given in the options - if (json.has("font")) this->SetFont(m_options->m_font.GetValue()); + if (json.has("addCustomFont")) { + Resources &resources = m_doc.GetResourcesForModification(); + resources.InitFonts(m_options->m_addCustomFont.GetValue(), m_options->m_font.GetValue()); + } + else if (json.has("font")) { + this->SetFont(m_options->m_font.GetValue()); + } return true; } diff --git a/src/view_element.cpp b/src/view_element.cpp index 82ba0443207..3bd292d45af 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -687,7 +687,12 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf dc->StartGraphic(element, "", element->GetID()); - this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); + if (clef->HasFontname() && m_doc->GetResources().FontHasGlyphAvailable(clef->GetFontname(), sym)) { + this->DrawSmuflCodeWithCustomFont(dc, clef->GetFontname(), x, y, sym, staff->m_drawingStaffSize, false); + } + else { + this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); + } if (m_doc->IsFacs() && element->HasFacs()) { const int noteHeight @@ -1133,10 +1138,15 @@ void View::DrawMeterSig(DeviceContext *dc, MeterSig *meterSig, Staff *staff, int x += m_doc->GetGlyphWidth(enclosingFront, glyphSize, false); } - if (meterSig->HasSym()) { + if (meterSig->HasSym() || meterSig->HasGlyphNum() || meterSig->HasGlyphName()) { const char32_t code = meterSig->GetSymbolGlyph(); - this->DrawSmuflCode(dc, x, y, code, glyphSize, false); - x += m_doc->GetGlyphWidth(code, glyphSize, false); + if (meterSig->HasFontname() && m_doc->GetResources().FontHasGlyphAvailable(meterSig->GetFontname(), code)) { + x += this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, code, glyphSize, false); + } + else { + this->DrawSmuflCode(dc, x, y, code, glyphSize, false); + x += m_doc->GetGlyphWidth(code, glyphSize, false); + } } else if (meterSig->GetForm() == METERFORM_num) { x += this->DrawMeterSigFigures(dc, x, y, meterSig, 0, staff); diff --git a/src/view_graph.cpp b/src/view_graph.cpp index 76bf8ade512..2970293d56a 100644 --- a/src/view_graph.cpp +++ b/src/view_graph.cpp @@ -276,6 +276,25 @@ void View::DrawEnclosingBrackets(DeviceContext *dc, int x, int y, int height, in horizontalThickness, verticalThickness); } +int View::DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, + int staffSize, bool dimin, bool setBBGlyph) +{ + assert(!customFont.empty()); + + Resources &resources = m_doc->GetResourcesForModification(); + const std::string prevFont = resources.GetCurrentFont(); + + resources.SetCurrentFont(customFont); + + int drawnWidth = m_doc->GetGlyphWidth(code, staffSize, false); + + DrawSmuflCode(dc, x, y, code, staffSize, dimin, setBBGlyph); + + resources.SetCurrentFont(prevFont); + + return drawnWidth; +} + void View::DrawSmuflCode(DeviceContext *dc, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph) { assert(dc); From d90ca51edb4f14f4992c943249aff5fbf48655b0 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Fri, 26 Jan 2024 19:45:02 +0100 Subject: [PATCH 189/383] Fix formatting of the previous commit --- include/vrv/svgdevicecontext.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index b69471e7e37..cacd7353bbd 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -29,7 +29,6 @@ class Resources; namespace vrv { - //---------------------------------------------------------------------------- // SvgDeviceContext //---------------------------------------------------------------------------- @@ -335,18 +334,19 @@ class SvgDeviceContext : public DeviceContext { // With multiple font support we need to keep track of: // a) the path to the glyph (to check if is has been already added) // b) the id assigned to glyphs on the (that is has been consumed by the already rendered elements) - // To keep things as similar as possible to previous versions we generate ids with as uuuu-ss (where uuuu is the Smulf code - // for the glyph and ss the per-session suffix) for most of the cases (single font usage). When the same glyph has been used - // from several fonts we use uuuu-n-ss where n indicates the collision count . Maybe we don't need to - // keep this pattern and can simplify this. + // To keep things as similar as possible to previous versions we generate ids with as uuuu-ss (where uuuu is the + // Smulf code for the glyph and ss the per-session suffix) for most of the cases (single font usage). When the same + // glyph has been used from several fonts we use uuuu-n-ss where n indicates the collision count . Maybe we don't + // need to keep this pattern and can simplify this. class GlyphRef { - public: - GlyphRef(const Glyph *glyph, int idx, const std::string &postfix); - const Glyph* GetGlyph() const { return m_glyph; }; - const std::string& GetRefId() const { return m_refId; }; - private: - const Glyph* m_glyph; - std::string m_refId; + public: + GlyphRef(const Glyph *glyph, int idx, const std::string &postfix); + const Glyph *GetGlyph() const { return m_glyph; }; + const std::string &GetRefId() const { return m_refId; }; + + private: + const Glyph *m_glyph; + std::string m_refId; }; const std::string InsertGlyphRef(const Glyph *glyph); std::map m_smuflGlyphs; From c4a4f54a08b45e338b7f5a1fa52ebbb58d4812aa Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 30 Jan 2024 08:22:38 +0100 Subject: [PATCH 190/383] Rename option to --font-add-custom --- include/vrv/options.h | 2 +- src/options.cpp | 6 +++--- src/toolkit.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/vrv/options.h b/include/vrv/options.h index 3ba1c0a7827..547609fab66 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -689,7 +689,7 @@ class Options { OptionDbl m_extenderLineMinSpace; OptionDbl m_fingeringScale; OptionString m_font; - OptionArray m_addCustomFont; + OptionArray m_fontAddCustom; OptionDbl m_graceFactor; OptionBool m_graceRhythmAlign; OptionBool m_graceRightAlign; diff --git a/src/options.cpp b/src/options.cpp index afd5e00d881..7f9ea84a41f 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1290,9 +1290,9 @@ Options::Options() m_font.Init("Leipzig"); this->Register(&m_font, "font", &m_generalLayout); - m_addCustomFont.SetInfo("Add custom font", "Add a custom music font"); - m_addCustomFont.Init(); - this->Register(&m_addCustomFont, "addCustomFont", &m_generalLayout); + m_fontAddCustom.SetInfo("Add custom font", "Add a custom music font"); + m_fontAddCustom.Init(); + this->Register(&m_fontAddCustom, "addCustomFont", &m_generalLayout); m_graceFactor.SetInfo("Grace factor", "The grace size ratio numerator"); m_graceFactor.Init(0.75, 0.5, 1.0); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index cfe9e1bd5b3..e3ea9553a67 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -76,7 +76,7 @@ Toolkit::Toolkit(bool initFont) if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); - resources.InitFonts(m_options->m_addCustomFont.GetValue(), m_options->m_font.GetValue()); + resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); } m_editorToolkit = NULL; @@ -117,7 +117,7 @@ bool Toolkit::SetResourcePath(const std::string &path) { Resources &resources = m_doc.GetResourcesForModification(); resources.SetPath(path); - return resources.InitFonts(m_options->m_addCustomFont.GetValue(), m_options->m_font.GetValue()); + return resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); } bool Toolkit::SetFont(const std::string &fontName) @@ -1131,7 +1131,7 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) // Forcing font resource to be reset if the font is given in the options if (json.has("addCustomFont")) { Resources &resources = m_doc.GetResourcesForModification(); - resources.InitFonts(m_options->m_addCustomFont.GetValue(), m_options->m_font.GetValue()); + resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); } else if (json.has("font")) { this->SetFont(m_options->m_font.GetValue()); From e190f9b9a703a32422575398ea65bf36f2b93bfc Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 30 Jan 2024 09:29:42 +0100 Subject: [PATCH 191/383] Fix forgotten renaming --- src/options.cpp | 2 +- src/toolkit.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/options.cpp b/src/options.cpp index 7f9ea84a41f..5b85c016cf8 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1292,7 +1292,7 @@ Options::Options() m_fontAddCustom.SetInfo("Add custom font", "Add a custom music font"); m_fontAddCustom.Init(); - this->Register(&m_fontAddCustom, "addCustomFont", &m_generalLayout); + this->Register(&m_fontAddCustom, "fontAddCustom", &m_generalLayout); m_graceFactor.SetInfo("Grace factor", "The grace size ratio numerator"); m_graceFactor.Init(0.75, 0.5, 1.0); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index e3ea9553a67..f52653e77a7 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1129,7 +1129,7 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) m_options->Sync(); // Forcing font resource to be reset if the font is given in the options - if (json.has("addCustomFont")) { + if (json.has("fontAddCustom")) { Resources &resources = m_doc.GetResourcesForModification(); resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); } From 7e86f9e51be23a443bc7caabe40c104cb396acb4 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 07:46:59 +0100 Subject: [PATCH 192/383] Update Leipzig font --- data/Leipzig.css | 2 +- data/Leipzig.xml | 3 ++- data/Leipzig/E8F8.xml | 2 +- data/Leipzig/EB9F.xml | 1 + fonts/Leipzig/Leipzig.svg | 8 +++++--- fonts/Leipzig/Leipzig.ttf | Bin 125424 -> 127320 bytes fonts/Leipzig/Leipzig.woff2 | Bin 172349 -> 45096 bytes fonts/Leipzig/leipzig_metadata.json | 16 +++++++++++++--- 8 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 data/Leipzig/EB9F.xml diff --git a/data/Leipzig.css b/data/Leipzig.css index 1c5ceaffd16..3e434c0f8f2 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index e9ec64193a3..fe1e59903e5 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -776,7 +776,7 @@ - + @@ -792,4 +792,5 @@ + \ No newline at end of file diff --git a/data/Leipzig/E8F8.xml b/data/Leipzig/E8F8.xml index dd25ac4801a..d42573c956c 100644 --- a/data/Leipzig/E8F8.xml +++ b/data/Leipzig/E8F8.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/EB9F.xml b/data/Leipzig/EB9F.xml new file mode 100644 index 00000000000..bfbba5c5de9 --- /dev/null +++ b/data/Leipzig/EB9F.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fonts/Leipzig/Leipzig.svg b/fonts/Leipzig/Leipzig.svg index 5ea289cf111..9672c1539a9 100644 --- a/fonts/Leipzig/Leipzig.svg +++ b/fonts/Leipzig/Leipzig.svg @@ -2,11 +2,11 @@ -Created by FontForge 20220308 at Sun Jul 2 12:58:41 2023 +Created by FontForge 20220308 at Tue Jan 30 18:14:47 2024 By Laurent Pugin Created by Etienne Darbellay, Jean-Francois Marti, Laurent Pugin, and Klaus Rettinghaus. This font is licensed under the SIL Open Font License \(http://scripts.sil.org/OFL\). -Version 5.2.85 +Version 5.2.87 @@ -2402,7 +2402,7 @@ d="M0 375h14v-750h-14v750zM100 375h14v-750h-14v750z" /> +d="M120.146 498.265c0.224774 0 17.1897 -1.82497 17.1897 -14.4827c0 -13.3186 -15.0253 -42.3383 -63.3358 -117.782c-34 -53 -64 -102 -64 -102c-10 6 -3 2 -10 6c74.9234 172.649 99.0583 228.265 120.146 228.265z" /> + diff --git a/fonts/Leipzig/Leipzig.ttf b/fonts/Leipzig/Leipzig.ttf index 5527ea96b9fe5ceacf842b6f3438827cc624d6ec..c674abd7203e3b26399e22ea0ade9812ed358cc6 100644 GIT binary patch delta 7168 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 diff --git a/fonts/Leipzig/Leipzig.woff2 b/fonts/Leipzig/Leipzig.woff2 index e1cfe00239690d99eebd866ffb765c1309c13d08..3f3c9daf924516db943e42317a9cb3a736be4d84 100644 GIT binary patch literal 45096 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 OBjlO?)dlfgss97BHU2#S literal 172349 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

2qU+Ph^*nX^mYhu%L!ZAi&aMgJ<;!(p}3v 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 Date: Wed, 31 Jan 2024 09:08:47 +0100 Subject: [PATCH 193/383] Update xcode deployment target --- Verovio.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index d5ec055fae2..96d420fe72f 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -5072,7 +5072,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, ); - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = ""; @@ -5124,7 +5124,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, ./include/vrv/, ); - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = NO; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = ""; @@ -5140,7 +5140,7 @@ "$(inherited)", NO_HUMDRUM_SUPPORT, ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5152,7 +5152,7 @@ CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; GCC_PREPROCESSOR_DEFINITIONS = NO_HUMDRUM_SUPPORT; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; From 6d01eff54b5fb783f0c11dc34f1bf1b19143b7e3 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 09:09:32 +0100 Subject: [PATCH 194/383] Add --font-fallback and --font-load-all options --- include/vrv/options.h | 5 +++++ src/options.cpp | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/vrv/options.h b/include/vrv/options.h index 547609fab66..98a66d74f36 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -67,6 +67,8 @@ enum option_ELISION { ELISION_unicode = UNICODE_UNDERTIE }; +enum option_FONT_FALLBACK { FONT_FALLBACK_Leipzig = 0, FONT_FALLBACK_Bravura }; + enum option_FOOTER { FOOTER_none = 0, FOOTER_auto, FOOTER_encoded, FOOTER_always }; enum option_HEADER { HEADER_none = 0, HEADER_auto, HEADER_encoded }; @@ -140,6 +142,7 @@ class Option { static const std::map s_breaks; static const std::map s_condense; static const std::map s_elision; + static const std::map s_fontFallback; static const std::map s_footer; static const std::map s_header; static const std::map s_multiRestStyle; @@ -690,6 +693,8 @@ class Options { OptionDbl m_fingeringScale; OptionString m_font; OptionArray m_fontAddCustom; + OptionIntMap m_fontFallback; + OptionBool m_fontLoadAll; OptionDbl m_graceFactor; OptionBool m_graceRhythmAlign; OptionBool m_graceRightAlign; diff --git a/src/options.cpp b/src/options.cpp index 5b85c016cf8..4dc94e035ca 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -32,6 +32,9 @@ const std::map Option::s_condense const std::map Option::s_elision = { { ELISION_regular, "regular" }, { ELISION_narrow, "narrow" }, { ELISION_wide, "wide" }, { ELISION_unicode, "unicode" } }; +const std::map Option::s_fontFallback + = { { FONT_FALLBACK_Leipzig, "Leipzig" }, { FONT_FALLBACK_Bravura, "Bravura" } }; + const std::map Option::s_footer = { { FOOTER_none, "none" }, { FOOTER_auto, "auto" }, { FOOTER_encoded, "encoded" }, { FOOTER_always, "always" } }; @@ -1294,6 +1297,14 @@ Options::Options() m_fontAddCustom.Init(); this->Register(&m_fontAddCustom, "fontAddCustom", &m_generalLayout); + m_fontFallback.SetInfo("Font fallback", "The music font fallback for missing glyphs"); + m_fontFallback.Init(FONT_FALLBACK_Leipzig, &Option::s_fontFallback); + this->Register(&m_fontFallback, "fontFallback", &m_generalLayout); + + m_fontLoadAll.SetInfo("Font init all", "Load all music fonts"); + m_fontLoadAll.Init(false); + this->Register(&m_fontLoadAll, "fontLoadAll", &m_generalLayout); + m_graceFactor.SetInfo("Grace factor", "The grace size ratio numerator"); m_graceFactor.Init(0.75, 0.5, 1.0); this->Register(&m_graceFactor, "graceFactor", &m_generalLayout); From 365dd4fe8242ae02a4fbe3b4a661627c3b360339 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 09:22:29 +0100 Subject: [PATCH 195/383] Adjust Resources class with the new options --- include/vrv/resources.h | 37 +++++++++----- src/resources.cpp | 110 +++++++++++++++++++++++++++++++--------- src/toolkit.cpp | 28 +++++++--- 3 files changed, 134 insertions(+), 41 deletions(-) diff --git a/include/vrv/resources.h b/include/vrv/resources.h index bd3e3594c8a..6d1bb1244b8 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -56,9 +56,18 @@ class Resources { */ ///@{ /** Init the SMufL music and text fonts */ - bool InitFonts(const std::vector &extraFonts, const std::string &defaultFont); + bool InitFonts(); + /** Set the font to be used and loads it if necessary */ + bool SetFont(const std::string &fontName); + /** Add custom (external) fonts */ + bool AddCustom(const std::vector &extraFonts); + /** Load all music fonts available in the resource directory */ + bool LoadAll(); + /** Set the fallback font (Leipzig or Bravura) when some glyphs are missing in the current font */ + bool SetFallback(const std::string &fontName); /** Init the text font (bounding boxes and ASCII only) */ bool InitTextFont(const std::string &fontName, const StyleAttributes &style); + /** Select a particular font */ bool SetCurrentFont(const std::string &fontName, bool allowLoading = false); std::string GetCurrentFont() const { return m_currentFontName; } @@ -82,6 +91,11 @@ class Resources { */ bool IsSmuflFallbackNeeded(const std::u32string &text) const; + /** + * Check if the current font is the fallback font + */ + bool IsCurrentFontFallback() const; + /** * Text fonts */ @@ -103,33 +117,32 @@ class Resources { private: class LoadedFont { public: - // LoadedFont() {}; - LoadedFont( - const std::string &name, const std::string &path, const GlyphTable &glyphTable, bool useFallback = true) - : m_name(name), m_path(path), m_glyphTable(glyphTable), m_useFallback(useFallback){}; + LoadedFont(const std::string &name, bool isFallback) : m_name(name), m_isFallback(isFallback){}; ~LoadedFont(){}; const std::string GetName() const { return m_name; }; - const std::string GetPath() const { return m_path; }; const GlyphTable &GetGlyphTable() const { return m_glyphTable; }; - bool useFallback() const { return m_useFallback; }; + GlyphTable &GetGlyphTableForModification() { return m_glyphTable; }; + bool isFallback() const { return m_isFallback; }; private: std::string m_name; - /** The path to the resources directory (e.g., for the svg/ subdirectory with fonts as XML */ - std::string m_path; /** The loaded SMuFL font */ GlyphTable m_glyphTable; - /** If the font have a fallback when a glyph is not present **/ - const bool m_useFallback; + /** If the font needs to fallback when a glyph is not present **/ + const bool m_isFallback; }; - bool LoadFont(const std::string &fontName, bool withFallback = true, bool buildNameTable = false); + bool LoadFont(const std::string &fontName); + const GlyphTable &GetCurrentGlyphTable() const { return m_loadedFonts.at(m_currentFontName).GetGlyphTable(); }; + const GlyphTable &GetFallbackGlyphTable() const { return m_loadedFonts.at(m_fallbackFontName).GetGlyphTable(); }; std::string m_path; std::string m_defaultFontName; + std::string m_fallbackFontName; std::map m_loadedFonts; std::string m_currentFontName; + /** A text font used for bounding box calculations */ GlyphTextMap m_textFont; mutable StyleAttributes m_currentStyle; diff --git a/src/resources.cpp b/src/resources.cpp index 1730742b5a0..7f82efcb632 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -9,6 +9,7 @@ //---------------------------------------------------------------------------- +#include #include //---------------------------------------------------------------------------- @@ -21,6 +22,9 @@ #include "pugixml.hpp" +#define BRAVURA "Bravura" +#define LEIPZIG "Leipzig" + namespace vrv { //---------------------------------------------------------------------------- @@ -50,26 +54,18 @@ Resources::Resources() m_currentStyle = k_defaultStyle; } -bool Resources::InitFonts(const std::vector &extraFonts, const std::string &defaultFont) +bool Resources::InitFonts() { - // We need to rethink this for handling multiple fonts in an optimal way + m_loadedFonts.clear(); // Font Bravura first. As it is expected to have always all symbols we build the code -> name table from it - if (!LoadFont("Bravura", false, true)) LogError("Bravura font could not be loaded."); + if (!LoadFont(BRAVURA)) LogError("Bravura font could not be loaded."); // Leipzig is our initial default font - if (!LoadFont("Leipzig", false)) LogError("Leipzig font could not be loaded."); - // options supplied fonts - for (const std::string &font : extraFonts) { - if (!LoadFont(font, true)) LogError("Option supplied font %s could not be loaded.", font.c_str()); - } - // and the default font provided in options, if it is not on: of the previous - if (!defaultFont.empty() && !IsFontLoaded(defaultFont)) { - if (!LoadFont(defaultFont, false)) - LogError("%s default font could not be loaded. Fallballing to Leipzig", defaultFont.c_str()); - } + if (!LoadFont(LEIPZIG)) LogError("Leipzig font could not be loaded."); - m_defaultFontName = IsFontLoaded(defaultFont) ? defaultFont : "Leipzig"; + m_defaultFontName = LEIPZIG; m_currentFontName = m_defaultFontName; + m_fallbackFontName = m_defaultFontName; struct TextFontInfo_type { const StyleAttributes m_style; @@ -94,6 +90,57 @@ bool Resources::InitFonts(const std::vector &extraFonts, const std: return true; } +bool Resources::SetFont(const std::string &fontName) +{ + // and the default font provided in options, if it is not one of the previous + if (!fontName.empty() && !IsFontLoaded(fontName)) { + if (!LoadFont(fontName)) { + LogError("%s font could not be loaded.", fontName.c_str()); + return false; + } + } + + m_defaultFontName = IsFontLoaded(fontName) ? fontName : LEIPZIG; + m_currentFontName = m_defaultFontName; + + return true; +} + +bool Resources::AddCustom(const std::vector &extraFonts) +{ + bool success = true; + // options supplied fonts + for (const std::string &fontName : extraFonts) { + success = success && LoadFont(fontName); + if (!success) { + LogError("Option supplied font %s could not be loaded.", fontName.c_str()); + } + } + return success; +} + +bool Resources::LoadAll() +{ + bool success = true; + std::string path = Resources::GetPath() + "/"; + for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(path)) { + const std::filesystem::path path = entry.path(); + if (path.has_extension() && path.has_stem() && path.extension() == ".xml") { + const std::string fontName = path.stem(); + if (!IsFontLoaded(fontName)) { + success = success && LoadFont(fontName); + } + } + } + return success; +} + +bool Resources::SetFallback(const std::string &fontName) +{ + m_fallbackFontName = fontName; + return true; +} + bool Resources::SetCurrentFont(const std::string &fontName, bool allowLoading) { if (IsFontLoaded(fontName)) { @@ -111,12 +158,21 @@ bool Resources::SetCurrentFont(const std::string &fontName, bool allowLoading) const Glyph *Resources::GetGlyph(char32_t smuflCode) const { - return GetCurrentGlyphTable().count(smuflCode) ? &GetCurrentGlyphTable().at(smuflCode) : NULL; + if (GetCurrentGlyphTable().count(smuflCode)) { + return &GetCurrentGlyphTable().at(smuflCode); + } + else if (!this->IsCurrentFontFallback()) { + const GlyphTable &fallbackTable = this->GetFallbackGlyphTable(); + return (fallbackTable.count(smuflCode)) ? &fallbackTable.at(smuflCode) : NULL; + } + else { + return NULL; + } } const Glyph *Resources::GetGlyph(const std::string &smuflName) const { - return m_glyphNameTable.count(smuflName) ? &GetCurrentGlyphTable().at(m_glyphNameTable.at(smuflName)) : NULL; + return (this->GetGlyphCode(smuflName)) ? &GetCurrentGlyphTable().at(this->GetGlyphCode(smuflName)) : NULL; } char32_t Resources::GetGlyphCode(const std::string &smuflName) const @@ -126,7 +182,7 @@ char32_t Resources::GetGlyphCode(const std::string &smuflName) const bool Resources::IsSmuflFallbackNeeded(const std::u32string &text) const { - if (!m_loadedFonts.at(m_currentFontName).useFallback()) { + if (m_loadedFonts.at(m_currentFontName).isFallback()) { return false; } for (char32_t c : text) { @@ -136,6 +192,11 @@ bool Resources::IsSmuflFallbackNeeded(const std::u32string &text) const return false; } +bool Resources::IsCurrentFontFallback() const +{ + return (m_currentFontName == m_fallbackFontName); +} + bool Resources::FontHasGlyphAvailable(const std::string &fontName, char32_t smuflCode) const { if (!IsFontLoaded(fontName)) { @@ -194,7 +255,7 @@ char32_t Resources::GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar) return smuflChar; } -bool Resources::LoadFont(const std::string &fontName, bool withFallback, bool buildNameTable) +bool Resources::LoadFont(const std::string &fontName) { pugi::xml_document doc; const std::string filename = Resources::GetPath() + "/" + fontName + ".xml"; @@ -210,7 +271,13 @@ bool Resources::LoadFont(const std::string &fontName, bool withFallback, bool bu return false; } - GlyphTable glyphTable; + bool buildNameTable = (fontName == BRAVURA) ? true : false; + bool isFallback = ((fontName == BRAVURA) || (fontName == LEIPZIG)) ? true : false; + + m_loadedFonts.insert(std::pair(fontName, Resources::LoadedFont(fontName, isFallback))); + LoadedFont &font = m_loadedFonts.at(fontName); + + GlyphTable &glyphTable = font.GetGlyphTableForModification(); const int unitsPerEm = atoi(root.attribute("units-per-em").value()); @@ -249,14 +316,11 @@ bool Resources::LoadFont(const std::string &fontName, bool withFallback, bool bu } } - if (buildNameTable && glyphTable.size() < SMUFL_COUNT) { + if (isFallback && glyphTable.size() < SMUFL_COUNT) { LogError("Expected %d default SMuFL glyphs but could load only %d.", SMUFL_COUNT, glyphTable.size()); return false; } - m_loadedFonts.insert(std::pair( - fontName, Resources::LoadedFont(fontName, m_path, glyphTable, withFallback))); - return true; } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index f52653e77a7..9de49e5b2ca 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -72,13 +72,13 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; - m_options = m_doc.GetOptions(); - if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); - resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); + resources.InitFonts(); } + m_options = m_doc.GetOptions(); + m_editorToolkit = NULL; #ifndef NO_RUNTIME @@ -117,7 +117,15 @@ bool Toolkit::SetResourcePath(const std::string &path) { Resources &resources = m_doc.GetResourcesForModification(); resources.SetPath(path); - return resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); + bool success = resources.InitFonts(); + success = success && resources.SetFallback(m_options->m_fontFallback.GetStrValue()); + if (m_options->m_fontLoadAll.GetValue()) { + success = success && resources.LoadAll(); + } + if (!m_options->m_fontAddCustom.GetValue().empty()) { + success = success && resources.AddCustom(m_options->m_fontAddCustom.GetValue()); + } + return success; } bool Toolkit::SetFont(const std::string &fontName) @@ -1131,11 +1139,19 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) // Forcing font resource to be reset if the font is given in the options if (json.has("fontAddCustom")) { Resources &resources = m_doc.GetResourcesForModification(); - resources.InitFonts(m_options->m_fontAddCustom.GetValue(), m_options->m_font.GetValue()); + resources.AddCustom(m_options->m_fontAddCustom.GetValue()); } - else if (json.has("font")) { + if (json.has("font")) { this->SetFont(m_options->m_font.GetValue()); } + if (json.has("fontFallback")) { + Resources &resources = m_doc.GetResourcesForModification(); + resources.SetFallback(m_options->m_fontFallback.GetStrValue()); + } + if (json.has("fontLoadAll")) { + Resources &resources = m_doc.GetResourcesForModification(); + resources.LoadAll(); + } return true; } From 1e5e776eacdbcf9aaecddcf64593489e02a2b815 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 09:28:56 +0100 Subject: [PATCH 196/383] Change SVG map key to const Glyph * --- include/vrv/svgdevicecontext.h | 2 +- src/svgdevicecontext.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index cacd7353bbd..58c4b2798ec 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -349,7 +349,7 @@ class SvgDeviceContext : public DeviceContext { std::string m_refId; }; const std::string InsertGlyphRef(const Glyph *glyph); - std::map m_smuflGlyphs; + std::map m_smuflGlyphs; std::map m_glyphCodesCounter; // pugixml data diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 8dc8f4184e6..c91e7378264 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -99,8 +99,8 @@ const std::string SvgDeviceContext::InsertGlyphRef(const Glyph *glyph) const std::string code = glyph->GetCodeStr(); const std::string path = glyph->GetPath(); - if (m_smuflGlyphs.find(path) != m_smuflGlyphs.end()) { - return m_smuflGlyphs.at(path).GetRefId(); + if (m_smuflGlyphs.find(glyph) != m_smuflGlyphs.end()) { + return m_smuflGlyphs.at(glyph).GetRefId(); } int count; @@ -112,7 +112,7 @@ const std::string SvgDeviceContext::InsertGlyphRef(const Glyph *glyph) } GlyphRef ref(glyph, count, m_glyphPostfixId); const std::string id = ref.GetRefId(); - m_smuflGlyphs.insert(std::pair(path, ref)); + m_smuflGlyphs.insert(std::pair(glyph, ref)); m_glyphCodesCounter[code] = count + 1; return id; @@ -205,9 +205,9 @@ void SvgDeviceContext::Commit(bool xml_declaration) pugi::xml_document sourceDoc; // for each needed glyph - for (const std::pair entry : m_smuflGlyphs) { + for (const std::pair entry : m_smuflGlyphs) { // load the XML file that contains it as a pugi::xml_document - std::ifstream source(entry.first); + std::ifstream source(entry.first->GetPath()); sourceDoc.load(source); // copy all the nodes inside into the master document From fb1ed040e4fc53ae8080e89315afbf18e0ab987d Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 09:31:13 +0100 Subject: [PATCH 197/383] Fix formatting --- include/vrv/svgdevicecontext.h | 2 +- src/svgdevicecontext.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index 58c4b2798ec..b76ed3a80dc 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -349,7 +349,7 @@ class SvgDeviceContext : public DeviceContext { std::string m_refId; }; const std::string InsertGlyphRef(const Glyph *glyph); - std::map m_smuflGlyphs; + std::map m_smuflGlyphs; std::map m_glyphCodesCounter; // pugixml data diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index c91e7378264..32b0605d80f 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -112,7 +112,7 @@ const std::string SvgDeviceContext::InsertGlyphRef(const Glyph *glyph) } GlyphRef ref(glyph, count, m_glyphPostfixId); const std::string id = ref.GetRefId(); - m_smuflGlyphs.insert(std::pair(glyph, ref)); + m_smuflGlyphs.insert(std::pair(glyph, ref)); m_glyphCodesCounter[code] = count + 1; return id; From d0f3ff781c721f136da3cc9bb24206264a8d763e Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 09:49:05 +0100 Subject: [PATCH 198/383] Remove unused variable --- src/svgdevicecontext.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 32b0605d80f..e42d4de430b 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -97,7 +97,6 @@ SvgDeviceContext::GlyphRef::GlyphRef(const Glyph *glyph, int idx, const std::str const std::string SvgDeviceContext::InsertGlyphRef(const Glyph *glyph) { const std::string code = glyph->GetCodeStr(); - const std::string path = glyph->GetPath(); if (m_smuflGlyphs.find(glyph) != m_smuflGlyphs.end()) { return m_smuflGlyphs.at(glyph).GetRefId(); From f28d734914dbb97a22a81ac8c56a83da5ea89542 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 09:57:44 +0100 Subject: [PATCH 199/383] Code commenting and renaming --- include/vrv/svgdevicecontext.h | 9 ++++----- src/svgdevicecontext.cpp | 16 ++++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index b76ed3a80dc..c00be84fe5f 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -332,15 +332,14 @@ class SvgDeviceContext : public DeviceContext { // including any glyph for the same code but from different fonts. // They will be added at the end of the file as . // With multiple font support we need to keep track of: - // a) the path to the glyph (to check if is has been already added) + // a) the glyph (to check if is has been already added) // b) the id assigned to glyphs on the (that is has been consumed by the already rendered elements) // To keep things as similar as possible to previous versions we generate ids with as uuuu-ss (where uuuu is the // Smulf code for the glyph and ss the per-session suffix) for most of the cases (single font usage). When the same - // glyph has been used from several fonts we use uuuu-n-ss where n indicates the collision count . Maybe we don't - // need to keep this pattern and can simplify this. + // glyph has been used from several fonts we use uuuu-n-ss where n indicates the collision count. class GlyphRef { public: - GlyphRef(const Glyph *glyph, int idx, const std::string &postfix); + GlyphRef(const Glyph *glyph, int count, const std::string &postfix); const Glyph *GetGlyph() const { return m_glyph; }; const std::string &GetRefId() const { return m_refId; }; @@ -350,7 +349,7 @@ class SvgDeviceContext : public DeviceContext { }; const std::string InsertGlyphRef(const Glyph *glyph); std::map m_smuflGlyphs; - std::map m_glyphCodesCounter; + std::map m_glyphCodeFontCounter; // pugixml data pugi::xml_document m_svgDoc; diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index e42d4de430b..350fab421bb 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -84,13 +84,14 @@ bool SvgDeviceContext::CopyFileToStream(const std::string &filename, std::ostrea return true; } -SvgDeviceContext::GlyphRef::GlyphRef(const Glyph *glyph, int idx, const std::string &postfix) : m_glyph(glyph) +SvgDeviceContext::GlyphRef::GlyphRef(const Glyph *glyph, int count, const std::string &postfix) : m_glyph(glyph) { - if (idx == 0) { + // Add the counter only when necessary (more than one font for that glyph) + if (count == 0) { m_refId = StringFormat("%s-%s", glyph->GetCodeStr().c_str(), postfix.c_str()); } else { - m_refId = StringFormat("%s-%d-%s", glyph->GetCodeStr().c_str(), idx, postfix.c_str()); + m_refId = StringFormat("%s-%d-%s", glyph->GetCodeStr().c_str(), count, postfix.c_str()); } }; @@ -98,21 +99,24 @@ const std::string SvgDeviceContext::InsertGlyphRef(const Glyph *glyph) { const std::string code = glyph->GetCodeStr(); + // We have already used this glyph if (m_smuflGlyphs.find(glyph) != m_smuflGlyphs.end()) { return m_smuflGlyphs.at(glyph).GetRefId(); } int count; - if (m_glyphCodesCounter.find(code) == m_glyphCodesCounter.end()) { + // This is the first time we have a glyph with this code + if (m_glyphCodeFontCounter.find(code) == m_glyphCodeFontCounter.end()) { count = 0; } + // We used it but with another font else { - count = m_glyphCodesCounter[(code)]; + count = m_glyphCodeFontCounter[(code)]; } GlyphRef ref(glyph, count, m_glyphPostfixId); const std::string id = ref.GetRefId(); m_smuflGlyphs.insert(std::pair(glyph, ref)); - m_glyphCodesCounter[code] = count + 1; + m_glyphCodeFontCounter[code] = count + 1; return id; } From 03030218442cd11a2dc04029483f37e9512943a3 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 14:39:11 +0100 Subject: [PATCH 200/383] Refactoring of View::DrawSmuflCodeWithCustomFont --- include/vrv/doc.h | 14 +++++++------- include/vrv/resources.h | 7 ++++++- include/vrv/view.h | 2 +- src/doc.cpp | 30 ++++++++++++++++-------------- src/resources.cpp | 12 ++++++++---- src/view_element.cpp | 22 ++++++---------------- src/view_graph.cpp | 11 +++++------ 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/include/vrv/doc.h b/include/vrv/doc.h index 687f6f068e2..678d215a5fe 100644 --- a/include/vrv/doc.h +++ b/include/vrv/doc.h @@ -176,13 +176,13 @@ class Doc : public Object { * @name Get the height or width for a glyph taking into account the staff and grace sizes */ ///@{ - int GetGlyphHeight(char32_t code, int staffSize, bool graceSize) const; - int GetGlyphWidth(char32_t code, int staffSize, bool graceSize) const; - int GetGlyphLeft(char32_t code, int staffSize, bool graceSize) const; - int GetGlyphRight(char32_t code, int staffSize, bool graceSize) const; - int GetGlyphBottom(char32_t code, int staffSize, bool graceSize) const; - int GetGlyphTop(char32_t code, int staffSize, bool graceSize) const; - int GetGlyphAdvX(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphHeight(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphWidth(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphLeft(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphRight(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphBottom(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphTop(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphAdvX(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; int GetDrawingUnit(int staffSize) const; int GetDrawingDoubleUnit(int staffSize) const; int GetDrawingStaffSize(int staffSize) const; diff --git a/include/vrv/resources.h b/include/vrv/resources.h index 6d1bb1244b8..5caff86d04b 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -79,7 +79,7 @@ class Resources { */ ///@{ /** Returns the glyph (if exists) for a glyph code in the current SMuFL font */ - const Glyph *GetGlyph(char32_t smuflCode) const; + const Glyph *GetGlyph(char32_t smuflCode, const std::string &fontname = "") const; /** Returns the glyph (if exists) for a glyph name in the current SMuFL font */ const Glyph *GetGlyph(const std::string &smuflName) const; /** Returns the glyph (if exists) for a glyph name in the current SMuFL font */ @@ -134,6 +134,11 @@ class Resources { bool LoadFont(const std::string &fontName); + const bool HasGlyphTableFor(const std::string &fontname) const { return m_loadedFonts.contains(fontname); } + const GlyphTable &GetGlyphTableFor(const std::string &fontname) const + { + return m_loadedFonts.at(fontname).GetGlyphTable(); + } const GlyphTable &GetCurrentGlyphTable() const { return m_loadedFonts.at(m_currentFontName).GetGlyphTable(); }; const GlyphTable &GetFallbackGlyphTable() const { return m_loadedFonts.at(m_fallbackFontName).GetGlyphTable(); }; diff --git a/include/vrv/view.h b/include/vrv/view.h index 7f8c3750def..5ccc22bee77 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -567,7 +567,7 @@ class View { DeviceContext *dc, int y1, SegmentedLine &line, int width, int dashLength = 0, int gapLength = 0); void DrawSmuflCode( DeviceContext *dc, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph = false); - int DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, + void DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph = false); void DrawThickBezierCurve( diff --git a/src/doc.cpp b/src/doc.cpp index 5f36ed7264a..2499ee250bb 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -1584,11 +1584,11 @@ void Doc::CollectVisibleScores() } } -int Doc::GetGlyphHeight(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphHeight(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code); + const Glyph *glyph = resources.GetGlyph(code, fontname); assert(glyph); glyph->GetBoundingBox(x, y, w, h); h = h * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1597,11 +1597,11 @@ int Doc::GetGlyphHeight(char32_t code, int staffSize, bool graceSize) const return h; } -int Doc::GetGlyphWidth(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphWidth(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code); + const Glyph *glyph = resources.GetGlyph(code, fontname); assert(glyph); glyph->GetBoundingBox(x, y, w, h); w = w * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1610,10 +1610,10 @@ int Doc::GetGlyphWidth(char32_t code, int staffSize, bool graceSize) const return w; } -int Doc::GetGlyphAdvX(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphAdvX(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code); + const Glyph *glyph = resources.GetGlyph(code, fontname); assert(glyph); int advX = glyph->GetHorizAdvX(); advX = advX * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1640,11 +1640,11 @@ Point Doc::ConvertFontPoint(const Glyph *glyph, const Point &fontPoint, int staf return point; } -int Doc::GetGlyphLeft(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphLeft(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code); + const Glyph *glyph = resources.GetGlyph(code, fontname); assert(glyph); glyph->GetBoundingBox(x, y, w, h); x = x * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1653,16 +1653,17 @@ int Doc::GetGlyphLeft(char32_t code, int staffSize, bool graceSize) const return x; } -int Doc::GetGlyphRight(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphRight(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { - return this->GetGlyphLeft(code, staffSize, graceSize) + this->GetGlyphWidth(code, staffSize, graceSize); + return this->GetGlyphLeft(code, staffSize, graceSize, fontname) + + this->GetGlyphWidth(code, staffSize, graceSize, fontname); } -int Doc::GetGlyphBottom(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphBottom(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code); + const Glyph *glyph = resources.GetGlyph(code, fontname); assert(glyph); glyph->GetBoundingBox(x, y, w, h); y = y * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1671,9 +1672,10 @@ int Doc::GetGlyphBottom(char32_t code, int staffSize, bool graceSize) const return y; } -int Doc::GetGlyphTop(char32_t code, int staffSize, bool graceSize) const +int Doc::GetGlyphTop(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const { - return this->GetGlyphBottom(code, staffSize, graceSize) + this->GetGlyphHeight(code, staffSize, graceSize); + return this->GetGlyphBottom(code, staffSize, graceSize, fontname) + + this->GetGlyphHeight(code, staffSize, graceSize, fontname); } int Doc::GetTextGlyphHeight(char32_t code, const FontInfo *font, bool graceSize) const diff --git a/src/resources.cpp b/src/resources.cpp index 7f82efcb632..734f9c92e2a 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -156,14 +156,18 @@ bool Resources::SetCurrentFont(const std::string &fontName, bool allowLoading) } } -const Glyph *Resources::GetGlyph(char32_t smuflCode) const +const Glyph *Resources::GetGlyph(char32_t smuflCode, const std::string &fontname) const { - if (GetCurrentGlyphTable().count(smuflCode)) { + if (!fontname.empty() && this->HasGlyphTableFor(fontname)) { + const GlyphTable &fontGlyphTable = this->GetGlyphTableFor(fontname); + if (fontGlyphTable.contains(smuflCode)) return &fontGlyphTable.at(smuflCode); + } + if (GetCurrentGlyphTable().contains(smuflCode)) { return &GetCurrentGlyphTable().at(smuflCode); } else if (!this->IsCurrentFontFallback()) { const GlyphTable &fallbackTable = this->GetFallbackGlyphTable(); - return (fallbackTable.count(smuflCode)) ? &fallbackTable.at(smuflCode) : NULL; + return (fallbackTable.contains(smuflCode)) ? &fallbackTable.at(smuflCode) : NULL; } else { return NULL; @@ -177,7 +181,7 @@ const Glyph *Resources::GetGlyph(const std::string &smuflName) const char32_t Resources::GetGlyphCode(const std::string &smuflName) const { - return m_glyphNameTable.count(smuflName) ? m_glyphNameTable.at(smuflName) : 0; + return m_glyphNameTable.contains(smuflName) ? m_glyphNameTable.at(smuflName) : 0; } bool Resources::IsSmuflFallbackNeeded(const std::u32string &text) const diff --git a/src/view_element.cpp b/src/view_element.cpp index 3bd292d45af..b5b98db094f 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -687,12 +687,7 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf dc->StartGraphic(element, "", element->GetID()); - if (clef->HasFontname() && m_doc->GetResources().FontHasGlyphAvailable(clef->GetFontname(), sym)) { - this->DrawSmuflCodeWithCustomFont(dc, clef->GetFontname(), x, y, sym, staff->m_drawingStaffSize, false); - } - else { - this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); - } + this->DrawSmuflCodeWithCustomFont(dc, clef->GetFontname(), x, y, sym, staff->m_drawingStaffSize, false); if (m_doc->IsFacs() && element->HasFacs()) { const int noteHeight @@ -1134,19 +1129,14 @@ void View::DrawMeterSig(DeviceContext *dc, MeterSig *meterSig, Staff *staff, int const int glyphSize = staff->GetDrawingStaffNotationSize(); if (enclosingFront) { - this->DrawSmuflCode(dc, x, y, enclosingFront, glyphSize, false); - x += m_doc->GetGlyphWidth(enclosingFront, glyphSize, false); + this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, enclosingFront, glyphSize, false); + x += m_doc->GetGlyphWidth(enclosingFront, glyphSize, false, meterSig->GetFontname()); } if (meterSig->HasSym() || meterSig->HasGlyphNum() || meterSig->HasGlyphName()) { const char32_t code = meterSig->GetSymbolGlyph(); - if (meterSig->HasFontname() && m_doc->GetResources().FontHasGlyphAvailable(meterSig->GetFontname(), code)) { - x += this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, code, glyphSize, false); - } - else { - this->DrawSmuflCode(dc, x, y, code, glyphSize, false); - x += m_doc->GetGlyphWidth(code, glyphSize, false); - } + this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, code, glyphSize, false); + x += m_doc->GetGlyphWidth(code, glyphSize, false, meterSig->GetFontname()); } else if (meterSig->GetForm() == METERFORM_num) { x += this->DrawMeterSigFigures(dc, x, y, meterSig, 0, staff); @@ -1156,7 +1146,7 @@ void View::DrawMeterSig(DeviceContext *dc, MeterSig *meterSig, Staff *staff, int } if (enclosingBack) { - this->DrawSmuflCode(dc, x, y, enclosingBack, glyphSize, false); + this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, enclosingBack, glyphSize, false); } dc->EndGraphic(meterSig, this); diff --git a/src/view_graph.cpp b/src/view_graph.cpp index 2970293d56a..240c9b5e07d 100644 --- a/src/view_graph.cpp +++ b/src/view_graph.cpp @@ -276,23 +276,22 @@ void View::DrawEnclosingBrackets(DeviceContext *dc, int x, int y, int height, in horizontalThickness, verticalThickness); } -int View::DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, +void View::DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph) { - assert(!customFont.empty()); + if (customFont.empty()) { + this->DrawSmuflCode(dc, x, y, code, staffSize, dimin, setBBGlyph); + return; + } Resources &resources = m_doc->GetResourcesForModification(); const std::string prevFont = resources.GetCurrentFont(); resources.SetCurrentFont(customFont); - int drawnWidth = m_doc->GetGlyphWidth(code, staffSize, false); - DrawSmuflCode(dc, x, y, code, staffSize, dimin, setBBGlyph); resources.SetCurrentFont(prevFont); - - return drawnWidth; } void View::DrawSmuflCode(DeviceContext *dc, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph) From eba63e055ca749a7a6d8fe5fd9ce04c9248e54d7 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 15:03:17 +0100 Subject: [PATCH 201/383] Change font globally DrawClef and DrawMeterSig --- include/vrv/doc.h | 14 +++++++------- include/vrv/resources.h | 7 +------ include/vrv/view.h | 4 ++-- src/doc.cpp | 32 +++++++++++++++----------------- src/resources.cpp | 6 +----- src/view_element.cpp | 36 ++++++++++++++++++++++++++++++------ src/view_graph.cpp | 2 ++ 7 files changed, 58 insertions(+), 43 deletions(-) diff --git a/include/vrv/doc.h b/include/vrv/doc.h index 678d215a5fe..687f6f068e2 100644 --- a/include/vrv/doc.h +++ b/include/vrv/doc.h @@ -176,13 +176,13 @@ class Doc : public Object { * @name Get the height or width for a glyph taking into account the staff and grace sizes */ ///@{ - int GetGlyphHeight(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; - int GetGlyphWidth(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; - int GetGlyphLeft(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; - int GetGlyphRight(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; - int GetGlyphBottom(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; - int GetGlyphTop(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; - int GetGlyphAdvX(char32_t code, int staffSize, bool graceSize, const std::string &fontName = "") const; + int GetGlyphHeight(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphWidth(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphLeft(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphRight(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphBottom(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphTop(char32_t code, int staffSize, bool graceSize) const; + int GetGlyphAdvX(char32_t code, int staffSize, bool graceSize) const; int GetDrawingUnit(int staffSize) const; int GetDrawingDoubleUnit(int staffSize) const; int GetDrawingStaffSize(int staffSize) const; diff --git a/include/vrv/resources.h b/include/vrv/resources.h index 5caff86d04b..6d1bb1244b8 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -79,7 +79,7 @@ class Resources { */ ///@{ /** Returns the glyph (if exists) for a glyph code in the current SMuFL font */ - const Glyph *GetGlyph(char32_t smuflCode, const std::string &fontname = "") const; + const Glyph *GetGlyph(char32_t smuflCode) const; /** Returns the glyph (if exists) for a glyph name in the current SMuFL font */ const Glyph *GetGlyph(const std::string &smuflName) const; /** Returns the glyph (if exists) for a glyph name in the current SMuFL font */ @@ -134,11 +134,6 @@ class Resources { bool LoadFont(const std::string &fontName); - const bool HasGlyphTableFor(const std::string &fontname) const { return m_loadedFonts.contains(fontname); } - const GlyphTable &GetGlyphTableFor(const std::string &fontname) const - { - return m_loadedFonts.at(fontname).GetGlyphTable(); - } const GlyphTable &GetCurrentGlyphTable() const { return m_loadedFonts.at(m_currentFontName).GetGlyphTable(); }; const GlyphTable &GetFallbackGlyphTable() const { return m_loadedFonts.at(m_fallbackFontName).GetGlyphTable(); }; diff --git a/include/vrv/view.h b/include/vrv/view.h index 5ccc22bee77..c9137403e20 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -567,8 +567,8 @@ class View { DeviceContext *dc, int y1, SegmentedLine &line, int width, int dashLength = 0, int gapLength = 0); void DrawSmuflCode( DeviceContext *dc, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph = false); - void DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, - int staffSize, bool dimin, bool setBBGlyph = false); + // void DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, + // int staffSize, bool dimin, bool setBBGlyph = false); void DrawThickBezierCurve( DeviceContext *dc, Point bezier[4], int thickness, int staffSize, int penWidth, int penStyle = AxSOLID); diff --git a/src/doc.cpp b/src/doc.cpp index 2499ee250bb..5b13dd476d7 100644 --- a/src/doc.cpp +++ b/src/doc.cpp @@ -1584,11 +1584,11 @@ void Doc::CollectVisibleScores() } } -int Doc::GetGlyphHeight(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphHeight(char32_t code, int staffSize, bool graceSize) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code, fontname); + const Glyph *glyph = resources.GetGlyph(code); assert(glyph); glyph->GetBoundingBox(x, y, w, h); h = h * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1597,11 +1597,11 @@ int Doc::GetGlyphHeight(char32_t code, int staffSize, bool graceSize, const std: return h; } -int Doc::GetGlyphWidth(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphWidth(char32_t code, int staffSize, bool graceSize) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code, fontname); + const Glyph *glyph = resources.GetGlyph(code); assert(glyph); glyph->GetBoundingBox(x, y, w, h); w = w * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1610,10 +1610,10 @@ int Doc::GetGlyphWidth(char32_t code, int staffSize, bool graceSize, const std:: return w; } -int Doc::GetGlyphAdvX(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphAdvX(char32_t code, int staffSize, bool graceSize) const { const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code, fontname); + const Glyph *glyph = resources.GetGlyph(code); assert(glyph); int advX = glyph->GetHorizAdvX(); advX = advX * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1640,11 +1640,11 @@ Point Doc::ConvertFontPoint(const Glyph *glyph, const Point &fontPoint, int staf return point; } -int Doc::GetGlyphLeft(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphLeft(char32_t code, int staffSize, bool graceSize) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code, fontname); + const Glyph *glyph = resources.GetGlyph(code); assert(glyph); glyph->GetBoundingBox(x, y, w, h); x = x * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1653,17 +1653,16 @@ int Doc::GetGlyphLeft(char32_t code, int staffSize, bool graceSize, const std::s return x; } -int Doc::GetGlyphRight(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphRight(char32_t code, int staffSize, bool graceSize) const { - return this->GetGlyphLeft(code, staffSize, graceSize, fontname) - + this->GetGlyphWidth(code, staffSize, graceSize, fontname); + return this->GetGlyphLeft(code, staffSize, graceSize) + this->GetGlyphWidth(code, staffSize, graceSize); } -int Doc::GetGlyphBottom(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphBottom(char32_t code, int staffSize, bool graceSize) const { int x, y, w, h; const Resources &resources = this->GetResources(); - const Glyph *glyph = resources.GetGlyph(code, fontname); + const Glyph *glyph = resources.GetGlyph(code); assert(glyph); glyph->GetBoundingBox(x, y, w, h); y = y * m_drawingSmuflFontSize / glyph->GetUnitsPerEm(); @@ -1672,10 +1671,9 @@ int Doc::GetGlyphBottom(char32_t code, int staffSize, bool graceSize, const std: return y; } -int Doc::GetGlyphTop(char32_t code, int staffSize, bool graceSize, const std::string &fontname) const +int Doc::GetGlyphTop(char32_t code, int staffSize, bool graceSize) const { - return this->GetGlyphBottom(code, staffSize, graceSize, fontname) - + this->GetGlyphHeight(code, staffSize, graceSize, fontname); + return this->GetGlyphBottom(code, staffSize, graceSize) + this->GetGlyphHeight(code, staffSize, graceSize); } int Doc::GetTextGlyphHeight(char32_t code, const FontInfo *font, bool graceSize) const @@ -1845,7 +1843,7 @@ double Doc::GetCueScaling() const FontInfo *Doc::GetDrawingSmuflFont(int staffSize, bool graceSize) { - m_drawingSmuflFont.SetFaceName(m_options->m_font.GetValue().c_str()); + m_drawingSmuflFont.SetFaceName(this->GetResources().GetCurrentFont().c_str()); int value = m_drawingSmuflFontSize * staffSize / 100; if (graceSize) value = value * m_options->m_graceFactor.GetValue(); m_drawingSmuflFont.SetPointSize(value); diff --git a/src/resources.cpp b/src/resources.cpp index 734f9c92e2a..3e0da984487 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -156,12 +156,8 @@ bool Resources::SetCurrentFont(const std::string &fontName, bool allowLoading) } } -const Glyph *Resources::GetGlyph(char32_t smuflCode, const std::string &fontname) const +const Glyph *Resources::GetGlyph(char32_t smuflCode) const { - if (!fontname.empty() && this->HasGlyphTableFor(fontname)) { - const GlyphTable &fontGlyphTable = this->GetGlyphTableFor(fontname); - if (fontGlyphTable.contains(smuflCode)) return &fontGlyphTable.at(smuflCode); - } if (GetCurrentGlyphTable().contains(smuflCode)) { return &GetCurrentGlyphTable().at(smuflCode); } diff --git a/src/view_element.cpp b/src/view_element.cpp index b5b98db094f..f9564730841 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -687,7 +687,14 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf dc->StartGraphic(element, "", element->GetID()); - this->DrawSmuflCodeWithCustomFont(dc, clef->GetFontname(), x, y, sym, staff->m_drawingStaffSize, false); + std::string previousFont = ""; + if (clef->HasFontname()) { + Resources &resources = m_doc->GetResourcesForModification(); + previousFont = resources.GetCurrentFont(); + resources.SetCurrentFont(clef->GetFontname()); + } + + this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false); if (m_doc->IsFacs() && element->HasFacs()) { const int noteHeight @@ -705,6 +712,11 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf // Possibly draw enclosing brackets this->DrawClefEnclosing(dc, clef, staff, sym, x, y); + if (!previousFont.empty()) { + Resources &resources = m_doc->GetResourcesForModification(); + resources.SetCurrentFont(previousFont); + } + dc->EndGraphic(element, this); } @@ -1123,20 +1135,27 @@ void View::DrawMeterSig(DeviceContext *dc, MeterSig *meterSig, Staff *staff, int dc->StartGraphic(meterSig, "", meterSig->GetID()); + std::string previousFont; + if (meterSig->HasFontname()) { + Resources &resources = m_doc->GetResourcesForModification(); + previousFont = resources.GetCurrentFont(); + resources.SetCurrentFont(meterSig->GetFontname()); + } + int y = staff->GetDrawingY() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); int x = meterSig->GetDrawingX() + horizOffset; const int glyphSize = staff->GetDrawingStaffNotationSize(); if (enclosingFront) { - this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, enclosingFront, glyphSize, false); - x += m_doc->GetGlyphWidth(enclosingFront, glyphSize, false, meterSig->GetFontname()); + this->DrawSmuflCode(dc, x, y, enclosingFront, glyphSize, false); + x += m_doc->GetGlyphWidth(enclosingFront, glyphSize, false); } if (meterSig->HasSym() || meterSig->HasGlyphNum() || meterSig->HasGlyphName()) { const char32_t code = meterSig->GetSymbolGlyph(); - this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, code, glyphSize, false); - x += m_doc->GetGlyphWidth(code, glyphSize, false, meterSig->GetFontname()); + this->DrawSmuflCode(dc, x, y, code, glyphSize, false); + x += m_doc->GetGlyphWidth(code, glyphSize, false); } else if (meterSig->GetForm() == METERFORM_num) { x += this->DrawMeterSigFigures(dc, x, y, meterSig, 0, staff); @@ -1146,7 +1165,12 @@ void View::DrawMeterSig(DeviceContext *dc, MeterSig *meterSig, Staff *staff, int } if (enclosingBack) { - this->DrawSmuflCodeWithCustomFont(dc, meterSig->GetFontname(), x, y, enclosingBack, glyphSize, false); + this->DrawSmuflCode(dc, x, y, enclosingBack, glyphSize, false); + } + + if (!previousFont.empty()) { + Resources &resources = m_doc->GetResourcesForModification(); + resources.SetCurrentFont(previousFont); } dc->EndGraphic(meterSig, this); diff --git a/src/view_graph.cpp b/src/view_graph.cpp index 240c9b5e07d..8165042c2a0 100644 --- a/src/view_graph.cpp +++ b/src/view_graph.cpp @@ -276,6 +276,7 @@ void View::DrawEnclosingBrackets(DeviceContext *dc, int x, int y, int height, in horizontalThickness, verticalThickness); } +/* void View::DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &customFont, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph) { @@ -293,6 +294,7 @@ void View::DrawSmuflCodeWithCustomFont(DeviceContext *dc, const std::string &cus resources.SetCurrentFont(prevFont); } +*/ void View::DrawSmuflCode(DeviceContext *dc, int x, int y, char32_t code, int staffSize, bool dimin, bool setBBGlyph) { From 18be36c273b3938f24b957c12bd02f2180b17512 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 15:07:40 +0100 Subject: [PATCH 202/383] Use Resources CurrentFont and FallbackFont --- src/svgdevicecontext.cpp | 4 ++-- src/view_element.cpp | 2 +- src/view_text.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 350fab421bb..c5aae440c35 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -195,9 +195,9 @@ void SvgDeviceContext::Commit(bool xml_declaration) if (m_vrvTextFont && resources) { this->IncludeTextFont(resources->GetCurrentFont(), resources); } - // include the Leipzig fallback font + // include the fallback font if (m_vrvTextFontFallback && resources) { - this->IncludeTextFont("Leipzig", resources); + this->IncludeTextFont(resources->GetCurrentFont(), resources); } } diff --git a/src/view_element.cpp b/src/view_element.cpp index f9564730841..88a2d28ec34 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1816,7 +1816,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff FontInfo vrvTxt; assert(dc->HasFont()); vrvTxt.SetPointSize(dc->GetFont()->GetPointSize() * m_doc->GetMusicToLyricFontSizeRatio()); - vrvTxt.SetFaceName(m_doc->GetOptions()->m_font.GetValue()); + vrvTxt.SetFaceName(m_doc->GetResources().GetCurrentFont()); std::u32string str; str.push_back(m_doc->GetOptions()->m_lyricElision.GetValue()); bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(str); diff --git a/src/view_text.cpp b/src/view_text.cpp index 2d2839dd935..ff281247add 100644 --- a/src/view_text.cpp +++ b/src/view_text.cpp @@ -123,7 +123,7 @@ void View::DrawDynamString(DeviceContext *dc, const std::u32string &str, TextDra std::u32string smuflStr = Dynam::GetSymbolStr(token.first, singleGlyphs); FontInfo vrvTxt; vrvTxt.SetPointSize(dc->GetFont()->GetPointSize() * m_doc->GetMusicToLyricFontSizeRatio()); - vrvTxt.SetFaceName(m_doc->GetOptions()->m_font.GetValue()); + vrvTxt.SetFaceName(m_doc->GetResources().GetCurrentFont()); bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(smuflStr); vrvTxt.SetSmuflWithFallback(isFallbackNeeded); vrvTxt.SetStyle(FONTSTYLE_normal); @@ -197,7 +197,7 @@ void View::DrawHarmString(DeviceContext *dc, const std::u32string &str, TextDraw FontInfo vrvTxt; vrvTxt.SetPointSize(dc->GetFont()->GetPointSize() * m_doc->GetMusicToLyricFontSizeRatio()); - vrvTxt.SetFaceName(m_doc->GetOptions()->m_font.GetValue()); + vrvTxt.SetFaceName(m_doc->GetResources().GetCurrentFont()); bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(smuflAccid); vrvTxt.SetSmuflWithFallback(isFallbackNeeded); dc->SetFont(&vrvTxt); @@ -292,7 +292,7 @@ void View::DrawLyricString( FontInfo vrvTxt; vrvTxt.SetPointSize(dc->GetFont()->GetPointSize() * m_doc->GetMusicToLyricFontSizeRatio()); - vrvTxt.SetFaceName(m_doc->GetOptions()->m_font.GetValue()); + vrvTxt.SetFaceName(m_doc->GetResources().GetCurrentFont()); std::u32string elision; elision.push_back(m_doc->GetOptions()->m_lyricElision.GetValue()); bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(elision); @@ -407,7 +407,7 @@ void View::DrawRend(DeviceContext *dc, Rend *rend, TextDrawingParams ¶ms) // Because we do not have the string at this stage we rely only on the selected font // This means fallback will not work for missing glyphs within rendFont.SetSmuflWithFallback(false); - rendFont.SetFaceName(m_doc->GetOptions()->m_font.GetValue()); + rendFont.SetFaceName(m_doc->GetResources().GetCurrentFont()); int pointSize = (rendFont.GetPointSize() != 0) ? rendFont.GetPointSize() : params.m_pointSize; rendFont.SetPointSize(pointSize * m_doc->GetMusicToLyricFontSizeRatio()); customFont = true; @@ -619,7 +619,7 @@ void View::DrawSymbol(DeviceContext *dc, Symbol *symbol, TextDrawingParams ¶ if (symbol->HasGlyphAuth() && symbol->GetGlyphAuth() == "smufl") { bool isFallbackNeeded = (m_doc->GetResources()).IsSmuflFallbackNeeded(str); symbolFont.SetSmuflWithFallback(isFallbackNeeded); - symbolFont.SetFaceName(m_doc->GetOptions()->m_font.GetValue()); + symbolFont.SetFaceName(m_doc->GetResources().GetCurrentFont()); int pointSize = (symbolFont.GetPointSize() != 0) ? symbolFont.GetPointSize() : params.m_pointSize; symbolFont.SetPointSize(pointSize * m_doc->GetMusicToLyricFontSizeRatio()); } From f1536a3d80ac2c2dcc18bbb10c7816b8e7a4ec8b Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 31 Jan 2024 15:44:49 +0100 Subject: [PATCH 203/383] Convert path to string --- src/resources.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources.cpp b/src/resources.cpp index 3e0da984487..bdd7ddeed45 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -126,7 +126,7 @@ bool Resources::LoadAll() for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(path)) { const std::filesystem::path path = entry.path(); if (path.has_extension() && path.has_stem() && path.extension() == ".xml") { - const std::string fontName = path.stem(); + const std::string fontName = path.stem().string(); if (!IsFontLoaded(fontName)) { success = success && LoadFont(fontName); } From 1da35608544d2a2cb60f671cde4106eaac5cd979 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 1 Feb 2024 08:14:05 +0100 Subject: [PATCH 204/383] Update other builds to 10.15 --- Verovio.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 96d420fe72f..b1ad229dbd9 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -5003,7 +5003,7 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5014,7 +5014,7 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5140,7 +5140,7 @@ "$(inherited)", NO_HUMDRUM_SUPPORT, ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5152,7 +5152,7 @@ CODE_SIGN_IDENTITY = "-"; DEAD_CODE_STRIPPING = YES; GCC_PREPROCESSOR_DEFINITIONS = NO_HUMDRUM_SUPPORT; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = ""; }; @@ -5166,7 +5166,7 @@ EXECUTABLE_PREFIX = lib; HEADER_SEARCH_PATHS = ""; MACH_O_TYPE = staticlib; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; @@ -5180,7 +5180,7 @@ EXECUTABLE_PREFIX = lib; HEADER_SEARCH_PATHS = ""; MACH_O_TYPE = staticlib; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; @@ -5227,7 +5227,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.rism.VerovioFramework; @@ -5280,7 +5280,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.rism.VerovioFramework; From 0e117e28be4e9ca88b9cb283fbddfecfc0a34563 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 1 Feb 2024 08:14:34 +0100 Subject: [PATCH 205/383] Add ZipFileReader class --- Verovio.xcodeproj/project.pbxproj | 16 ++++ bindings/iOS/all.h | 1 + include/vrv/filereader.h | 82 ++++++++++++++++++++ src/filereader.cpp | 119 ++++++++++++++++++++++++++++++ src/toolkit.cpp | 35 ++++----- 5 files changed, 233 insertions(+), 20 deletions(-) create mode 100644 include/vrv/filereader.h create mode 100644 src/filereader.cpp diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index b1ad229dbd9..e2fda8406a0 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -182,6 +182,12 @@ 4D16945A1E3A44F300569BF4 /* dot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DC34BA719BC4A83006175CD /* dot.cpp */; }; 4D16946A1E3A455100569BF4 /* humlib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 40CA06581E351161009CFDD7 /* humlib.cpp */; }; 4D1694741E3A455200569BF4 /* humlib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 40CA06581E351161009CFDD7 /* humlib.cpp */; }; + 4D1AC9772B6A9BB200434023 /* filereader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1AC9762B6A9BB200434023 /* filereader.cpp */; }; + 4D1AC9782B6A9BB200434023 /* filereader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1AC9762B6A9BB200434023 /* filereader.cpp */; }; + 4D1AC9792B6A9BB200434023 /* filereader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1AC9762B6A9BB200434023 /* filereader.cpp */; }; + 4D1AC97A2B6A9BB200434023 /* filereader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1AC9762B6A9BB200434023 /* filereader.cpp */; }; + 4D1AC97C2B6A9BD000434023 /* filereader.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D1AC97B2B6A9BD000434023 /* filereader.h */; }; + 4D1AC97D2B6A9BD000434023 /* filereader.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D1AC97B2B6A9BD000434023 /* filereader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4D1BD1B521908D6B000D35B2 /* halfmrpt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1BD1B421908D6B000D35B2 /* halfmrpt.cpp */; }; 4D1BD1B621908D6B000D35B2 /* halfmrpt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1BD1B421908D6B000D35B2 /* halfmrpt.cpp */; }; 4D1BD1B721908D6B000D35B2 /* halfmrpt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1BD1B421908D6B000D35B2 /* halfmrpt.cpp */; }; @@ -1763,6 +1769,8 @@ 4D09D3EC1EA8AD8500A420E6 /* horizontalaligner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = horizontalaligner.cpp; path = src/horizontalaligner.cpp; sourceTree = ""; }; 4D14600F1EA8A913007DB90C /* horizontalaligner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = horizontalaligner.h; path = include/vrv/horizontalaligner.h; sourceTree = ""; }; 4D1694601E3A44F300569BF4 /* Verovio-Humdrum */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Verovio-Humdrum"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4D1AC9762B6A9BB200434023 /* filereader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = filereader.cpp; path = src/filereader.cpp; sourceTree = ""; }; + 4D1AC97B2B6A9BD000434023 /* filereader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = filereader.h; path = include/vrv/filereader.h; sourceTree = ""; }; 4D1BD1B421908D6B000D35B2 /* halfmrpt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = halfmrpt.cpp; path = src/halfmrpt.cpp; sourceTree = ""; }; 4D1BD1B821908D78000D35B2 /* halfmrpt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = halfmrpt.h; path = include/vrv/halfmrpt.h; sourceTree = ""; }; 4D1BE7661C688F5A0086DC0E /* Binasc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Binasc.cpp; path = src/midi/Binasc.cpp; sourceTree = ""; }; @@ -2776,6 +2784,8 @@ 36E0442D2347A9290054F141 /* expansionmap.h */, 4D79643826C6B3520026288B /* featureextractor.cpp */, 4D79643026C6AA720026288B /* featureextractor.h */, + 4D1AC9762B6A9BB200434023 /* filereader.cpp */, + 4D1AC97B2B6A9BD000434023 /* filereader.h */, 4DF28A041A754DF000BA9F7D /* floatingobject.cpp */, 4D95D4F41D7185DE00B2B856 /* floatingobject.h */, 4D09D3EC1EA8AD8500A420E6 /* horizontalaligner.cpp */, @@ -3388,6 +3398,7 @@ E79320642991452100D80975 /* calcstemfunctor.h in Headers */, 4D1BD1B921908D78000D35B2 /* halfmrpt.h in Headers */, 4DACC9F42990F29A00B55913 /* atts_visual.h in Headers */, + 4D1AC97C2B6A9BD000434023 /* filereader.h in Headers */, 4DA0EADD22BB77AF00A7EBEB /* zone.h in Headers */, E71EF3C32975E4DC00D36264 /* resetfunctor.h in Headers */, 4D763EC91987D067003FCAB5 /* metersig.h in Headers */, @@ -3706,6 +3717,7 @@ BBC19FBF22B37CA000100F42 /* all.h in Headers */, 4DA0EAE222BB77AF00A7EBEB /* editortoolkit_mensural.h in Headers */, E79C87C8269440810098FE85 /* lv.h in Headers */, + 4D1AC97D2B6A9BD000434023 /* filereader.h in Headers */, BB4C4B5822A932D7001F6AF0 /* layerelement.h in Headers */, BB4C4B9422A932E5001F6AF0 /* areaposinterface.h in Headers */, ); @@ -3873,6 +3885,7 @@ 4D1693F61E3A44F300569BF4 /* barline.cpp in Sources */, E7901661298BCB2C008FDB4E /* calcalignmentxposfunctor.cpp in Sources */, 400FEDD6206FA74D000D3233 /* gracegrp.cpp in Sources */, + 4D1AC9782B6A9BB200434023 /* filereader.cpp in Sources */, 4D89F90F201771AE00A4D336 /* num.cpp in Sources */, 4D1693F71E3A44F300569BF4 /* bboxdevicecontext.cpp in Sources */, 4D1693F81E3A44F300569BF4 /* beam.cpp in Sources */, @@ -4153,6 +4166,7 @@ files = ( 4DEF8A6421B7AAF90093A76B /* f.cpp in Sources */, 4DB3D89E1F7E7FAA00B5FC2B /* fig.cpp in Sources */, + 4D1AC9772B6A9BB200434023 /* filereader.cpp in Sources */, 4D4FCD121F54570E0009C455 /* staffdef.cpp in Sources */, 8F086EE2188539540037FD8E /* verticalaligner.cpp in Sources */, 4DEC4D9621C81E3B00D1D273 /* expan.cpp in Sources */, @@ -4438,6 +4452,7 @@ 4DB3D8F01F83D1A700B5FC2B /* fig.cpp in Sources */, E790165F298BCB27008FDB4E /* calcalignmentxposfunctor.cpp in Sources */, 8F3DD36718854B410051330C /* verticalaligner.cpp in Sources */, + 4D1AC9792B6A9BB200434023 /* filereader.cpp in Sources */, 4D766F0220ACAD6E006875D8 /* nc.cpp in Sources */, 4DEC4D9821C81E3B00D1D273 /* expan.cpp in Sources */, 4D3C3F0E294B89AF009993E6 /* ornam.cpp in Sources */, @@ -4721,6 +4736,7 @@ BB4C4B9322A932E5001F6AF0 /* areaposinterface.cpp in Sources */, E7901660298BCB27008FDB4E /* calcalignmentxposfunctor.cpp in Sources */, BB4C4AA122A9328F001F6AF0 /* verticalaligner.cpp in Sources */, + 4D1AC97A2B6A9BB200434023 /* filereader.cpp in Sources */, BB4C4B2F22A932CF001F6AF0 /* pedal.cpp in Sources */, 4D2E759222BC2B80004C51F0 /* tuning.cpp in Sources */, BB4C4B4922A932D7001F6AF0 /* clef.cpp in Sources */, diff --git a/bindings/iOS/all.h b/bindings/iOS/all.h index 9698250ef2e..0a5dbb6b62d 100644 --- a/bindings/iOS/all.h +++ b/bindings/iOS/all.h @@ -100,6 +100,7 @@ #import #import #import +#import #import #import #import diff --git a/include/vrv/filereader.h b/include/vrv/filereader.h new file mode 100644 index 00000000000..08a6a068bff --- /dev/null +++ b/include/vrv/filereader.h @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: filereader.h +// Author: Laurent Pugin +// Created: 31/01/2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_FILEREADER_H__ +#define __VRV_FILEREADER_H__ + +#include +#include + +namespace miniz_cpp { +class zip_file; +} + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// ZipFileReader +//---------------------------------------------------------------------------- + +/** + * This class is a reader for zip archives. + */ +class ZipFileReader { +public: + /** + * @name Constructors, destructors, and other standard methods + */ + ///@{ + ZipFileReader(); + ~ZipFileReader(); + ///@} + + /** + * Reset a previously loaded file. + */ + void Reset(); + + /** + * Load a file into memory. + */ + bool Load(const std::string &filename); + + /** + * Load a vector into memory + */ + bool Load(const std::vector &bytes); + + /** + * Check if the archive contains the file + */ + bool HasFile(const std::string &filename); + + /** + * Read the text file. + * Return an empty string if the file does not exist. + */ + std::string ReadTextFile(const std::string &filename); + + /** + * Return a list of all files (including directories) + */ + std::list GetFileList(); + +private: + // +public: + // +private: + /** A pointer to the miniz zip file */ + miniz_cpp::zip_file *m_file; + +}; // class ZipFileReader + +} // namespace vrv + +#endif // __VRV_FILEREADER_H__ diff --git a/src/filereader.cpp b/src/filereader.cpp new file mode 100644 index 00000000000..aee33a5d00d --- /dev/null +++ b/src/filereader.cpp @@ -0,0 +1,119 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: filereader.cpp +// Author: Laurent Pugin +// Created: 31/01/2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "filereader.h" + +//---------------------------------------------------------------------------- + +#include + +//---------------------------------------------------------------------------- + +#include "vrv.h" + +//---------------------------------------------------------------------------- + +#include "zip_file.hpp" + +namespace vrv { + +//---------------------------------------------------------------------------- +// ZipFileReader +//---------------------------------------------------------------------------- + +ZipFileReader::ZipFileReader() +{ + m_file = NULL; + + this->Reset(); +} + +ZipFileReader::~ZipFileReader() +{ + this->Reset(); +} + +void ZipFileReader::Reset() +{ + if (m_file) { + delete m_file; + m_file = NULL; + } +} + +bool ZipFileReader::Load(const std::string &filename) +{ + std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); + if (!fin.is_open()) { + return false; + } + + fin.seekg(0, std::ios::end); + std::streamsize fileSize = (std::streamsize)fin.tellg(); + fin.clear(); + fin.seekg(0, std::wios::beg); + + std::vector bytes; + bytes.reserve(fileSize + 1); + + unsigned char buffer; + while (fin.read((char *)&buffer, sizeof(unsigned char))) { + bytes.push_back(buffer); + } + return this->Load(bytes); +} + +bool ZipFileReader::Load(const std::vector &bytes) +{ + this->Reset(); + + m_file = new miniz_cpp::zip_file(bytes); + + return true; +} + +std::list ZipFileReader::GetFileList() +{ + assert(m_file); + + std::list list; + for (miniz_cpp::zip_info &member : m_file->infolist()) { + list.push_back(member.filename); + } + return list; +} + +bool ZipFileReader::HasFile(const std::string &filename) +{ + assert(m_file); + + // Look for the file in the zip + for (miniz_cpp::zip_info &member : m_file->infolist()) { + if (member.filename == filename) { + return true; + } + } + + return true; +} + +std::string ZipFileReader::ReadTextFile(const std::string &filename) +{ + assert(m_file); + + // Look for the meta file in the zip + for (miniz_cpp::zip_info &member : m_file->infolist()) { + if (member.filename == filename) { + return m_file->read(member.filename); + } + } + + LogError("No file '%s' to read found in the archive", filename.c_str()); + return ""; +} + +} // namespace vrv diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 9de49e5b2ca..8e7c1bec05d 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -21,6 +21,7 @@ #include "editortoolkit_cmn.h" #include "editortoolkit_mensural.h" #include "editortoolkit_neume.h" +#include "filereader.h" #include "findfunctor.h" #include "ioabc.h" #include "iodarms.h" @@ -48,10 +49,6 @@ #include "crc.h" #include "jsonxx.h" -#ifndef NO_MXL_SUPPORT -#include "zip_file.hpp" -#endif /* NO_MXL_SUPPORT */ - namespace vrv { const char *UTF_16_BE_BOM = "\xFE\xFF"; @@ -431,26 +428,24 @@ bool Toolkit::LoadZipFile(const std::string &filename) bool Toolkit::LoadZipData(const std::vector &bytes) { #ifndef NO_MXL_SUPPORT - miniz_cpp::zip_file file(bytes); - - std::string filename; - // Look for the meta file in the zip - for (miniz_cpp::zip_info &member : file.infolist()) { - if (member.filename == "META-INF/container.xml") { - std::string container = file.read(member.filename); - // Find the file name with an xpath query - pugi::xml_document doc; - doc.load_buffer(container.c_str(), container.size()); - pugi::xml_node root = doc.first_child(); - pugi::xml_node rootfile = root.select_node("/container/rootfiles/rootfile").node(); - filename = rootfile.attribute("full-path").value(); - break; - } + ZipFileReader zipFileReader; + zipFileReader.Load(bytes); + + const std::string metaInf = "META-INF/container.xml"; + if (!zipFileReader.HasFile(metaInf)) { + LogError("No '%s' file to load found in the archive", metaInf.c_str()); + return false; } + std::string containerXml = zipFileReader.ReadTextFile("META-INF/container.xml"); + pugi::xml_document doc; + doc.load_buffer(containerXml.c_str(), containerXml.size()); + pugi::xml_node root = doc.first_child(); + pugi::xml_node rootfile = root.select_node("/container/rootfiles/rootfile").node(); + std::string filename = rootfile.attribute("full-path").value(); if (!filename.empty()) { LogInfo("Loading file '%s' in the archive", filename.c_str()); - return this->LoadData(file.read(filename)); + return this->LoadData(zipFileReader.ReadTextFile(filename)); } else { LogError("No file to load found in the archive"); From 3b05e01557e2999bdb9265c698a932efff32feea Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 1 Feb 2024 08:43:22 +0100 Subject: [PATCH 206/383] Let the Glyph return the XML from the path --- include/vrv/glyph.h | 5 +++++ src/glyph.cpp | 11 +++++++++++ src/svgdevicecontext.cpp | 5 ++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/vrv/glyph.h b/include/vrv/glyph.h index 24089f559cf..a1110c8c368 100644 --- a/include/vrv/glyph.h +++ b/include/vrv/glyph.h @@ -106,6 +106,11 @@ class Glyph { */ const Point *GetAnchor(SMuFLGlyphAnchor anchor) const; + /** + * Return the XML (content) of the glyph + */ + std::string GetXML() const; + private: // public: diff --git a/src/glyph.cpp b/src/glyph.cpp index e78b91c0d1f..82fd2439d9c 100644 --- a/src/glyph.cpp +++ b/src/glyph.cpp @@ -11,6 +11,9 @@ #include #include +#include +#include +#include //---------------------------------------------------------------------------- @@ -144,4 +147,12 @@ const Point *Glyph::GetAnchor(SMuFLGlyphAnchor anchor) const return &m_anchors.at(anchor); } +std::string Glyph::GetXML() const +{ + std::ifstream fstream(m_path); + std::stringstream sstream; + sstream << fstream.rdbuf(); + return sstream.str(); +} + } // namespace vrv diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index c5aae440c35..21c31b0e597 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -209,9 +209,8 @@ void SvgDeviceContext::Commit(bool xml_declaration) // for each needed glyph for (const std::pair entry : m_smuflGlyphs) { - // load the XML file that contains it as a pugi::xml_document - std::ifstream source(entry.first->GetPath()); - sourceDoc.load(source); + // load the XML as a pugi::xml_document + sourceDoc.load_string(entry.first->GetXML().c_str()); // copy all the nodes inside into the master document for (pugi::xml_node child = sourceDoc.first_child(); child; child = child.next_sibling()) { From 4cecd9f03f4c34809b72d8e7000ac9b10e7f7f30 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 1 Feb 2024 08:45:23 +0100 Subject: [PATCH 207/383] Add missing include --- include/vrv/filereader.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/vrv/filereader.h b/include/vrv/filereader.h index 08a6a068bff..c6b8f862c7c 100644 --- a/include/vrv/filereader.h +++ b/include/vrv/filereader.h @@ -10,7 +10,11 @@ #include #include +#include +//---------------------------------------------------------------------------- + +/** Forward declaration of the zip_file.hpp class */ namespace miniz_cpp { class zip_file; } From 3545f2a69a3284df909326bd055462a0ce8fdba8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 1 Feb 2024 09:19:55 +0100 Subject: [PATCH 208/383] Add implementation for zip custom fonts --- include/vrv/glyph.h | 11 ++++++++- include/vrv/resources.h | 3 ++- src/glyph.cpp | 13 ++++++---- src/options.cpp | 2 +- src/resources.cpp | 53 ++++++++++++++++++++++++++++++++--------- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/include/vrv/glyph.h b/include/vrv/glyph.h index a1110c8c368..dc28607aeeb 100644 --- a/include/vrv/glyph.h +++ b/include/vrv/glyph.h @@ -107,7 +107,14 @@ class Glyph { const Point *GetAnchor(SMuFLGlyphAnchor anchor) const; /** - * Return the XML (content) of the glyph + * Set the XML (content) of the glyph. + * This is used only for glyph added from zip archive custom fonts. + */ + void SetXML(const std::string &xml) { m_xml = xml; } + + /** + * Return the XML (content) of the glyph. + * Return the stored XML or load it from the path. */ std::string GetXML() const; @@ -129,6 +136,8 @@ class Glyph { std::string m_codeStr; /** Path to the glyph XML file */ std::string m_path; + /** XML of the content for files loaded from zip archive custom font */ + std::string m_xml; /** A map of the available anchors */ std::map m_anchors; /** A flag indicating it is a fallback */ diff --git a/include/vrv/resources.h b/include/vrv/resources.h index 6d1bb1244b8..b77f0d1b7d4 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -12,6 +12,7 @@ //---------------------------------------------------------------------------- +#include "filereader.h" #include "glyph.h" namespace vrv { @@ -132,7 +133,7 @@ class Resources { const bool m_isFallback; }; - bool LoadFont(const std::string &fontName); + bool LoadFont(const std::string &fontName, ZipFileReader *zipFile = NULL); const GlyphTable &GetCurrentGlyphTable() const { return m_loadedFonts.at(m_currentFontName).GetGlyphTable(); }; const GlyphTable &GetFallbackGlyphTable() const { return m_loadedFonts.at(m_fallbackFontName).GetGlyphTable(); }; diff --git a/src/glyph.cpp b/src/glyph.cpp index 82fd2439d9c..2413bf0f8bc 100644 --- a/src/glyph.cpp +++ b/src/glyph.cpp @@ -149,10 +149,15 @@ const Point *Glyph::GetAnchor(SMuFLGlyphAnchor anchor) const std::string Glyph::GetXML() const { - std::ifstream fstream(m_path); - std::stringstream sstream; - sstream << fstream.rdbuf(); - return sstream.str(); + if (!m_xml.empty()) { + return m_xml; + } + else { + std::ifstream fstream(m_path); + std::stringstream sstream; + sstream << fstream.rdbuf(); + return sstream.str(); + } } } // namespace vrv diff --git a/src/options.cpp b/src/options.cpp index 4dc94e035ca..0a24f1c16bd 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1293,7 +1293,7 @@ Options::Options() m_font.Init("Leipzig"); this->Register(&m_font, "font", &m_generalLayout); - m_fontAddCustom.SetInfo("Add custom font", "Add a custom music font"); + m_fontAddCustom.SetInfo("Add custom font", "Add a custom music font as zip file"); m_fontAddCustom.Init(); this->Register(&m_fontAddCustom, "fontAddCustom", &m_generalLayout); diff --git a/src/resources.cpp b/src/resources.cpp index bdd7ddeed45..4947b290e79 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -110,10 +110,13 @@ bool Resources::AddCustom(const std::vector &extraFonts) { bool success = true; // options supplied fonts - for (const std::string &fontName : extraFonts) { - success = success && LoadFont(fontName); + for (const std::string &fontFile : extraFonts) { + std::filesystem::path path(fontFile); + ZipFileReader zipFile; + zipFile.Load(fontFile); + success = success && path.has_stem() && LoadFont(path.stem().string(), &zipFile); if (!success) { - LogError("Option supplied font %s could not be loaded.", fontName.c_str()); + LogError("Option supplied font %s could not be loaded.", fontFile.c_str()); } } return success; @@ -255,15 +258,33 @@ char32_t Resources::GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar) return smuflChar; } -bool Resources::LoadFont(const std::string &fontName) +bool Resources::LoadFont(const std::string &fontName, ZipFileReader *zipFile) { pugi::xml_document doc; - const std::string filename = Resources::GetPath() + "/" + fontName + ".xml"; - pugi::xml_parse_result parseResult = doc.load_file(filename.c_str()); - if (!parseResult) { - // File not found, default bounding boxes will be used - LogError("Failed to load font and glyph bounding boxes"); - return false; + // For zip archive custom font, load the data from the zipFile + if (zipFile) { + const std::string filename = fontName + ".xml"; + if (!zipFile->HasFile(filename)) { + // File not found, default bounding boxes will be used + LogError("Failed to load font and glyph bounding boxes"); + return false; + } + pugi::xml_parse_result parseResult = doc.load_string(zipFile->ReadTextFile(filename).c_str()); + if (!parseResult) { + // File not found, default bounding boxes will be used + LogError("Failed to load font and glyph bounding boxes"); + return false; + } + } + // Other wise use the resource directory + else { + const std::string filename = Resources::GetPath() + "/" + fontName + ".xml"; + pugi::xml_parse_result parseResult = doc.load_file(filename.c_str()); + if (!parseResult) { + // File not found, default bounding boxes will be used + LogError("Failed to load font and glyph bounding boxes"); + return false; + } } pugi::xml_node root = doc.first_child(); if (!root.attribute("units-per-em")) { @@ -295,7 +316,17 @@ bool Resources::LoadFont(const std::string &fontName) if (current.attribute("w")) width = current.attribute("w").as_float(); if (current.attribute("h")) height = current.attribute("h").as_float(); glyph.SetBoundingBox(x, y, width, height); - glyph.SetPath(Resources::GetPath() + "/" + fontName + "/" + c_attribute.value() + ".xml"); + + std::string glyphFilename = fontName + "/" + c_attribute.value() + ".xml"; + // Store the XML in the glyph for fonts loaded from zip files + if (zipFile) { + glyph.SetXML(zipFile->ReadTextFile(glyphFilename)); + } + // Otherwise only store the path + else { + glyph.SetPath(Resources::GetPath() + "/" + glyphFilename); + } + if (current.attribute("h-a-x")) glyph.SetHorizAdvX(current.attribute("h-a-x").as_float()); // load anchors From 205f03c40a019d118ab276fae42cafbf6f2e2beb Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 1 Feb 2024 13:16:36 +0100 Subject: [PATCH 209/383] Retrieve the CSS font string from the Resource class --- include/vrv/resources.h | 18 ++++++++++++++++++ include/vrv/svgdevicecontext.h | 2 -- src/resources.cpp | 32 ++++++++++++++++++++++++++++++++ src/svgdevicecontext.cpp | 12 +----------- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/include/vrv/resources.h b/include/vrv/resources.h index b77f0d1b7d4..25426cc491b 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -109,6 +109,12 @@ class Resources { bool FontHasGlyphAvailable(const std::string &fontName, char32_t smuflCode) const; ///@} + /** + * Get the CSS font string for the corresponding font. + * Return an empty string if the font has not been loaded. + */ + std::string GetCSSFontFor(const std::string &fontName) const; + /** * Static method that converts unicode music code points to SMuFL equivalent. * Return the parameter char if nothing can be converted. @@ -116,7 +122,12 @@ class Resources { static char32_t GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar); private: + //---------------------------------------------------------------------------- + // LoadedFont + //---------------------------------------------------------------------------- + class LoadedFont { + public: LoadedFont(const std::string &name, bool isFallback) : m_name(name), m_isFallback(isFallback){}; ~LoadedFont(){}; @@ -125,14 +136,21 @@ class Resources { GlyphTable &GetGlyphTableForModification() { return m_glyphTable; }; bool isFallback() const { return m_isFallback; }; + void SetCSSFont(const std::string &css) { m_css = css; } + std::string GetCSSFont(const std::string &path) const; + private: std::string m_name; /** The loaded SMuFL font */ GlyphTable m_glyphTable; /** If the font needs to fallback when a glyph is not present **/ const bool m_isFallback; + /** CSS font for font loaded as zip archive */ + std::string m_css; }; + //---------------------------------------------------------------------------- + bool LoadFont(const std::string &fontName, ZipFileReader *zipFile = NULL); const GlyphTable &GetCurrentGlyphTable() const { return m_loadedFonts.at(m_currentFontName).GetGlyphTable(); }; diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index c00be84fe5f..0139e26ed8b 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -9,10 +9,8 @@ #define __VRV_SVG_DC_H__ #include -#include #include #include -#include #include #include diff --git a/src/resources.cpp b/src/resources.cpp index 4947b290e79..152a7a42a11 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -10,6 +10,9 @@ //---------------------------------------------------------------------------- #include +#include +#include +#include #include //---------------------------------------------------------------------------- @@ -215,6 +218,16 @@ bool Resources::FontHasGlyphAvailable(const std::string &fontName, char32_t smuf } } +std::string Resources::GetCSSFontFor(const std::string &fontName) const +{ + if (!IsFontLoaded(fontName)) { + return ""; + } + + const LoadedFont &font = m_loadedFonts.at(fontName); + return font.GetCSSFont(m_path); +} + void Resources::SelectTextFont(data_FONTWEIGHT fontWeight, data_FONTSTYLE fontStyle) const { if (fontWeight == FONTWEIGHT_NONE) { @@ -298,6 +311,11 @@ bool Resources::LoadFont(const std::string &fontName, ZipFileReader *zipFile) m_loadedFonts.insert(std::pair(fontName, Resources::LoadedFont(fontName, isFallback))); LoadedFont &font = m_loadedFonts.at(fontName); + // For zip archive custom font also store the CSS + if (zipFile) { + font.SetCSSFont(zipFile->ReadTextFile(fontName + ".css")); + } + GlyphTable &glyphTable = font.GetGlyphTableForModification(); const int unitsPerEm = atoi(root.attribute("units-per-em").value()); @@ -403,4 +421,18 @@ bool Resources::InitTextFont(const std::string &fontName, const StyleAttributes return true; } +std::string Resources::LoadedFont::GetCSSFont(const std::string &path) const +{ + if (!m_css.empty()) { + return m_css; + } + else { + const std::string cssFontPath = StringFormat("%s/%s.css", path.c_str(), m_name.c_str()); + std::ifstream fstream(cssFontPath); + std::stringstream sstream; + sstream << fstream.rdbuf(); + return sstream.str(); + } +} + } // namespace vrv diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 21c31b0e597..3722ff299e8 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -128,17 +128,7 @@ void SvgDeviceContext::IncludeTextFont(const std::string &fontname, const Resour std::string cssContent; if (m_smuflTextFont == SMUFLTEXTFONT_embedded) { - const std::string cssFontPath = StringFormat("%s/%s.css", resources->GetPath().c_str(), fontname.c_str()); - std::ifstream cssFontFile(cssFontPath); - if (!cssFontFile.is_open()) { - LogWarning("The CSS font for '%s' could not be loaded and will not be embedded in the SVG", - resources->GetCurrentFont().c_str()); - } - else { - std::stringstream cssFontStream; - cssFontStream << cssFontFile.rdbuf(); - cssContent = cssFontStream.str(); - } + cssContent = resources->GetCSSFontFor(fontname); } else { std::string versionPath From 2480e14f7f928eac39423bdaf830a5d23d9dfc78 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Feb 2024 09:38:09 +0100 Subject: [PATCH 210/383] Fix json types in font options --- src/toolkit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 8e7c1bec05d..6496423907a 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1139,11 +1139,11 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) if (json.has("font")) { this->SetFont(m_options->m_font.GetValue()); } - if (json.has("fontFallback")) { + if (json.has("fontFallback")) { Resources &resources = m_doc.GetResourcesForModification(); resources.SetFallback(m_options->m_fontFallback.GetStrValue()); } - if (json.has("fontLoadAll")) { + if (json.has("fontLoadAll")) { Resources &resources = m_doc.GetResourcesForModification(); resources.LoadAll(); } From b5c7bc83bb9bafd138c2e89ccaa0d555b5ac40c3 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Feb 2024 09:38:57 +0100 Subject: [PATCH 211/383] Implement reading base64 zip archive for passing custom font in the JS toolkit --- include/vrv/resources.h | 5 +++++ src/filereader.cpp | 10 ++++++++++ src/resources.cpp | 30 ++++++++++++++++++++++++++---- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/include/vrv/resources.h b/include/vrv/resources.h index 25426cc491b..42dc7a8afea 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -115,6 +115,11 @@ class Resources { */ std::string GetCSSFontFor(const std::string &fontName) const; + /** + * Retrieve the font name either from the filename path or from the zipFile content. + */ + std::string GetCustomFontname(const std::string &filename, const ZipFileReader &zipFile); + /** * Static method that converts unicode music code points to SMuFL equivalent. * Return the parameter char if nothing can be converted. diff --git a/src/filereader.cpp b/src/filereader.cpp index aee33a5d00d..579ad5fa655 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -47,6 +47,15 @@ void ZipFileReader::Reset() bool ZipFileReader::Load(const std::string &filename) { +#ifdef __EMSCRIPTEN__ + std::string data = filename; + if (data.starts_with("data:")) { + data = data.substr(data.find("base64,") + 7); + } + LogWarning("%s", data.c_str()); + std::vector bytes = Base64Decode(data); + return this->Load(bytes); +#else std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); if (!fin.is_open()) { return false; @@ -65,6 +74,7 @@ bool ZipFileReader::Load(const std::string &filename) bytes.push_back(buffer); } return this->Load(bytes); +#endif } bool ZipFileReader::Load(const std::vector &bytes) diff --git a/src/resources.cpp b/src/resources.cpp index 152a7a42a11..dab27538a7b 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -114,12 +114,15 @@ bool Resources::AddCustom(const std::vector &extraFonts) bool success = true; // options supplied fonts for (const std::string &fontFile : extraFonts) { - std::filesystem::path path(fontFile); ZipFileReader zipFile; zipFile.Load(fontFile); - success = success && path.has_stem() && LoadFont(path.stem().string(), &zipFile); + std::string fontName = GetCustomFontname("", zipFile); + if (fontName.empty() || IsFontLoaded(fontName)) { + continue; + } + success = success && LoadFont(fontName, &zipFile); if (!success) { - LogError("Option supplied font %s could not be loaded.", fontFile.c_str()); + LogError("Option supplied font %s could not be loaded.", fontName.c_str()); } } return success; @@ -228,6 +231,23 @@ std::string Resources::GetCSSFontFor(const std::string &fontName) const return font.GetCSSFont(m_path); } +std::string Resources::GetCustomFontname(const std::string &filename, const ZipFileReader &zipFile) +{ +#ifdef __EMSCRIPTEN__ + for (auto &s : zipFile->GetFileList()) { + std::filesystem::path path(s); + if (!path.has_parent_path() && path.has_extension() && path.extension() == ".xml") { + return path.stem(); + } + } + LogWarning("The font name could not be extracted from the archive"); + return ""; +#else + std::filesystem::path path(filename); + return (path.has_stem()) ? path.stem().string() : ""; +#endif +} + void Resources::SelectTextFont(data_FONTWEIGHT fontWeight, data_FONTSTYLE fontStyle) const { if (fontWeight == FONTWEIGHT_NONE) { @@ -271,11 +291,13 @@ char32_t Resources::GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar) return smuflChar; } -bool Resources::LoadFont(const std::string &fontName, ZipFileReader *zipFile) +bool Resources::LoadFont(const std::string &fontname, ZipFileReader *zipFile) { + std::string fontName = fontname; pugi::xml_document doc; // For zip archive custom font, load the data from the zipFile if (zipFile) { + fontName = "GoldenAge"; const std::string filename = fontName + ".xml"; if (!zipFile->HasFile(filename)) { // File not found, default bounding boxes will be used From c0e8328fa552d59cf3b4a66f328b0eeaaf4f6e50 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Feb 2024 09:39:30 +0100 Subject: [PATCH 212/383] Add JS option preprocessing functions (experimental) --- emscripten/npm/src/verovio-toolkit.js | 46 ++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/emscripten/npm/src/verovio-toolkit.js b/emscripten/npm/src/verovio-toolkit.js index 68714185384..3b598e84408 100644 --- a/emscripten/npm/src/verovio-toolkit.js +++ b/emscripten/npm/src/verovio-toolkit.js @@ -1,6 +1,49 @@ import { createEmscriptenProxy } from "./emscripten-proxy.js"; +async function solve(options) { + const res = await fetch( + `https://raw.githubusercontent.com/lpugin/test-font/main/GoldenAge.zip`, + { + method: "GET", + } + ); + const data = await res.blob(); + console.log( res ); + console.log( options ); + return options; +} + +const convertToBase64 = (blob) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (event) => resolve(event.target.result); + reader.onerror = reject; + reader.readAsDataURL(blob); +}); + +async function preprocessOptions(options) +{ + // Nothing to do if we do not have 'fontAddCustom' set + if (!Object.hasOwn(options, 'fontAddCustom')) { + return options; + } + const filenames = options['fontAddCustom']; + let filesInBase64 = []; + // Get all the files and convert them to a base64 string + for ( let i = 0; i < filenames.length; i++ ) { + const res = await fetch(filenames[i], { + method: "GET", + } + ); + const data = await res.blob(); + const fileInBase64 = await convertToBase64(data); + filesInBase64.push(fileInBase64); + } + options["fontAddCustom"] = filesInBase64; + //console.log( options ); + return options; +}; + export class VerovioToolkit { constructor(VerovioModule) { @@ -182,7 +225,8 @@ export class VerovioToolkit { return this.proxy.select(this.ptr, JSON.stringify(selection)); } - setOptions(options) { + async setOptions(options) { + options = await preprocessOptions(options); return this.proxy.setOptions(this.ptr, JSON.stringify(options)); } From dc2f95dff93f772e9328c62916670a60c358e2d6 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Feb 2024 11:42:51 +0100 Subject: [PATCH 213/383] Fix const missing and remove hardcoded fontname --- include/vrv/filereader.h | 2 +- src/filereader.cpp | 4 ++-- src/resources.cpp | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/vrv/filereader.h b/include/vrv/filereader.h index c6b8f862c7c..ea17d92de86 100644 --- a/include/vrv/filereader.h +++ b/include/vrv/filereader.h @@ -69,7 +69,7 @@ class ZipFileReader { /** * Return a list of all files (including directories) */ - std::list GetFileList(); + std::list GetFileList() const; private: // diff --git a/src/filereader.cpp b/src/filereader.cpp index 579ad5fa655..2a3de8bb94a 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -49,10 +49,10 @@ bool ZipFileReader::Load(const std::string &filename) { #ifdef __EMSCRIPTEN__ std::string data = filename; + // Remove the mimetype prefix if any if (data.starts_with("data:")) { data = data.substr(data.find("base64,") + 7); } - LogWarning("%s", data.c_str()); std::vector bytes = Base64Decode(data); return this->Load(bytes); #else @@ -86,7 +86,7 @@ bool ZipFileReader::Load(const std::vector &bytes) return true; } -std::list ZipFileReader::GetFileList() +std::list ZipFileReader::GetFileList() const { assert(m_file); diff --git a/src/resources.cpp b/src/resources.cpp index dab27538a7b..174b80aa8d3 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -234,7 +234,7 @@ std::string Resources::GetCSSFontFor(const std::string &fontName) const std::string Resources::GetCustomFontname(const std::string &filename, const ZipFileReader &zipFile) { #ifdef __EMSCRIPTEN__ - for (auto &s : zipFile->GetFileList()) { + for (auto &s : zipFile.GetFileList()) { std::filesystem::path path(s); if (!path.has_parent_path() && path.has_extension() && path.extension() == ".xml") { return path.stem(); @@ -291,13 +291,11 @@ char32_t Resources::GetSmuflGlyphForUnicodeChar(const char32_t unicodeChar) return smuflChar; } -bool Resources::LoadFont(const std::string &fontname, ZipFileReader *zipFile) +bool Resources::LoadFont(const std::string &fontName, ZipFileReader *zipFile) { - std::string fontName = fontname; pugi::xml_document doc; // For zip archive custom font, load the data from the zipFile if (zipFile) { - fontName = "GoldenAge"; const std::string filename = fontName + ".xml"; if (!zipFile->HasFile(filename)) { // File not found, default bounding boxes will be used From 180ae09195355636d8357bdfff0d421d4f35fe5a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Feb 2024 14:57:31 +0100 Subject: [PATCH 214/383] Adjust JS option preprocessing --- emscripten/npm/src/verovio-toolkit.js | 58 ++++++++++++--------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/emscripten/npm/src/verovio-toolkit.js b/emscripten/npm/src/verovio-toolkit.js index 3b598e84408..6ee8b347c42 100644 --- a/emscripten/npm/src/verovio-toolkit.js +++ b/emscripten/npm/src/verovio-toolkit.js @@ -14,36 +14,6 @@ async function solve(options) { return options; } -const convertToBase64 = (blob) => new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = (event) => resolve(event.target.result); - reader.onerror = reject; - reader.readAsDataURL(blob); -}); - -async function preprocessOptions(options) -{ - // Nothing to do if we do not have 'fontAddCustom' set - if (!Object.hasOwn(options, 'fontAddCustom')) { - return options; - } - const filenames = options['fontAddCustom']; - let filesInBase64 = []; - // Get all the files and convert them to a base64 string - for ( let i = 0; i < filenames.length; i++ ) { - const res = await fetch(filenames[i], { - method: "GET", - } - ); - const data = await res.blob(); - const fileInBase64 = await convertToBase64(data); - filesInBase64.push(fileInBase64); - } - options["fontAddCustom"] = filesInBase64; - //console.log( options ); - return options; -}; - export class VerovioToolkit { constructor(VerovioModule) { @@ -225,8 +195,8 @@ export class VerovioToolkit { return this.proxy.select(this.ptr, JSON.stringify(selection)); } - async setOptions(options) { - options = await preprocessOptions(options); + setOptions(options) { + options = this.preprocessOptions(options); return this.proxy.setOptions(this.ptr, JSON.stringify(options)); } @@ -237,6 +207,30 @@ export class VerovioToolkit { return JSON.parse(this.proxy.validatePAE(this.ptr, data)); } + preprocessOptions(options) { + // Nothing to do if we do not have 'fontAddCustom' set + if (!Object.hasOwn(options, 'fontAddCustom')) { + return options; + } + const filenames = options['fontAddCustom']; + let filesInBase64 = []; + // Get all the files and convert them to a base64 string + for (let i = 0; i < filenames.length; i++ ) { + const request = new XMLHttpRequest(); + request.open("GET", filenames[i], false); // `false` makes the request synchronous + request.send(null); + + if (request.status === 200) { + filesInBase64.push(request.responseText); + } + else { + console.error(`${filenames[i]} could not be retrieved`); + } + } + options["fontAddCustom"] = filesInBase64; + //console.log( options ); + return options; + } } // A pointer to the object - only one instance can be created for now From bab9848808f6e2d3f906b6c75ed92076b5c2ba49 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 7 Feb 2024 08:23:27 +0100 Subject: [PATCH 215/383] Adjust font loading from the command line --- src/filereader.cpp | 1 + src/resources.cpp | 6 ++++-- src/toolkit.cpp | 15 ++++++++++----- tools/main.cpp | 6 ------ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/filereader.cpp b/src/filereader.cpp index 2a3de8bb94a..4703dd56a44 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -58,6 +58,7 @@ bool ZipFileReader::Load(const std::string &filename) #else std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); if (!fin.is_open()) { + LogError("File archive '%s' could not be open.", filename.c_str()); return false; } diff --git a/src/resources.cpp b/src/resources.cpp index 174b80aa8d3..3feb2880202 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -115,8 +115,10 @@ bool Resources::AddCustom(const std::vector &extraFonts) // options supplied fonts for (const std::string &fontFile : extraFonts) { ZipFileReader zipFile; - zipFile.Load(fontFile); - std::string fontName = GetCustomFontname("", zipFile); + if (!zipFile.Load(fontFile)) { + continue; + } + std::string fontName = GetCustomFontname(fontFile, zipFile); if (fontName.empty() || IsFontLoaded(fontName)) { continue; } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 6496423907a..3552ff2279f 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -115,13 +115,18 @@ bool Toolkit::SetResourcePath(const std::string &path) Resources &resources = m_doc.GetResourcesForModification(); resources.SetPath(path); bool success = resources.InitFonts(); - success = success && resources.SetFallback(m_options->m_fontFallback.GetStrValue()); - if (m_options->m_fontLoadAll.GetValue()) { - success = success && resources.LoadAll(); - } - if (!m_options->m_fontAddCustom.GetValue().empty()) { + if (m_options->m_fontAddCustom.IsSet()) { success = success && resources.AddCustom(m_options->m_fontAddCustom.GetValue()); } + if (m_options->m_font.IsSet()) { + success = success && this->SetFont(m_options->m_font.GetValue()); + } + if (m_options->m_fontFallback.IsSet()) { + success = success && resources.SetFallback(m_options->m_fontFallback.GetStrValue()); + } + if (m_options->m_fontLoadAll.IsSet()) { + success = success && resources.LoadAll(); + } return success; } diff --git a/tools/main.cpp b/tools/main.cpp index ab9593f207f..d30a41fca43 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -293,12 +293,6 @@ int main(int argc, char **argv) exit(1); } - // Load a specified font - if (!toolkit.SetOptions(vrv::StringFormat("{\"font\": \"%s\" }", options->m_font.GetValue().c_str()))) { - std::cerr << "Font '" << options->m_font.GetValue() << "' could not be loaded." << std::endl; - exit(1); - } - const std::vector outformats = { "mei", "mei-basic", "mei-pb", "mei-facs", "svg", "midi", "timemap", "expansionmap", "humdrum", "hum", "pae" }; if (std::find(outformats.begin(), outformats.end(), outformat) == outformats.end()) { From d4fdcb64ab1ea039b2f5ee65cb170efd53b4129e Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 7 Feb 2024 09:14:22 +0100 Subject: [PATCH 216/383] Use fallback for text fonts --- include/vrv/resources.h | 2 ++ src/resources.cpp | 3 +-- src/svgdevicecontext.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/vrv/resources.h b/include/vrv/resources.h index 42dc7a8afea..7988fb1faf4 100644 --- a/include/vrv/resources.h +++ b/include/vrv/resources.h @@ -66,6 +66,8 @@ class Resources { bool LoadAll(); /** Set the fallback font (Leipzig or Bravura) when some glyphs are missing in the current font */ bool SetFallback(const std::string &fontName); + /** Get the fallback font name */ + std::string GetFallbackFont() const { return m_defaultFontName; } /** Init the text font (bounding boxes and ASCII only) */ bool InitTextFont(const std::string &fontName, const StyleAttributes &style); diff --git a/src/resources.cpp b/src/resources.cpp index 3feb2880202..d2f5774be50 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -197,8 +197,7 @@ bool Resources::IsSmuflFallbackNeeded(const std::u32string &text) const return false; } for (char32_t c : text) { - const Glyph *glyph = this->GetGlyph(c); - if (glyph == NULL) return true; + if (!GetCurrentGlyphTable().contains(c)) return true; } return false; } diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 3722ff299e8..1769f1f138d 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -187,7 +187,7 @@ void SvgDeviceContext::Commit(bool xml_declaration) } // include the fallback font if (m_vrvTextFontFallback && resources) { - this->IncludeTextFont(resources->GetCurrentFont(), resources); + this->IncludeTextFont(resources->GetFallbackFont(), resources); } } From 7b17b30ac9fc9cf01a5f523a62a790e136ebde86 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 7 Feb 2024 09:47:10 +0100 Subject: [PATCH 217/383] Add support for meterSig@color --- include/vrv/metersig.h | 1 + src/iomei.cpp | 2 ++ src/metersig.cpp | 3 +++ 3 files changed, 6 insertions(+) diff --git a/include/vrv/metersig.h b/include/vrv/metersig.h index 610458329b1..585c259b9c4 100644 --- a/include/vrv/metersig.h +++ b/include/vrv/metersig.h @@ -25,6 +25,7 @@ class ScoreDefInterface; * This class models the MEI element. */ class MeterSig : public LayerElement, + public AttColor, public AttEnclosingChars, public AttExtSymNames, public AttMeterSigLog, diff --git a/src/iomei.cpp b/src/iomei.cpp index ad9afa02d92..aa139f99077 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2629,6 +2629,7 @@ void MEIOutput::WriteMeterSig(pugi::xml_node currentNode, MeterSig *meterSig) } this->WriteLayerElement(currentNode, meterSig); + meterSig->WriteColor(currentNode); meterSig->WriteEnclosingChars(currentNode); meterSig->WriteMeterSigLog(currentNode); meterSig->WriteMeterSigVis(currentNode); @@ -6689,6 +6690,7 @@ bool MEIInput::ReadMeterSig(Object *parent, pugi::xml_node meterSig) this->UpgradeMeterSigTo_5_0(meterSig, vrvMeterSig); } + vrvMeterSig->ReadColor(meterSig); vrvMeterSig->ReadEnclosingChars(meterSig); vrvMeterSig->ReadExtSymNames(meterSig); vrvMeterSig->ReadMeterSigLog(meterSig); diff --git a/src/metersig.cpp b/src/metersig.cpp index ccd59b25e93..f3df8054d23 100644 --- a/src/metersig.cpp +++ b/src/metersig.cpp @@ -31,6 +31,7 @@ static const ClassRegistrar s_factory("meterSig", METERSIG); MeterSig::MeterSig() : LayerElement(METERSIG, "msig-") + , AttColor() , AttEnclosingChars() , AttExtSymNames() , AttMeterSigLog() @@ -38,6 +39,7 @@ MeterSig::MeterSig() , AttTypography() , AttVisibility() { + this->RegisterAttClass(ATT_COLOR); this->RegisterAttClass(ATT_ENCLOSINGCHARS); this->RegisterAttClass(ATT_EXTSYMNAMES); this->RegisterAttClass(ATT_METERSIGLOG); @@ -53,6 +55,7 @@ MeterSig::~MeterSig() {} void MeterSig::Reset() { LayerElement::Reset(); + this->ResetColor(); this->ResetEnclosingChars(); this->ResetExtSymNames(); this->ResetMeterSigLog(); From dd024d7ece42c759f02f0c89c34a1ad676e78dc1 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 9 Feb 2024 12:05:57 +0100 Subject: [PATCH 218/383] Update cibuildwheel to 2.16.5 --- .github/workflows/python-ci-wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index 219ed29ce00..f425bef5845 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -82,7 +82,7 @@ jobs: #===============================================# # wheels - name: Build wheels - uses: pypa/cibuildwheel@v2.16.1 + uses: pypa/cibuildwheel@v2.16.5 with: output-dir: wheelhouse env: From 2a7ee615e41533d97d8ac6c57933c277bbefcb65 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 9 Feb 2024 13:03:14 +0100 Subject: [PATCH 219/383] Set macos deployment target [skip-ci] --- .github/workflows/python-ci-wheel.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index f425bef5845..ee9ee651098 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -89,6 +89,8 @@ jobs: CIBW_SKIP: cp37-macosx_arm64 CIBW_BUILD: ${{ env.CIBW_BUILD_IDENTIFIER }} CIBW_ARCHS_MACOS: x86_64 arm64 + CIBW_ENVIRONMENT_MACOS: + MACOSX_DEPLOYMENT_TARGET=10.15 CIBW_TEST_SKIP: cp*-macosx_arm64 CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_MANYLINUX_I686_IMAGE: manylinux2014 From 5f726d9b444b4136fe48293d8725e5cb3be4c05d Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 16 Feb 2024 12:54:01 +0100 Subject: [PATCH 220/383] fix doubled graphic --- src/view_tab.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/view_tab.cpp b/src/view_tab.cpp index ecd12728cae..c1172885fb7 100644 --- a/src/view_tab.cpp +++ b/src/view_tab.cpp @@ -98,8 +98,6 @@ void View::DrawTabNote(DeviceContext *dc, LayerElement *element, Layer *layer, S // TabGrp *tabGrp = note->IsTabGrpNote(); // assert(tabGrp); - dc->StartGraphic(note, "", note->GetID()); - int x = element->GetDrawingX(); int y = element->GetDrawingY(); @@ -152,8 +150,6 @@ void View::DrawTabNote(DeviceContext *dc, LayerElement *element, Layer *layer, S // Draw children (nothing yet) this->DrawLayerChildren(dc, note, layer, staff, measure); - - dc->EndGraphic(note, this); } void View::DrawTabDurSym(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) From 1c474b3daddc72e80ac7c486dea649f04bc86285 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 16 Feb 2024 10:25:30 +0100 Subject: [PATCH 221/383] import breaksec --- src/iomusxml.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index e7501b40ae1..cab02b71ddf 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -3625,6 +3625,10 @@ void MusicXmlInput::ReadMusicXmlNote( TabGrp *tabGrp = vrv_cast(element); tabGrp->SetBreaksec(breakSec); } + if (element->Is(REST)) { + Rest *rest = vrv_cast(element); + rest->SetBreaksec(breakSec); + } } else { if (IsInStack(BEAM, layer)) { From 1d5dad2d560d561e9958c7dd148622436705596a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 20 Feb 2024 15:51:01 +0100 Subject: [PATCH 222/383] Fix missing parameter in JS proxy --- emscripten/npm/src/emscripten-proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emscripten/npm/src/emscripten-proxy.js b/emscripten/npm/src/emscripten-proxy.js index 3f0c7669b5b..d7991c57b7f 100644 --- a/emscripten/npm/src/emscripten-proxy.js +++ b/emscripten/npm/src/emscripten-proxy.js @@ -109,7 +109,7 @@ function getToolkitFunction(VerovioModule, method) { mapping.renderToMIDI = VerovioModule.cwrap("vrvToolkit_renderToMIDI", "string", ["number", "string"]); // char *renderToPAE(Toolkit *ic) - mapping.renderToPAE = VerovioModule.cwrap("vrvToolkit_renderToPAE", "string"); + mapping.renderToPAE = VerovioModule.cwrap("vrvToolkit_renderToPAE", "string", ["number"]); // char *renderToSvg(Toolkit *ic, int pageNo, int xmlDeclaration) mapping.renderToSVG = VerovioModule.cwrap("vrvToolkit_renderToSVG", "string", ["number", "number", "number"]); From cf4879c3f9d23eede097395e8a4dc3519c8b10f0 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 20 Feb 2024 15:54:07 +0100 Subject: [PATCH 223/383] Add PAEOutput::HasFermata helper --- include/vrv/iopae.h | 1 + src/iopae.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index bb70c151677..7c1eb5c76d5 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -166,6 +166,7 @@ class PAEOutput : public Output { ///@{ void WriteDur(DurationInterface *interface); void WriteGrace(AttGraced *attGraced); + bool HasFermata(Object *object); ///@} public: diff --git a/src/iopae.cpp b/src/iopae.cpp index 306c96c4d6b..6fc7fa27d76 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -454,9 +454,7 @@ void PAEOutput::WriteNote(Note *note) m_streamStringOutput << accid; } - PointingToComparison pointingToComparisonFermata(FERMATA, note); - Fermata *fermata - = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonFermata, 1)); + bool fermata = this->HasFermata(note); if (fermata) m_streamStringOutput << "("; std::string pname = note->AttPitch::PitchnameToStr(note->GetPname()); @@ -583,6 +581,14 @@ void PAEOutput::WriteGrace(AttGraced *attGraced) } } +bool PAEOutput::HasFermata(Object *object) +{ + PointingToComparison pointingToComparisonFermata(FERMATA, object); + Fermata *fermata + = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonFermata, 1)); + return (fermata); +} + //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- #ifdef USE_PAE_OLD_PARSER From 65ba92d8a2de0144e4436c04a20471102feea542 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 20 Feb 2024 15:56:36 +0100 Subject: [PATCH 224/383] Write fermata for rest and mRest in PAE output --- src/iopae.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/iopae.cpp b/src/iopae.cpp index 6fc7fa27d76..938efd8d54f 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -401,7 +401,12 @@ void PAEOutput::WriteMRest(MRest *mRest) if (m_skip) return; + bool fermata = this->HasFermata(mRest); + if (fermata) m_streamStringOutput << "("; + m_streamStringOutput << "="; + + if (fermata) m_streamStringOutput << ")"; } void PAEOutput::WriteMultiRest(MultiRest *multiRest) @@ -479,7 +484,13 @@ void PAEOutput::WriteRest(Rest *rest) if (m_skip) return; this->WriteDur(rest); + + bool fermata = this->HasFermata(rest); + if (fermata) m_streamStringOutput << "("; + m_streamStringOutput << "-"; + + if (fermata) m_streamStringOutput << ")"; } void PAEOutput::WriteSpace(Space *space) From 8c99008c2fd564a678ae23d8c161da5decc30b5d Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sat, 24 Feb 2024 22:54:52 +0100 Subject: [PATCH 225/383] add filled half note --- data/Bravura.css | 2 +- data/Bravura.xml | 1 + data/Bravura/E0FB.xml | 1 + data/Gootville.css | 2 +- data/Leipzig.css | 2 +- data/Leipzig.xml | 8 ++++++- data/Leipzig/E0FA.xml | 2 +- data/Leipzig/E0FB.xml | 1 + data/Leland.css | 2 +- data/Petaluma.css | 2 +- data/Petaluma.xml | 1 + data/Petaluma/E0FB.xml | 1 + fonts/Leipzig/Leipzig.svg | 10 +++++---- fonts/Leipzig/Leipzig.ttf | Bin 127320 -> 127396 bytes fonts/Leipzig/Leipzig.woff2 | Bin 45096 -> 45060 bytes fonts/Leipzig/leipzig_metadata.json | 32 ++++++++++++++++++++++++++-- fonts/supported.xml | 2 +- include/vrv/smufl.h | 3 ++- 18 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 data/Bravura/E0FB.xml create mode 100644 data/Leipzig/E0FB.xml create mode 100644 data/Petaluma/E0FB.xml diff --git a/data/Bravura.css b/data/Bravura.css index c4c899e374f..135c1441c3e 100644 --- a/data/Bravura.css +++ b/data/Bravura.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Bravura'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Bravura.xml b/data/Bravura.xml index 6c8403f057e..bc11153a139 100644 --- a/data/Bravura.xml +++ b/data/Bravura.xml @@ -137,6 +137,7 @@ + diff --git a/data/Bravura/E0FB.xml b/data/Bravura/E0FB.xml new file mode 100644 index 00000000000..d31a94c6fb4 --- /dev/null +++ b/data/Bravura/E0FB.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Gootville.css b/data/Gootville.css index 68ce50f6a2c..c40a0ed2167 100644 --- a/data/Gootville.css +++ b/data/Gootville.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Gootville'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.css b/data/Leipzig.css index 3e434c0f8f2..ddea8607924 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index fe1e59903e5..31e33ecb8ae 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -89,7 +89,7 @@ - + @@ -793,4 +793,10 @@ + + + + + + \ No newline at end of file diff --git a/data/Leipzig/E0FA.xml b/data/Leipzig/E0FA.xml index abace349678..804cced5bf6 100644 --- a/data/Leipzig/E0FA.xml +++ b/data/Leipzig/E0FA.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/E0FB.xml b/data/Leipzig/E0FB.xml new file mode 100644 index 00000000000..416d896d8e9 --- /dev/null +++ b/data/Leipzig/E0FB.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leland.css b/data/Leland.css index 35db26c5b3a..c94c3c4eb2b 100644 --- a/data/Leland.css +++ b/data/Leland.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leland'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Petaluma.css b/data/Petaluma.css index f0a19c22553..fcf2a0f807f 100644 --- a/data/Petaluma.css +++ b/data/Petaluma.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Petaluma'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Petaluma.xml b/data/Petaluma.xml index fcc6082e623..6e4e06c1935 100644 --- a/data/Petaluma.xml +++ b/data/Petaluma.xml @@ -645,6 +645,7 @@ + diff --git a/data/Petaluma/E0FB.xml b/data/Petaluma/E0FB.xml new file mode 100644 index 00000000000..32346ea6de5 --- /dev/null +++ b/data/Petaluma/E0FB.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fonts/Leipzig/Leipzig.svg b/fonts/Leipzig/Leipzig.svg index 9672c1539a9..533fab01983 100644 --- a/fonts/Leipzig/Leipzig.svg +++ b/fonts/Leipzig/Leipzig.svg @@ -2,11 +2,11 @@ -Created by FontForge 20220308 at Tue Jan 30 18:14:47 2024 - By Laurent Pugin +Created by FontForge 20230101 at Sat Feb 24 19:01:45 2024 + By Klaus Rettinghaus Created by Etienne Darbellay, Jean-Francois Marti, Laurent Pugin, and Klaus Rettinghaus. This font is licensed under the SIL Open Font License \(http://scripts.sil.org/OFL\). -Version 5.2.87 +Version 5.2.88 @@ -182,7 +182,7 @@ d="M0 0c0 0 40 320 300 320s300 -320 300 -320h-32s-38 227 -268 227s-268 -227 -268 +d="M198 133c102 0 207 -45 207 -133c0 -92 -118 -133 -227 -133c-101 0 -178 46 -178 133c0 88 93 133 198 133z" /> @@ -2435,5 +2435,7 @@ d="M120.146 498.265c0.224774 0 17.1897 -1.82497 17.1897 -14.4827c0 -13.3186 -15. /> + diff --git a/fonts/Leipzig/Leipzig.ttf b/fonts/Leipzig/Leipzig.ttf index c674abd7203e3b26399e22ea0ade9812ed358cc6..8eafdf58bb892464610ba28c3e88d11d445bbb33 100644 GIT binary patch delta 3793 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 C?mL?R diff --git a/fonts/Leipzig/Leipzig.woff2 b/fonts/Leipzig/Leipzig.woff2 index 3f3c9daf924516db943e42317a9cb3a736be4d84..174d3b074450ed34b0a1f3d3c6a9ad5414227368 100644 GIT binary patch literal 45060 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

?0wt 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 literal 45096 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 OBjlO?)dlfgss97BHU2#S diff --git a/fonts/Leipzig/leipzig_metadata.json b/fonts/Leipzig/leipzig_metadata.json index c0a32252cc8..ee909e258c6 100644 --- a/fonts/Leipzig/leipzig_metadata.json +++ b/fonts/Leipzig/leipzig_metadata.json @@ -30,7 +30,7 @@ "tupletBracketThickness": 0.16 }, "fontName": "Leipzig", - "fontVersion": "5.2.87", + "fontVersion": "5.2.88", "glyphBBoxes": { "4stringTabClef": { "bBoxNE": [ @@ -4972,6 +4972,16 @@ -0.528 ] }, + "noteheadHalfFilled": { + "bBoxNE": [ + 1.256, + 0.552 + ], + "bBoxSW": [ + 0.0, + -0.528 + ] + }, "noteheadHalfWithX": { "bBoxNE": [ 1.256, @@ -7766,6 +7776,24 @@ 0.164 ] }, + "noteheadHalfFilled": { + "cutOutNW": [ + 0.14, + 0.24 + ], + "cutOutSE": [ + 1.076, + -0.24 + ], + "stemDownNW": [ + 0.0, + -0.144 + ], + "stemUpSE": [ + 1.256, + 0.164 + ] + }, "noteheadHalfWithX": { "cutOutNW": [ 0.14, @@ -7860,7 +7888,7 @@ 0.4 ], "cutOutSE": [ - 1.42, + 1.4, -0.4 ] }, diff --git a/fonts/supported.xml b/fonts/supported.xml index ccd9a8d22d7..695b187c50b 100644 --- a/fonts/supported.xml +++ b/fonts/supported.xml @@ -269,7 +269,7 @@ - + U+E0FF U+E0A0 diff --git a/include/vrv/smufl.h b/include/vrv/smufl.h index 5e70fa53c69..300b9559819 100644 --- a/include/vrv/smufl.h +++ b/include/vrv/smufl.h @@ -106,6 +106,7 @@ enum { SMUFL_E0F5_noteheadParenthesisLeft = 0xE0F5, SMUFL_E0F6_noteheadParenthesisRight = 0xE0F6, SMUFL_E0FA_noteheadWholeFilled = 0xE0FA, + SMUFL_E0FB_noteheadHalfFilled = 0xE0FB, SMUFL_E101_noteheadSlashHorizontalEnds = 0xE101, SMUFL_E102_noteheadSlashWhiteWhole = 0xE102, SMUFL_E103_noteheadSlashWhiteHalf = 0xE103, @@ -644,7 +645,7 @@ enum { }; /** The number of glyphs for verification **/ -#define SMUFL_COUNT 619 +#define SMUFL_COUNT 620 } // namespace vrv From 5dc72f67537f372c3d5e1652b496158031e3120c Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 26 Feb 2024 14:37:59 +0100 Subject: [PATCH 226/383] Add library installation paths [skip-ci] --- cmake/CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 0c865ce37df..817cbad031a 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -242,8 +242,15 @@ if (BUILD_AS_ANDROID_LIBRARY) target_link_libraries(verovio ${log-lib}) endif() -install( - TARGETS verovio DESTINATION bin +install(TARGETS verovio + # for executables and dll on Win + RUNTIME DESTINATION bin + # shared libraries + LIBRARY DESTINATION lib + # for static libraries + ARCHIVE DESTINATION lib + # public headers + INCLUDES DESTINATION include ) install( DIRECTORY ../data/ From 2ae160772f4671813e7dda4ba0e00ef413934dd5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 27 Feb 2024 18:38:18 +0100 Subject: [PATCH 227/383] add CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS [skip-ci] --- cmake/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 817cbad031a..19a3028ccad 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -175,6 +175,9 @@ set(all_SRC if (BUILD_AS_LIBRARY OR BUILD_AS_ANDROID_LIBRARY) message(STATUS "***** Building Verovio as shared library *****") + if(WIN32) + add_definitions(-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS) + endif() add_library(verovio SHARED ../tools/c_wrapper.cpp ${all_SRC}) ########### From 69a7fc5ea8de0bd80a8d58895827e11b2dee52fb Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 28 Feb 2024 14:27:59 +0100 Subject: [PATCH 228/383] Add cmake instruction for copying header files --- cmake/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 19a3028ccad..66030ba8155 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -179,6 +179,8 @@ if (BUILD_AS_LIBRARY OR BUILD_AS_ANDROID_LIBRARY) add_definitions(-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS) endif() add_library(verovio SHARED ../tools/c_wrapper.cpp ${all_SRC}) + # list all headers to be copied for all installation + file(GLOB_RECURSE all_HEADERS "../include/*/*.h*" "../libmei/*/*.h*") ########### # WASM BC # @@ -260,3 +262,7 @@ install( DESTINATION share/verovio FILES_MATCHING PATTERN "*.xml" PATTERN "*.svg" PATTERN "*.css" ) +# install all headers in /usr/local/include/verovio +if (BUILD_AS_LIBRARY) + install(FILES ${all_HEADERS} DESTINATION include/verovio) +endif() From 8092f1a3424f3d743102b132cfdfae341da7c7e8 Mon Sep 17 00:00:00 2001 From: andre2007 Date: Tue, 27 Feb 2024 20:41:16 +0100 Subject: [PATCH 229/383] Include info how to build verovio.dll on Windows using MS Build Tools --- bindings/c/README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/bindings/c/README.md b/bindings/c/README.md index c25a089845b..118a10a3edf 100644 --- a/bindings/c/README.md +++ b/bindings/c/README.md @@ -1,6 +1,9 @@ ## C function interface -To use Verovio with any language that supports a plain C function interface you will first need to build Verovio as a library +To use Verovio with any language that supports a plain C function interface you will first need to build Verovio as a library. +The compiled library (`libverovio.so`/`verovio.dll`) will contain callable C symbols. These wrapper symbols are defined in `./tools/c_wrapper.h` + +### Building libverovio.so on Linux ```sh cd tools @@ -8,7 +11,18 @@ cmake -DBUILD_AS_LIBRARY=ON . make ``` -The compiled library (`libverovio.so`) will contain callable C symbols. These wrapper symbols are defined in `./tools/c_wrapper.h` +### Building verovio.dll on Windows using Microsoft Visual Studio Build Tools 2022 + +In addition to Microsoft Visual Studio Build Tools 2022, also [Make](https://gnuwin32.sourceforge.net/packages/make.htm) is used. + +Open `x64 Native Tools Command Prompt for VS 2022` and enter: + +``` +cd cmake +cmake -G "Unix Makefiles" -DBUILD_AS_LIBRARY=ON -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE . +make +``` + ## Complete example in C From 1a51d4476b0dd992b70eac98e837a8fd54f96c22 Mon Sep 17 00:00:00 2001 From: Amedeo Sorpreso Date: Wed, 28 Feb 2024 16:59:39 +0100 Subject: [PATCH 230/383] Update vrv.cpp To ensure correct code formatting for floating-point values, two lines of code have been added to the "StringFormat" method, temporarily resetting the compiler's std::locale configurations. This prevents formatting issues stemming from specific OS or development environment settings, ensuring consistent and accurate numerical representation in the C++ source code during program execution. (At least, they do on my machine; I hope it's the same on yours... :) ) --- src/vrv.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vrv.cpp b/src/vrv.cpp index 8e4353b086c..f4ac75c2d33 100644 --- a/src/vrv.cpp +++ b/src/vrv.cpp @@ -211,12 +211,14 @@ void EnableLogToBuffer(bool value) std::string StringFormat(const char *fmt, ...) { + std::locale previousLocale = std::locale::global(std::locale("C")); std::string str(STRING_FORMAT_MAX_LEN, 0); va_list args; va_start(args, fmt); vsnprintf(&str[0], STRING_FORMAT_MAX_LEN, fmt, args); va_end(args); str.resize(strlen(str.data())); + std::locale::global(previousLocale); return str; } From 0d86031f3553eb59f06e975008c492ecd6095661 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 29 Feb 2024 09:01:02 +0100 Subject: [PATCH 231/383] Add c_wrapper.h to the library headers [skip-ci] --- cmake/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 66030ba8155..5d2b688547a 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -178,9 +178,9 @@ if (BUILD_AS_LIBRARY OR BUILD_AS_ANDROID_LIBRARY) if(WIN32) add_definitions(-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS) endif() - add_library(verovio SHARED ../tools/c_wrapper.cpp ${all_SRC}) + add_library(verovio SHARED "../tools/c_wrapper.cpp" ${all_SRC}) # list all headers to be copied for all installation - file(GLOB_RECURSE all_HEADERS "../include/*/*.h*" "../libmei/*/*.h*") + file(GLOB_RECURSE all_HEADERS "../include/*/*.h*" "../libmei/*/*.h*" "../tools/c_wrapper.h") ########### # WASM BC # From 114cb8ef9617eee28ce110de16b645981c2a77fe Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 29 Feb 2024 09:48:44 +0100 Subject: [PATCH 232/383] Update README.md for Android [skip-ci] --- bindings/android/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bindings/android/README.md b/bindings/android/README.md index d4d42bd7f94..bd9a2d5719a 100644 --- a/bindings/android/README.md +++ b/bindings/android/README.md @@ -1,4 +1,7 @@ -## About +## Documentation for Android + +> [!WARNING] +> This documentation has not been updated and might be obsolete This allows verovio to be built as a native library for Android. It does not include any Java bindings. @@ -50,4 +53,4 @@ Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env, jobject / std::string hello = "Hello from Verovio " + tk.GetVersion(); return env->NewStringUTF(hello.c_str()); } -``` \ No newline at end of file +``` From e2b12290ee0093aa256614dfbfcf08ca2100bdbe Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 29 Feb 2024 09:50:06 +0100 Subject: [PATCH 233/383] Update README.md for Qt [skip-ci] --- bindings/qt/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bindings/qt/README.md b/bindings/qt/README.md index cdfa3858604..192cbb79066 100644 --- a/bindings/qt/README.md +++ b/bindings/qt/README.md @@ -1,4 +1,9 @@ -## Build instructions +## Documentation for the Qt binding + +> [!WARNING] +> This documentation has not been updated and might be obsolete + +### Build instructions Building the demo for Qt involves following steps: @@ -30,7 +35,7 @@ Building the demo for Qt involves following steps: make -j8 ``` -## Start Qt Demo +### Start Qt Demo To start the Qt demo add the directories for the CPP and Qt libraries to LD_LIBRARY_PATH @@ -38,7 +43,7 @@ To start the Qt demo add the directories for the CPP and Qt libraries to LD_LIBR LD_LIBRARY_PATH=../../../tools/:../build-library ./verovio-qt-demo ``` -## Android +### Android The demo can also be compiled for Android. This requires following steps: From e51c4b2a7cb7f999cf82269cd68c0c974a7efde4 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 29 Feb 2024 09:51:45 +0100 Subject: [PATCH 234/383] Remove the C binding directory [skip-ci] * Now documented in https://book.verovio.org/installing-or-building-from-sources/library.html --- bindings/c/README.md | 39 --------------------------------------- bindings/c/main.c | 14 -------------- 2 files changed, 53 deletions(-) delete mode 100644 bindings/c/README.md delete mode 100644 bindings/c/main.c diff --git a/bindings/c/README.md b/bindings/c/README.md deleted file mode 100644 index 118a10a3edf..00000000000 --- a/bindings/c/README.md +++ /dev/null @@ -1,39 +0,0 @@ -## C function interface - -To use Verovio with any language that supports a plain C function interface you will first need to build Verovio as a library. -The compiled library (`libverovio.so`/`verovio.dll`) will contain callable C symbols. These wrapper symbols are defined in `./tools/c_wrapper.h` - -### Building libverovio.so on Linux - -```sh -cd tools -cmake -DBUILD_AS_LIBRARY=ON . -make -``` - -### Building verovio.dll on Windows using Microsoft Visual Studio Build Tools 2022 - -In addition to Microsoft Visual Studio Build Tools 2022, also [Make](https://gnuwin32.sourceforge.net/packages/make.htm) is used. - -Open `x64 Native Tools Command Prompt for VS 2022` and enter: - -``` -cd cmake -cmake -G "Unix Makefiles" -DBUILD_AS_LIBRARY=ON -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE . -make -``` - - -## Complete example in C - -To run the `main.c` example you can use gcc to compile the example and link to the pre-built library. - -```sh -gcc main.c -o main -L../../tools -lverovio -``` - -Run (without changing your default LD LIBRARY PATH): - -```sh -LD_LIBRARY_PATH=../../tools ./main -``` diff --git a/bindings/c/main.c b/bindings/c/main.c deleted file mode 100644 index be1d8f2bef9..00000000000 --- a/bindings/c/main.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "stdbool.h" -#include "stdio.h" - -// include the header with the C functions -#include "../../tools/c_wrapper.h" - -int main() -{ - printf("Calling constructor\n"); - void *pointer = vrvToolkit_constructorResourcePath("../../data"); - printf("Pointer value %p\n", pointer); - const char *options = vrvToolkit_getAvailableOptions(pointer); - printf("%s", options); -} From fd28caeaf01d04797e16db0690e7cc18c96a6573 Mon Sep 17 00:00:00 2001 From: Andre Pany Date: Sun, 3 Mar 2024 21:11:08 +0100 Subject: [PATCH 235/383] Add toolkit functions to c_wrapper --- tools/c_wrapper.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++- tools/c_wrapper.h | 20 ++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/tools/c_wrapper.cpp b/tools/c_wrapper.cpp index a4023388046..10e32fd86b2 100644 --- a/tools/c_wrapper.cpp +++ b/tools/c_wrapper.cpp @@ -115,6 +115,19 @@ const char *vrvToolkit_getHumdrum(void *tkPtr) return buffer; } +bool vrvToolkit_getHumdrumFile(void *tkPtr, const char *filename) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->GetHumdrumFile(filename); +} + +const char *vrvToolkit_getID(void *tkPtr) +{ + Toolkit *tk = static_cast(tkPtr); + tk->SetCString(tk->GetID()); + return tk->GetCString(); +} + const char *vrvToolkit_convertHumdrumToHumdrum(void *tkPtr, const char *humdrumData) { Toolkit *tk = static_cast(tkPtr); @@ -192,6 +205,19 @@ int vrvToolkit_getPageWithElement(void *tkPtr, const char *xmlId) return tk->GetPageWithElement(xmlId); } +const char *vrvToolkit_getResourcePath(void *tkPtr) +{ + Toolkit *tk = static_cast(tkPtr); + tk->SetCString(tk->GetResourcePath()); + return tk->GetCString(); +} + +int vrvToolkit_getScale(void *tkPtr) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->GetScale(); +} + double vrvToolkit_getTimeForElement(void *tkPtr, const char *xmlId) { Toolkit *tk = static_cast(tkPtr); @@ -218,6 +244,12 @@ bool vrvToolkit_loadData(void *tkPtr, const char *data) return tk->LoadData(data); } +bool vrvToolkit_loadFile(void *tkPtr, const char *filename) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->LoadFile(filename); +} + bool vrvToolkit_loadZipDataBase64(void *tkPtr, const char *data) { Toolkit *tk = static_cast(tkPtr); @@ -256,13 +288,25 @@ const char *vrvToolkit_renderToExpansionMap(void *tkPtr) return tk->GetCString(); } -const char *vrvToolkit_renderToMIDI(void *tkPtr, const char *c_options) +bool vrvToolkit_renderToExpansionMapFile(void *tkPtr, const char *filename) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->RenderToExpansionMapFile(filename); +} + +const char *vrvToolkit_renderToMIDI(void *tkPtr) { Toolkit *tk = static_cast(tkPtr); tk->SetCString(tk->RenderToMIDI()); return tk->GetCString(); } +bool vrvToolkit_renderToMIDIFile(void *tkPtr, const char *filename) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->RenderToMIDIFile(filename); +} + const char *vrvToolkit_renderToPAE(void *tkPtr) { Toolkit *tk = static_cast(tkPtr); @@ -270,6 +314,12 @@ const char *vrvToolkit_renderToPAE(void *tkPtr) return tk->GetCString(); } +bool vrvToolkit_renderToPAEFile(void *tkPtr, const char *filename) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->RenderToPAEFile(filename); +} + const char *vrvToolkit_renderToSVG(void *tkPtr, int page_no, bool xmlDeclaration) { Toolkit *tk = static_cast(tkPtr); @@ -277,6 +327,12 @@ const char *vrvToolkit_renderToSVG(void *tkPtr, int page_no, bool xmlDeclaration return tk->GetCString(); } +bool vrvToolkit_renderToSVGFile(void *tkPtr, const char *filename, int pageNo) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->RenderToSVGFile(filename, pageNo); +} + const char *vrvToolkit_renderToTimemap(void *tkPtr, const char *c_options) { Toolkit *tk = static_cast(tkPtr); @@ -284,6 +340,12 @@ const char *vrvToolkit_renderToTimemap(void *tkPtr, const char *c_options) return tk->GetCString(); } +bool vrvToolkit_renderToTimemapFile(void *tkPtr, const char *filename, const char *c_options) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->RenderToTimemapFile(filename, c_options); +} + void vrvToolkit_resetOptions(void *tkPtr) { Toolkit *tk = static_cast(tkPtr); @@ -296,18 +358,48 @@ void vrvToolkit_resetXmlIdSeed(void *tkPtr, int seed) tk->ResetXmlIdSeed(seed); } +bool vrvToolkit_saveFile(void *tkPtr, const char *filename, const char *c_options) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->SaveFile(filename, c_options); +} + bool vrvToolkit_select(void *tkPtr, const char *selection) { Toolkit *tk = static_cast(tkPtr); return tk->Select(selection); } +bool vrvToolkit_setInputFrom(void *tkPtr, const char *inputFrom) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->SetInputFrom(inputFrom); +} + bool vrvToolkit_setOptions(void *tkPtr, const char *options) { Toolkit *tk = static_cast(tkPtr); return tk->SetOptions(options); } +bool vrvToolkit_setOutputTo(void *tkPtr, const char *outputTo) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->SetOutputTo(outputTo); +} + +bool vrvToolkit_setResourcePath(void *tkPtr, const char *path) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->SetResourcePath(path); +} + +bool vrvToolkit_setScale(void *tkPtr, int scale) +{ + Toolkit *tk = static_cast(tkPtr); + return tk->SetScale(scale); +} + const char *vrvToolkit_validatePAE(void *tkPtr, const char *data) { Toolkit *tk = static_cast(tkPtr); @@ -315,4 +407,11 @@ const char *vrvToolkit_validatePAE(void *tkPtr, const char *data) return tk->GetCString(); } +const char *vrvToolkit_validatePAEFile(void *tkPtr, const char *filename) +{ + Toolkit *tk = static_cast(tkPtr); + tk->SetCString(tk->ValidatePAEFile(filename)); + return tk->GetCString(); +} + } // extern C diff --git a/tools/c_wrapper.h b/tools/c_wrapper.h index 21ef1b7db7b..9b221a59828 100644 --- a/tools/c_wrapper.h +++ b/tools/c_wrapper.h @@ -21,6 +21,7 @@ void *vrvToolkit_constructorResourcePath(const char *resourcePath); void vrvToolkit_destructor(void *tkPtr); bool vrvToolkit_edit(void *tkPtr, const char *editorAction); +const char *vrvToolkit_editInfo(void *tkPtr); const char *vrvToolkit_getAvailableOptions(void *tkPtr); const char *vrvToolkit_getDefaultOptions(void *tkPtr); const char *vrvToolkit_getDescriptiveFeatures(void *tkPtr, const char *options); @@ -28,6 +29,8 @@ const char *vrvToolkit_getElementAttr(void *tkPtr, const char *xmlId); const char *vrvToolkit_getElementsAtTime(void *tkPtr, int millisec); const char *vrvToolkit_getExpansionIdsForElement(void *tkPtr, const char *xmlId); const char *vrvToolkit_getHumdrum(void *tkPtr); +bool vrvToolkit_getHumdrumFile(void *tkPtr, const char *filename); +const char *vrvToolkit_getID(void *tkPtr); const char *vrvToolkit_convertHumdrumToHumdrum(void *tkPtr, const char *humdrumData); const char *vrvToolkit_convertHumdrumToMIDI(void *tkPtr, const char *humdrumData); const char *vrvToolkit_convertMEIToHumdrum(void *tkPtr, const char *meiData); @@ -39,24 +42,39 @@ const char *vrvToolkit_getOptions(void *tkPtr); const char *vrvToolkit_getOptionUsageString(void *tkPtr); int vrvToolkit_getPageCount(void *tkPtr); int vrvToolkit_getPageWithElement(void *tkPtr, const char *xmlId); +const char *vrvToolkit_getResourcePath(void *tkPtr); +int vrvToolkit_getScale(void *tkPtr); double vrvToolkit_getTimeForElement(void *tkPtr, const char *xmlId); +const char *vrvToolkit_getTimesForElement(void *tkPtr, const char *xmlId); const char *vrvToolkit_getVersion(void *tkPtr); bool vrvToolkit_loadData(void *tkPtr, const char *data); +bool vrvToolkit_loadFile(void *tkPtr, const char *filename); bool vrvToolkit_loadZipDataBase64(void *tkPtr, const char *data); bool vrvToolkit_loadZipDataBuffer(void *tkPtr, const unsigned char *data, int length); void vrvToolkit_redoLayout(void *tkPtr, const char *c_options); void vrvToolkit_redoPagePitchPosLayout(void *tkPtr); const char *vrvToolkit_renderData(void *tkPtr, const char *data, const char *options); const char *vrvToolkit_renderToExpansionMap(void *tkPtr); -const char *vrvToolkit_renderToMIDI(void *tkPtr, const char *c_options); +bool vrvToolkit_renderToExpansionMapFile(void *tkPtr, const char *filename); +const char *vrvToolkit_renderToMIDI(void *tkPtr); +bool vrvToolkit_renderToMIDIFile(void *tkPtr, const char *filename); const char *vrvToolkit_renderToPAE(void *tkPtr); +bool vrvToolkit_renderToPAEFile(void *tkPtr, const char *filename); const char *vrvToolkit_renderToSVG(void *tkPtr, int page_no, bool xmlDeclaration); +bool vrvToolkit_renderToSVGFile(void *tkPtr, const char *filename, int pageNo); const char *vrvToolkit_renderToTimemap(void *tkPtr, const char *c_options); +bool vrvToolkit_renderToTimemapFile(void *tkPtr, const char *filename, const char *c_options); void vrvToolkit_resetOptions(void *tkPtr); void vrvToolkit_resetXmlIdSeed(void *tkPtr, int seed); +bool vrvToolkit_saveFile(void *tkPtr, const char *filename, const char *c_options); bool vrvToolkit_select(void *tkPtr, const char *selection); +bool vrvToolkit_setInputFrom(void *tkPtr, const char *inputFrom); bool vrvToolkit_setOptions(void *tkPtr, const char *options); +bool vrvToolkit_setOutputTo(void *tkPtr, const char *outputTo); +bool vrvToolkit_setResourcePath(void *tkPtr, const char *path); +bool vrvToolkit_setScale(void *tkPtr, int scale); const char *vrvToolkit_validatePAE(void *tkPtr, const char *data); +const char *vrvToolkit_validatePAEFile(void *tkPtr, const char *filename); #ifdef __cplusplus } // extern C From d5d8b37e46962dc050e23655d3a216228d9956dd Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Wed, 6 Mar 2024 18:15:29 +0100 Subject: [PATCH 236/383] Fix issue #3605: draw colored notes and dur="2" with empty note head --- src/view_element.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 88a2d28ec34..3c698c7add7 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1488,8 +1488,6 @@ void View::DrawNote(DeviceContext *dc, LayerElement *element, Layer *layer, Staf } drawingDur = DUR_4; } - drawingDur = ((note->GetColored() == BOOLEAN_true) && drawingDur > DUR_1) ? (drawingDur + 1) : drawingDur; - if (drawingDur < DUR_BR) { this->DrawMaximaToBrevis(dc, noteY, element, layer, staff); } @@ -1497,7 +1495,15 @@ void View::DrawNote(DeviceContext *dc, LayerElement *element, Layer *layer, Staf // Whole notes char32_t fontNo; if (note->GetColored() == BOOLEAN_true) { - fontNo = (drawingDur == DUR_1) ? SMUFL_E0FA_noteheadWholeFilled : SMUFL_E0A3_noteheadHalf; + if (DUR_1 == drawingDur) { + fontNo = SMUFL_E0FA_noteheadWholeFilled; + } + else if (DUR_2 == drawingDur) { + fontNo = SMUFL_E0FB_noteheadHalfFilled; + } + else { + fontNo = SMUFL_E0A3_noteheadHalf; + } } else { fontNo = note->GetNoteheadGlyph(drawingDur); From a1c0e3f4430a5101bd809aa43cf366f6ea16e7fd Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 11 Mar 2024 22:59:11 +0100 Subject: [PATCH 237/383] Fix lyrics position in midi output for in notes <= quarter --- src/midifunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/midifunctor.cpp b/src/midifunctor.cpp index 5bf7f6246e8..8e19d336ed8 100644 --- a/src/midifunctor.cpp +++ b/src/midifunctor.cpp @@ -798,7 +798,7 @@ FunctorCode GenerateMIDIFunctor::VisitStaffDef(const StaffDef *staffDef) FunctorCode GenerateMIDIFunctor::VisitSyl(const Syl *syl) { - const int startTime = m_totalTime + m_lastNote->GetScoreTimeOnset(); + const double startTime = m_totalTime + m_lastNote->GetScoreTimeOnset(); const std::string sylText = UTF32to8(syl->GetText()); m_midiFile->addLyric(m_midiTrack, startTime * m_midiFile->getTPQ(), sylText); From 203c3ea0e2e2b7cb3e71538c3775d04508ac31b7 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 13:09:05 -0700 Subject: [PATCH 238/383] Added ClearCoords call when editing a beam --- src/editortoolkit_cmn.cpp | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 6d8f85f49a8..3369563c761 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -213,7 +213,7 @@ bool EditorToolkitCMN::Delete(std::string &elementId) { Object *element = this->GetElement(elementId); if (!element) return false; - + if (element->Is(NOTE)) { return this->DeleteNote(vrv_cast(element)); } @@ -247,15 +247,15 @@ bool EditorToolkitCMN::KeyDown(std::string &elementId, int key, bool shiftKey, b // For elements whose y-position corresponds to a certain pitch if (element->HasInterface(INTERFACE_PITCH)) { - PitchInterface *interface = element->GetPitchInterface(); - assert(interface); + PitchInterface* pitch_interface = element->GetPitchInterface(); + assert(pitch_interface); int step; switch (key) { case KEY_UP: step = 1; break; case KEY_DOWN: step = -1; break; default: step = 0; } - interface->AdjustPitchByOffset(step); + pitch_interface->AdjustPitchByOffset(step); return true; } return false; @@ -301,11 +301,11 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); - assert(interface); + TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); + assert(timespan_interface); measure->AddChild(element); - interface->SetStartid("#" + startid); - interface->SetEndid("#" + endid); + timespan_interface->SetStartid("#" + startid); + timespan_interface->SetEndid("#" + endid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -512,7 +512,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) Chord *chord = note->IsChordTone(); Beam *beam = note->GetAncestorBeam(); - + if (chord) { if (chord->HasEditorialContent()) { LogInfo("Deleting a note in a chord that has editorial content is not possible"); @@ -560,6 +560,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } } else if (beam) { + // If the beam has exactly 2 notes (take apart and leave a single note and a rest) if ((int)beam->m_beamSegment.GetElementCoordRefs()->size() == 2) { bool insertBefore = true; LayerElement *otherElement = beam->m_beamSegment.GetElementCoordRefs()->back()->m_element; @@ -584,7 +585,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) m_chainedId = rest->GetID(); return true; } - if (beam->IsFirstIn(note)) { + // If the beam has more than 2 and this is first + else if (beam->IsFirstIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); Object *parent = beam->GetParent(); @@ -592,8 +594,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) parent->InsertBefore(beam, rest); beam->DeleteChild(note); m_chainedId = rest->GetID(); - return true; } + // If the beam has more than 2 and this is last else if (beam->IsLastIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); @@ -602,18 +604,26 @@ bool EditorToolkitCMN::DeleteNote(Note *note) parent->InsertAfter(beam, rest); beam->DeleteChild(note); m_chainedId = rest->GetID(); - return true; } + // If the beam has more than 2 and this in the middle else { - Rest *rest = new Rest(); + Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); beam->ReplaceChild(note, rest); delete note; m_chainedId = rest->GetID(); - return true; } + + // All but the first IF statement branches lead here + + // Clearing the coords here fixes an error where the children get updated, but the + // internal m_beamElementCoordRefs does not. By clearing it, the system is forced + // to update that structure to reflect the current children. + beam->ClearCoords(); + return true; } else { + // Deal with just a single note (Not in beam or chord) Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); Object *parent = note->GetParent(); From 51503fcbece373a87f4c973dd1c8aa5b23519730 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 15:47:57 -0700 Subject: [PATCH 239/383] Adjusted to meet some existing comments --- src/editortoolkit_cmn.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 3369563c761..0ab3d479c20 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -213,7 +213,6 @@ bool EditorToolkitCMN::Delete(std::string &elementId) { Object *element = this->GetElement(elementId); if (!element) return false; - if (element->Is(NOTE)) { return this->DeleteNote(vrv_cast(element)); } @@ -348,10 +347,10 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); - assert(interface); + TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); + assert(timespan_interface); measure->AddChild(element); - interface->SetStartid("#" + startid); + timespan_interface->SetStartid("#" + startid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -512,7 +511,6 @@ bool EditorToolkitCMN::DeleteNote(Note *note) Chord *chord = note->IsChordTone(); Beam *beam = note->GetAncestorBeam(); - if (chord) { if (chord->HasEditorialContent()) { LogInfo("Deleting a note in a chord that has editorial content is not possible"); @@ -581,6 +579,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } beam->DetachChild(otherElement->GetIdx()); parent->ReplaceChild(beam, otherElement); + otherElement-> + // Here delete beam; m_chainedId = rest->GetID(); return true; From 0270685d76d857c66d780c4f8b4b2adb22215434 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 15:53:35 -0700 Subject: [PATCH 240/383] fixed indentation --- src/editortoolkit_cmn.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 0ab3d479c20..38b5f77eb74 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -558,7 +558,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } } else if (beam) { - // If the beam has exactly 2 notes (take apart and leave a single note and a rest) + // If the beam has exactly 2 notes (take apart and leave a single note and a rest) if ((int)beam->m_beamSegment.GetElementCoordRefs()->size() == 2) { bool insertBefore = true; LayerElement *otherElement = beam->m_beamSegment.GetElementCoordRefs()->back()->m_element; @@ -579,13 +579,11 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } beam->DetachChild(otherElement->GetIdx()); parent->ReplaceChild(beam, otherElement); - otherElement-> - // Here delete beam; m_chainedId = rest->GetID(); return true; } - // If the beam has more than 2 and this is first + // If the beam has more than 2 and this is first else if (beam->IsFirstIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); @@ -595,7 +593,7 @@ bool EditorToolkitCMN::DeleteNote(Note *note) beam->DeleteChild(note); m_chainedId = rest->GetID(); } - // If the beam has more than 2 and this is last + // If the beam has more than 2 and this is last else if (beam->IsLastIn(note)) { Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); @@ -605,25 +603,25 @@ bool EditorToolkitCMN::DeleteNote(Note *note) beam->DeleteChild(note); m_chainedId = rest->GetID(); } - // If the beam has more than 2 and this in the middle + // If the beam has more than 2 and this in the middle else { - Rest *rest = new Rest(); + Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); beam->ReplaceChild(note, rest); delete note; m_chainedId = rest->GetID(); } - - // All but the first IF statement branches lead here - - // Clearing the coords here fixes an error where the children get updated, but the - // internal m_beamElementCoordRefs does not. By clearing it, the system is forced - // to update that structure to reflect the current children. - beam->ClearCoords(); - return true; + + // All but the first IF statement branches lead here + + // Clearing the coords here fixes an error where the children get updated, but the + // internal m_beamElementCoordRefs does not. By clearing it, the system is forced + // to update that structure to reflect the current children. + beam->ClearCoords(); + return true; } else { - // Deal with just a single note (Not in beam or chord) + // Deal with just a single note (Not in beam or chord) Rest *rest = new Rest(); rest->DurationInterface::operator=(*note); Object *parent = note->GetParent(); From ef003b1983aaf869a7cafd12f8a82718e06418da Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 15:59:23 -0700 Subject: [PATCH 241/383] fixed known clang issues --- src/editortoolkit_cmn.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 38b5f77eb74..4fd5b498a38 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -246,7 +246,7 @@ bool EditorToolkitCMN::KeyDown(std::string &elementId, int key, bool shiftKey, b // For elements whose y-position corresponds to a certain pitch if (element->HasInterface(INTERFACE_PITCH)) { - PitchInterface* pitch_interface = element->GetPitchInterface(); + PitchInterface *pitch_interface = element->GetPitchInterface(); assert(pitch_interface); int step; switch (key) { @@ -613,10 +613,9 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } // All but the first IF statement branches lead here - - // Clearing the coords here fixes an error where the children get updated, but the - // internal m_beamElementCoordRefs does not. By clearing it, the system is forced - // to update that structure to reflect the current children. + /* Clearing the coords here fixes an error where the children get updated, but the + * internal m_beamElementCoordRefs does not. By clearing it, the system is forced + * to update that structure to reflect the current children. */ beam->ClearCoords(); return true; } From dad1b1cee2b4492f08393dbe4c8c3e8f6261743a Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 6 Mar 2024 16:08:08 -0700 Subject: [PATCH 242/383] Fixed clang problem --- src/editortoolkit_cmn.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index 4fd5b498a38..ed7ebd5fa03 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -611,7 +611,6 @@ bool EditorToolkitCMN::DeleteNote(Note *note) delete note; m_chainedId = rest->GetID(); } - // All but the first IF statement branches lead here /* Clearing the coords here fixes an error where the children get updated, but the * internal m_beamElementCoordRefs does not. By clearing it, the system is forced From 21b28fac1ef1d935aa277c18ff8e0fe6987bb096 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Thu, 7 Mar 2024 09:19:27 -0700 Subject: [PATCH 243/383] Reset interface variable name --- src/editortoolkit_cmn.cpp | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/editortoolkit_cmn.cpp b/src/editortoolkit_cmn.cpp index ed7ebd5fa03..18cbed036a5 100644 --- a/src/editortoolkit_cmn.cpp +++ b/src/editortoolkit_cmn.cpp @@ -246,15 +246,15 @@ bool EditorToolkitCMN::KeyDown(std::string &elementId, int key, bool shiftKey, b // For elements whose y-position corresponds to a certain pitch if (element->HasInterface(INTERFACE_PITCH)) { - PitchInterface *pitch_interface = element->GetPitchInterface(); - assert(pitch_interface); + PitchInterface *interface = element->GetPitchInterface(); + assert(interface); int step; switch (key) { case KEY_UP: step = 1; break; case KEY_DOWN: step = -1; break; default: step = 0; } - pitch_interface->AdjustPitchByOffset(step); + interface->AdjustPitchByOffset(step); return true; } return false; @@ -300,11 +300,11 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); - assert(timespan_interface); + TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); + assert(interface); measure->AddChild(element); - timespan_interface->SetStartid("#" + startid); - timespan_interface->SetEndid("#" + endid); + interface->SetStartid("#" + startid); + interface->SetEndid("#" + endid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -347,10 +347,10 @@ bool EditorToolkitCMN::Insert(std::string &elementType, std::string const &start } assert(element); - TimeSpanningInterface *timespan_interface = element->GetTimeSpanningInterface(); - assert(timespan_interface); + TimeSpanningInterface *interface = element->GetTimeSpanningInterface(); + assert(interface); measure->AddChild(element); - timespan_interface->SetStartid("#" + startid); + interface->SetStartid("#" + startid); m_chainedId = element->GetID(); m_editInfo.import("uuid", element->GetID()); @@ -451,7 +451,8 @@ bool EditorToolkitCMN::InsertNote(Object *object) } if (currentNote->HasEditorialContent()) { - LogInfo("Inserting a note where a note has editorial content is not possible"); + LogInfo("Inserting a note where a note has editorial content is not " + "possible"); return false; } @@ -513,7 +514,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) Beam *beam = note->GetAncestorBeam(); if (chord) { if (chord->HasEditorialContent()) { - LogInfo("Deleting a note in a chord that has editorial content is not possible"); + LogInfo("Deleting a note in a chord that has editorial content is not " + "possible"); return false; } int count = chord->GetChildCount(NOTE, UNLIMITED_DEPTH); @@ -558,7 +560,8 @@ bool EditorToolkitCMN::DeleteNote(Note *note) } } else if (beam) { - // If the beam has exactly 2 notes (take apart and leave a single note and a rest) + // If the beam has exactly 2 notes (take apart and leave a single note and a + // rest) if ((int)beam->m_beamSegment.GetElementCoordRefs()->size() == 2) { bool insertBefore = true; LayerElement *otherElement = beam->m_beamSegment.GetElementCoordRefs()->back()->m_element; @@ -612,9 +615,10 @@ bool EditorToolkitCMN::DeleteNote(Note *note) m_chainedId = rest->GetID(); } // All but the first IF statement branches lead here - /* Clearing the coords here fixes an error where the children get updated, but the - * internal m_beamElementCoordRefs does not. By clearing it, the system is forced - * to update that structure to reflect the current children. */ + /* Clearing the coords here fixes an error where the children get updated, + * but the internal m_beamElementCoordRefs does not. By clearing it, the + * system is forced to update that structure to reflect the current + * children. */ beam->ClearCoords(); return true; } From f77112a40ab3bad5c49fec241b6430e30d5f9d53 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Mon, 11 Mar 2024 17:36:34 +0100 Subject: [PATCH 244/383] Refactor PAE Duration handling This change extracts the PAE duration handling into a static method that can be used to convert durations to their PAE representations. It also adds a new feature to the feature extractor that includes the note duration on the PAE note. --- include/vrv/featureextractor.h | 1 + include/vrv/iopae.h | 4 +++ src/featureextractor.cpp | 13 +++++++- src/iopae.cpp | 57 ++++++++++++++++++++-------------- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/include/vrv/featureextractor.h b/include/vrv/featureextractor.h index 0c049af5ec3..21a74630489 100644 --- a/include/vrv/featureextractor.h +++ b/include/vrv/featureextractor.h @@ -55,6 +55,7 @@ class FeatureExtractor { std::list m_previousNotes; jsonxx::Array m_pitchesChromatic; + jsonxx::Array m_pitchesChromaticWithDuration; jsonxx::Array m_pitchesDiatonic; jsonxx::Array m_pitchesIds; diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index 7c1eb5c76d5..7d40d7db64e 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -91,6 +91,10 @@ class PAEOutput : public Output { */ bool WriteObjectEnd(Object *object) override; + /** + * Helper method to return a string representation of the PAE duration. + */ + static std::string GetPaeDur(data_DURATION dur, int ndots); private: /** * @name Methods for writing containers (measures, staff, etc) scoreDef and related. diff --git a/src/featureextractor.cpp b/src/featureextractor.cpp index bf9ccfc8cf7..cc0854ce3dd 100644 --- a/src/featureextractor.cpp +++ b/src/featureextractor.cpp @@ -17,6 +17,7 @@ #include "chord.h" #include "doc.h" #include "gracegrp.h" +#include "iopae.h" #include "layer.h" #include "mdiv.h" #include "measure.h" @@ -70,10 +71,16 @@ void FeatureExtractor::Extract(const Object *object) } std::stringstream pitch; + std::stringstream pitchWithDuration; + + pitchWithDuration << PAEOutput::GetPaeDur(note->GetDur(), note->GetDots()); + data_OCTAVE oct = note->GetOct(); char octSign = (oct > 3) ? '\'' : ','; int signCount = (oct > 3) ? (oct - 3) : (4 - oct); - pitch << std::string(signCount, octSign); + std::string octaves = std::string(signCount, octSign); + pitch << octaves; + pitchWithDuration << octaves; const Accid *accid = vrv_cast(note->FindDescendantByType(ACCID)); if (accid) { @@ -98,13 +105,16 @@ void FeatureExtractor::Extract(const Object *object) default: accidStr = accidStrWritten; } pitch << accidStr; + pitchWithDuration << accidStr; } std::string pname = note->AttPitch::PitchnameToStr(note->GetPname()); std::transform(pname.begin(), pname.end(), pname.begin(), ::toupper); pitch << pname; + pitchWithDuration << pname; m_pitchesChromatic << pitch.str(); + m_pitchesChromaticWithDuration << pitchWithDuration.str(); m_pitchesDiatonic << pname; jsonxx::Array pitchesIds; pitchesIds << note->GetID(); @@ -145,6 +155,7 @@ void FeatureExtractor::ToJson(std::string &output) { jsonxx::Object o; + o << "pitchesChromaticWithDuration" << m_pitchesChromaticWithDuration; o << "pitchesChromatic" << m_pitchesChromatic; o << "pitchesDiatonic" << m_pitchesDiatonic; o << "pitchesIds" << m_pitchesIds; diff --git a/src/iopae.cpp b/src/iopae.cpp index 938efd8d54f..0c730ac9eb1 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -542,6 +542,38 @@ void PAEOutput::WriteTupletEnd(Tuplet *tuplet) m_streamStringOutput << ";" << tuplet->GetNum() << ")"; } +std::string PAEOutput::GetPaeDur(data_DURATION ndur, int ndots) +{ + std::string dur; + switch (ndur) { + case (DURATION_long): dur = "0"; break; + case (DURATION_breve): dur = "9"; break; + case (DURATION_1): dur = "1"; break; + case (DURATION_2): dur = "2"; break; + case (DURATION_4): dur = "4"; break; + case (DURATION_8): dur = "8"; break; + case (DURATION_16): dur = "6"; break; + case (DURATION_32): dur = "3"; break; + case (DURATION_64): dur = "5"; break; + case (DURATION_128): dur = "7"; break; + case (DURATION_maxima): dur = "0"; break; + case (DURATION_longa): dur = "0"; break; + case (DURATION_brevis): dur = "9"; break; + case (DURATION_semibrevis): dur = "1"; break; + case (DURATION_minima): dur = "2"; break; + case (DURATION_semiminima): dur = "4"; break; + case (DURATION_fusa): dur = "8"; break; + case (DURATION_semifusa): dur = "6"; break; + default: LogWarning("Unsupported duration"); dur = "4"; + } + + if (ndots > 0) { + dur += std::string(ndots, '.'); + } + + return dur; +} + void PAEOutput::WriteDur(DurationInterface *interface) { assert(interface); @@ -550,30 +582,7 @@ void PAEOutput::WriteDur(DurationInterface *interface) if ((interface->GetDur() != m_currentDur) || (ndots != m_currentDots)) { m_currentDur = interface->GetDur(); m_currentDots = ndots; - std::string dur; - switch (m_currentDur) { - case (DURATION_long): dur = "0"; break; - case (DURATION_breve): dur = "9"; break; - case (DURATION_1): dur = "1"; break; - case (DURATION_2): dur = "2"; break; - case (DURATION_4): dur = "4"; break; - case (DURATION_8): dur = "8"; break; - case (DURATION_16): dur = "6"; break; - case (DURATION_32): dur = "3"; break; - case (DURATION_64): dur = "5"; break; - case (DURATION_128): dur = "7"; break; - case (DURATION_maxima): dur = "0"; break; - case (DURATION_longa): dur = "0"; break; - case (DURATION_brevis): dur = "9"; break; - case (DURATION_semibrevis): dur = "1"; break; - case (DURATION_minima): dur = "2"; break; - case (DURATION_semiminima): dur = "4"; break; - case (DURATION_fusa): dur = "8"; break; - case (DURATION_semifusa): dur = "6"; break; - default: LogWarning("Unsupported duration"); dur = "4"; - } - m_streamStringOutput << dur; - m_streamStringOutput << std::string(m_currentDots, '.'); + m_streamStringOutput << GetPaeDur(interface->GetDur(), m_currentDots); } } From 931bb136585e9107bde25cc002d6d604dd9f7af7 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Mon, 11 Mar 2024 17:43:30 +0100 Subject: [PATCH 245/383] Formatting --- include/vrv/iopae.h | 1 + src/featureextractor.cpp | 4 ++-- src/iopae.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index 7d40d7db64e..77313365e15 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -95,6 +95,7 @@ class PAEOutput : public Output { * Helper method to return a string representation of the PAE duration. */ static std::string GetPaeDur(data_DURATION dur, int ndots); + private: /** * @name Methods for writing containers (measures, staff, etc) scoreDef and related. diff --git a/src/featureextractor.cpp b/src/featureextractor.cpp index cc0854ce3dd..fad1e07b47b 100644 --- a/src/featureextractor.cpp +++ b/src/featureextractor.cpp @@ -72,9 +72,9 @@ void FeatureExtractor::Extract(const Object *object) std::stringstream pitch; std::stringstream pitchWithDuration; - + pitchWithDuration << PAEOutput::GetPaeDur(note->GetDur(), note->GetDots()); - + data_OCTAVE oct = note->GetOct(); char octSign = (oct > 3) ? '\'' : ','; int signCount = (oct > 3) ? (oct - 3) : (4 - oct); diff --git a/src/iopae.cpp b/src/iopae.cpp index 0c730ac9eb1..2c8f098e8a2 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -566,7 +566,7 @@ std::string PAEOutput::GetPaeDur(data_DURATION ndur, int ndots) case (DURATION_semifusa): dur = "6"; break; default: LogWarning("Unsupported duration"); dur = "4"; } - + if (ndots > 0) { dur += std::string(ndots, '.'); } From 19a7eea0bd3ad79aad5f8008343ca7c8a692ca4b Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Tue, 12 Mar 2024 12:00:13 +0100 Subject: [PATCH 246/383] Update src/iopae.cpp Co-authored-by: Laurent Pugin --- src/iopae.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iopae.cpp b/src/iopae.cpp index 2c8f098e8a2..1e844d31a06 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -582,7 +582,7 @@ void PAEOutput::WriteDur(DurationInterface *interface) if ((interface->GetDur() != m_currentDur) || (ndots != m_currentDots)) { m_currentDur = interface->GetDur(); m_currentDots = ndots; - m_streamStringOutput << GetPaeDur(interface->GetDur(), m_currentDots); + m_streamStringOutput << PAEOutput::GetPaeDur(interface->GetDur(), m_currentDots); } } From 9d4050029ab132aa91051435d6860ea2dc5acd0d Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 17 Mar 2024 18:29:36 +0100 Subject: [PATCH 247/383] Update Verovio.podspec to c++20 [skip-ci] --- Verovio.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Verovio.podspec b/Verovio.podspec index e401987e0fa..93b69a5ae10 100644 --- a/Verovio.podspec +++ b/Verovio.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = '14.0' s.osx.deployment_target = '10.15' s.pod_target_xcconfig = { - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", "CLANG_CXX_LIBRARY" => "libc++", "GCC_C_LANGUAGE_STANDARD" => "gnu11", "GCC_DYNAMIC_NO_PIC" => "NO", From dd1aa74f56126d77449ff155f59f664045a1f06f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:45:18 +0000 Subject: [PATCH 248/383] Bump black from 22.12.0 to 24.3.0 in /fonts Bumps [black](https://github.com/psf/black) from 22.12.0 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.12.0...24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- fonts/poetry.lock | 60 ++++++++++++++++++++++++++++++-------------- fonts/pyproject.toml | 2 +- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/fonts/poetry.lock b/fonts/poetry.lock index bd9153bf74d..53c2a8f35ed 100644 --- a/fonts/poetry.lock +++ b/fonts/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "astroid" @@ -39,36 +39,47 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "black" -version = "22.12.0" +version = "24.3.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" +packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -389,6 +400,17 @@ files = [ {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, ] +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + [[package]] name = "parso" version = "0.8.3" @@ -767,4 +789,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "8990a11839baa27daa52ec96424fdaf96f294140034d66d66d7fde50197b0749" +content-hash = "13dc88eb6e57ab4192765ec4d46f48aa0091403e19806d5c3ed58ffe35a76464" diff --git a/fonts/pyproject.toml b/fonts/pyproject.toml index 3bd27e61a62..6c68d206a6d 100644 --- a/fonts/pyproject.toml +++ b/fonts/pyproject.toml @@ -12,7 +12,7 @@ svgpathtools = "^1.6.0" [tool.poetry.group.dev.dependencies] ipython = "^8.10.0" -black = "^22.8.0" +black = "^24.3.0" mypy = "^0.971" pylint = "^2.15.3" From 8904eb941a942b7217de664b3941a3ca1b5aff9b Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 21 Mar 2024 14:51:17 +0100 Subject: [PATCH 249/383] Set locale in toolkit --- include/vrv/toolkit.h | 2 ++ src/toolkit.cpp | 5 +++++ src/vrv.cpp | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 14a5499baeb..e607b0d4ba6 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -790,6 +790,8 @@ class Toolkit { Options *m_options; + std::locale m_previousLocale; + /** * The C buffer string. */ diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 3552ff2279f..9f82ee8004c 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -69,6 +69,9 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; + // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) + m_previousLocale = std::locale::global(std::locale::classic()); + if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); resources.InitFonts(); @@ -85,6 +88,8 @@ Toolkit::Toolkit(bool initFont) Toolkit::~Toolkit() { + std::locale::global(m_previousLocale); + if (m_humdrumBuffer) { free(m_humdrumBuffer); m_humdrumBuffer = NULL; diff --git a/src/vrv.cpp b/src/vrv.cpp index f4ac75c2d33..8e4353b086c 100644 --- a/src/vrv.cpp +++ b/src/vrv.cpp @@ -211,14 +211,12 @@ void EnableLogToBuffer(bool value) std::string StringFormat(const char *fmt, ...) { - std::locale previousLocale = std::locale::global(std::locale("C")); std::string str(STRING_FORMAT_MAX_LEN, 0); va_list args; va_start(args, fmt); vsnprintf(&str[0], STRING_FORMAT_MAX_LEN, fmt, args); va_end(args); str.resize(strlen(str.data())); - std::locale::global(previousLocale); return str; } From 5f1a3a761a45a69cb2f01df631c16e4bfed812a0 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 21 Mar 2024 16:30:44 +0100 Subject: [PATCH 250/383] Add option --set-locale --- include/vrv/options.h | 1 + include/vrv/toolkit.h | 2 +- src/options.cpp | 4 ++++ src/toolkit.cpp | 12 ++++++++---- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/vrv/options.h b/include/vrv/options.h index 98a66d74f36..0e9160240a7 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -652,6 +652,7 @@ class Options { OptionBool m_preserveAnalyticalMarkup; OptionBool m_removeIds; OptionBool m_scaleToPageSize; + OptionBool m_setLocale; OptionBool m_showRuntime; OptionBool m_shrinkToFit; OptionIntMap m_smuflTextFont; diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index e607b0d4ba6..718df09219a 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -790,7 +790,7 @@ class Toolkit { Options *m_options; - std::locale m_previousLocale; + std::optional m_previousLocale; /** * The C buffer string. diff --git a/src/options.cpp b/src/options.cpp index 0a24f1c16bd..a873af78cd4 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1135,6 +1135,10 @@ Options::Options() m_scaleToPageSize.Init(false); this->Register(&m_scaleToPageSize, "scaleToPageSize", &m_general); + m_setLocale.SetInfo("Set the global locale", "Changes the global locale to C (this is not thread-safe)"); + m_setLocale.Init(false); + this->Register(&m_setLocale, "setLocale", &m_general); + m_showRuntime.SetInfo("Show runtime on CLI", "Display the total runtime on command-line"); m_showRuntime.Init(false); this->Register(&m_showRuntime, "showRuntime", &m_general); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 9f82ee8004c..85a35c88f54 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -69,9 +69,6 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; - // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) - m_previousLocale = std::locale::global(std::locale::classic()); - if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); resources.InitFonts(); @@ -88,7 +85,9 @@ Toolkit::Toolkit(bool initFont) Toolkit::~Toolkit() { - std::locale::global(m_previousLocale); + if (m_previousLocale) { + std::locale::global(*m_previousLocale); + } if (m_humdrumBuffer) { free(m_humdrumBuffer); @@ -1158,6 +1157,11 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) resources.LoadAll(); } + if (m_options->m_setLocale.GetValue() && !m_previousLocale) { + // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) + m_previousLocale = std::locale::global(std::locale::classic()); + } + return true; } From ecf265f423de497616565b595e28b0cea20c97e5 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Thu, 21 Mar 2024 18:24:35 +0100 Subject: [PATCH 251/383] Add proper methods and apply option in CLI --- include/vrv/toolkit.h | 8 ++++++++ src/toolkit.cpp | 26 ++++++++++++++++++-------- tools/main.cpp | 2 ++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 718df09219a..8ec0376e71d 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -732,6 +732,14 @@ class Toolkit { */ int GetOutputTo() { return m_outputTo; } + /** + * Setting the global locale. + */ + ///@{ + void SetLocale(); + void ResetLocale(); + ///@} + /** * Measuring runtime. * diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 85a35c88f54..2ae7b97c19b 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -85,9 +85,7 @@ Toolkit::Toolkit(bool initFont) Toolkit::~Toolkit() { - if (m_previousLocale) { - std::locale::global(*m_previousLocale); - } + this->ResetLocale(); if (m_humdrumBuffer) { free(m_humdrumBuffer); @@ -1140,6 +1138,8 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) m_options->Sync(); + this->SetLocale(); + // Forcing font resource to be reset if the font is given in the options if (json.has("fontAddCustom")) { Resources &resources = m_doc.GetResourcesForModification(); @@ -1157,11 +1157,6 @@ bool Toolkit::SetOptions(const std::string &jsonOptions) resources.LoadAll(); } - if (m_options->m_setLocale.GetValue() && !m_previousLocale) { - // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) - m_previousLocale = std::locale::global(std::locale::classic()); - } - return true; } @@ -2160,6 +2155,21 @@ std::string Toolkit::ConvertHumdrumToMIDI(const std::string &humdrumData) #endif } +void Toolkit::SetLocale() +{ + if (m_options->m_setLocale.GetValue() && !m_previousLocale) { + // Required for proper formatting, e.g., in StringFormat (see vrv.cpp) + m_previousLocale = std::locale::global(std::locale::classic()); + } +} + +void Toolkit::ResetLocale() +{ + if (m_previousLocale) { + std::locale::global(*m_previousLocale); + } +} + void Toolkit::InitClock() { #ifndef NO_RUNTIME diff --git a/tools/main.cpp b/tools/main.cpp index d30a41fca43..a57880eccb3 100644 --- a/tools/main.cpp +++ b/tools/main.cpp @@ -259,6 +259,8 @@ int main(int argc, char **argv) } options->Sync(); + toolkit.SetLocale(); + if (show_version) { display_version(); exit(0); From aab1915ba117bca601afc724c3541826e3fc0539 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Tue, 19 Mar 2024 18:23:52 +0100 Subject: [PATCH 252/383] fix ties from Dorico --- src/iomusxml.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index cab02b71ddf..2117e0e7510 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -3148,7 +3148,7 @@ void MusicXmlInput::ReadMusicXmlNote( } // ties - ReadMusicXmlTies(notations.node(), layer, note, measureNum); + ReadMusicXmlTies(node, layer, note, measureNum); // articulation std::vector artics; @@ -3834,7 +3834,8 @@ void MusicXmlInput::ReadMusicXmlBeamStart(const pugi::xml_node &node, const pugi void MusicXmlInput::ReadMusicXmlTies( const pugi::xml_node &node, Layer *layer, Note *note, const std::string &measureNum) { - for (pugi::xml_node xmlTie : node.children("tied")) { + pugi::xpath_node ties = node.select_node("notations[tied]"); + for (pugi::xml_node xmlTie : ties.node().children("tied")) { std::string tieType = xmlTie.attribute("type").as_string(); if (tieType.empty()) { From fb97b30f26f0d9d37367571354742249b447fa55 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Wed, 20 Mar 2024 10:32:01 +0100 Subject: [PATCH 253/383] iterate over node set --- src/iomusxml.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index 2117e0e7510..667e4d99a68 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -3834,8 +3834,9 @@ void MusicXmlInput::ReadMusicXmlBeamStart(const pugi::xml_node &node, const pugi void MusicXmlInput::ReadMusicXmlTies( const pugi::xml_node &node, Layer *layer, Note *note, const std::string &measureNum) { - pugi::xpath_node ties = node.select_node("notations[tied]"); - for (pugi::xml_node xmlTie : ties.node().children("tied")) { + pugi::xpath_node_set xmlTies = node.select_nodes("notations/tied"); + for (pugi::xpath_node_set::const_iterator it = xmlTies.begin(); it != xmlTies.end(); ++it) { + pugi::xml_node xmlTie = (*it).node(); std::string tieType = xmlTie.attribute("type").as_string(); if (tieType.empty()) { From 5752c6453838f5b76caa48f3421907a6f42ea0e7 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 22 Mar 2024 10:30:37 +0100 Subject: [PATCH 254/383] Update clang-format-check.yml --- .github/workflows/clang-format-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index e00c94d00fa..f79013e8515 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -18,8 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@v4.9.0 + uses: jidicula/clang-format-action@v4.11.0 with: - clang-format-version: "15" + clang-format-version: "18" check-path: ${{ matrix.path['check'] }} exclude-regex: ${{ matrix.path['exclude'] }} From 77dbb96359dcd8f697df9d9034ed458187148cf1 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 22 Mar 2024 10:34:33 +0100 Subject: [PATCH 255/383] formatting --- include/vrv/bboxdevicecontext.h | 6 +++--- include/vrv/devicecontext.h | 10 +++++----- include/vrv/layerelement.h | 2 +- include/vrv/lb.h | 2 +- include/vrv/view.h | 2 +- src/beam.cpp | 10 ++++------ src/options.cpp | 18 ++++++------------ 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/include/vrv/bboxdevicecontext.h b/include/vrv/bboxdevicecontext.h index 05b63b990ba..1c6ee4f9274 100644 --- a/include/vrv/bboxdevicecontext.h +++ b/include/vrv/bboxdevicecontext.h @@ -50,7 +50,7 @@ class BBoxDeviceContext : public DeviceContext { */ ///@{ void SetBackground(int color, int style = AxSOLID) override; - void SetBackgroundImage(void *image, double opacity = 1.0) override{}; + void SetBackgroundImage(void *image, double opacity = 1.0) override {}; void SetBackgroundMode(int mode) override; void SetTextForeground(int color) override; void SetTextBackground(int color) override; @@ -87,7 +87,7 @@ class BBoxDeviceContext : public DeviceContext { void DrawSpline(int n, Point points[]) override; void DrawGraphicUri(int x, int y, int width, int height, const std::string &uri) override; void DrawSvgShape(int x, int y, int width, int height, double scale, pugi::xml_node svg) override; - void DrawBackgroundImage(int x = 0, int y = 0) override{}; + void DrawBackgroundImage(int x = 0, int y = 0) override {}; ///@} /** @@ -150,7 +150,7 @@ class BBoxDeviceContext : public DeviceContext { * @name Method for adding description element */ ///@{ - void AddDescription(const std::string &text) override{}; + void AddDescription(const std::string &text) override {}; ///@} private: diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index e493d38b611..b319c835a1a 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -207,7 +207,7 @@ class DeviceContext { * Special method for forcing bounding boxes to be updated * Used for invisible elements (e.g., ) that needs to be take into account in spacing */ - virtual void DrawPlaceholder(int x, int y){}; + virtual void DrawPlaceholder(int x, int y) {}; /** * @name Method for starting and ending a text @@ -258,14 +258,14 @@ class DeviceContext { * For example, the method can be used for grouping shapes in in SVG */ ///@{ - virtual void StartCustomGraphic(const std::string &name, std::string gClass = "", std::string gId = ""){}; - virtual void EndCustomGraphic(){}; + virtual void StartCustomGraphic(const std::string &name, std::string gClass = "", std::string gId = "") {}; + virtual void EndCustomGraphic() {}; ///@} /** * Method for changing the color of a custom graphic */ - virtual void SetCustomGraphicColor(const std::string &color){}; + virtual void SetCustomGraphicColor(const std::string &color) {}; /** * @name Methods for re-starting and ending a graphic for objects drawn in separate steps @@ -308,7 +308,7 @@ class DeviceContext { * @name Method for adding description element */ ///@{ - virtual void AddDescription(const std::string &text){}; + virtual void AddDescription(const std::string &text) {}; ///@} /** diff --git a/include/vrv/layerelement.h b/include/vrv/layerelement.h index 085fa811845..cf8a7af9e32 100644 --- a/include/vrv/layerelement.h +++ b/include/vrv/layerelement.h @@ -296,7 +296,7 @@ class LayerElement : public Object, /** * Helper function to set shortening for elements with beam interface */ - virtual void SetElementShortening(int shortening){}; + virtual void SetElementShortening(int shortening) {}; /** * Get the stem mod for the element (if any) diff --git a/include/vrv/lb.h b/include/vrv/lb.h index 9b044f6d25b..52c97896da4 100644 --- a/include/vrv/lb.h +++ b/include/vrv/lb.h @@ -37,7 +37,7 @@ class Lb : public TextElement { /** * Lb is an empty element */ - void AddChild(Object *object) override{}; + void AddChild(Object *object) override {}; /** * Interface for class functor visitation diff --git a/include/vrv/view.h b/include/vrv/view.h index c9137403e20..2f59e4c6657 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -122,7 +122,7 @@ class View { virtual void DoRefresh() {} virtual void DoResize() {} virtual void DoReset() {} - virtual void OnPageChange(){}; + virtual void OnPageChange() {}; ///@} /** diff --git a/src/beam.cpp b/src/beam.cpp index 775ccb9f55a..5e30dfde1c5 100644 --- a/src/beam.cpp +++ b/src/beam.cpp @@ -333,9 +333,8 @@ std::pair BeamSegment::GetMinimalStemLength(const BeamDrawingInterface const auto [topOffset, bottomOffset] = this->GetVerticalOffset(beamInterface); // lambda check whether coord has element set and whether that element is CHORD or NOTE - const auto isNoteOrChord = [](BeamElementCoord *coord) { - return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); - }; + const auto isNoteOrChord + = [](BeamElementCoord *coord) { return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); }; using CoordIt = ArrayOfBeamElementCoords::const_iterator; for (CoordIt it = m_beamElementCoordRefs.begin(); it != m_beamElementCoordRefs.end(); ++it) { @@ -460,9 +459,8 @@ void BeamSegment::AdjustBeamToFrenchStyle(const BeamDrawingInterface *beamInterf // set to store durations of relevant notes (it's ordered, so min duration is going to be first) std::set noteDurations; // lambda check whether coord has element set and whether that element is CHORD or NOTE - const auto isNoteOrChord = [](BeamElementCoord *coord) { - return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); - }; + const auto isNoteOrChord + = [](BeamElementCoord *coord) { return (coord->m_element && coord->m_element->Is({ CHORD, NOTE })); }; // iterators using CoordIt = ArrayOfBeamElementCoords::iterator; using CoordReverseIt = ArrayOfBeamElementCoords::reverse_iterator; diff --git a/src/options.cpp b/src/options.cpp index a873af78cd4..86fd84f5c47 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -111,13 +111,11 @@ jsonxx::Object Option::ToJson() const const OptionBool *optBool = dynamic_cast(this); if (optBool) { - opt << "type" - << "bool"; + opt << "type" << "bool"; opt << "default" << optBool->GetDefault(); } else if (optDbl) { - opt << "type" - << "double"; + opt << "type" << "double"; jsonxx::Value value(optDbl->GetDefault()); value.precision_ = 2; opt << "default" << value; @@ -129,20 +127,17 @@ jsonxx::Object Option::ToJson() const opt << "max" << value; } else if (optInt) { - opt << "type" - << "int"; + opt << "type" << "int"; opt << "default" << optInt->GetDefault(); opt << "min" << optInt->GetMin(); opt << "max" << optInt->GetMax(); } else if (optString) { - opt << "type" - << "std::string"; + opt << "type" << "std::string"; opt << "default" << optString->GetDefault(); } else if (optArray) { - opt << "type" - << "array"; + opt << "type" << "array"; std::vector strValues = optArray->GetDefault(); std::vector::iterator strIter; jsonxx::Array values; @@ -152,8 +147,7 @@ jsonxx::Object Option::ToJson() const opt << "default" << values; } else if (optIntMap) { - opt << "type" - << "std::string-list"; + opt << "type" << "std::string-list"; opt << "default" << optIntMap->GetDefaultStrValue(); std::vector strValues = optIntMap->GetStrValues(false); std::vector::iterator strIter; From 6a064f0de21d6345f282badd01f54314a6473e76 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Mon, 8 Apr 2024 21:15:06 +0200 Subject: [PATCH 256/383] Fix HasFile implementation --- src/filereader.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/filereader.cpp b/src/filereader.cpp index 4703dd56a44..2ae6dfc9659 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -58,7 +58,7 @@ bool ZipFileReader::Load(const std::string &filename) #else std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); if (!fin.is_open()) { - LogError("File archive '%s' could not be open.", filename.c_str()); + LogError("File archive '%s' could not be opened.", filename.c_str()); return false; } @@ -92,7 +92,7 @@ std::list ZipFileReader::GetFileList() const assert(m_file); std::list list; - for (miniz_cpp::zip_info &member : m_file->infolist()) { + for (const miniz_cpp::zip_info &member : m_file->infolist()) { list.push_back(member.filename); } return list; @@ -103,13 +103,9 @@ bool ZipFileReader::HasFile(const std::string &filename) assert(m_file); // Look for the file in the zip - for (miniz_cpp::zip_info &member : m_file->infolist()) { - if (member.filename == filename) { - return true; - } - } - - return true; + const std::vector &fileInfoList = m_file->infolist(); + return std::any_of(fileInfoList.cbegin(), fileInfoList.cend(), + [&filename](const auto &info) { return info.filename == filename; }); } std::string ZipFileReader::ReadTextFile(const std::string &filename) @@ -117,7 +113,7 @@ std::string ZipFileReader::ReadTextFile(const std::string &filename) assert(m_file); // Look for the meta file in the zip - for (miniz_cpp::zip_info &member : m_file->infolist()) { + for (const miniz_cpp::zip_info &member : m_file->infolist()) { if (member.filename == filename) { return m_file->read(member.filename); } From 165b02e66e53030b47f053454406a241c2bfeaca Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 15 Apr 2024 09:45:15 +0200 Subject: [PATCH 257/383] only draw extender if set explicitly --- src/system.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/system.cpp b/src/system.cpp index 2a070dba54f..cbf3e6748f3 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -341,7 +341,7 @@ void System::AddToDrawingListIfNecessary(Object *object) else if (object->Is(DYNAM)) { Dynam *dynam = vrv_cast(object); assert(dynam); - if (dynam->GetEnd() || (dynam->GetNextLink() && (dynam->GetExtender() == BOOLEAN_true))) { + if ((dynam->GetEnd() || dynam->GetNextLink()) && (dynam->GetExtender() == BOOLEAN_true)) { this->AddToDrawingList(dynam); } } From 2dbf745459e7cd39afb4516b2c8adc4da8906d6a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 1 May 2024 08:28:11 +0200 Subject: [PATCH 258/383] Rename io.h/cpp to iobase to avoid conflicts. Closes #3662 --- Verovio.xcodeproj/project.pbxproj | 32 +++++++++++++++---------------- bindings/iOS/all.h | 2 +- include/vrv/ioabc.h | 2 +- include/vrv/{io.h => iobase.h} | 6 +++--- include/vrv/iodarms.h | 2 +- include/vrv/iohumdrum.h | 2 +- include/vrv/iomei.h | 2 +- include/vrv/iomusxml.h | 2 +- include/vrv/iopae.h | 2 +- src/{io.cpp => iobase.cpp} | 4 ++-- src/object.cpp | 2 +- 11 files changed, 29 insertions(+), 29 deletions(-) rename include/vrv/{io.h => iobase.h} (97%) rename src/{io.cpp => iobase.cpp} (95%) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index e2fda8406a0..9d46f1aadd8 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -103,7 +103,7 @@ 4D1693FF1E3A44F300569BF4 /* durationinterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EBE188539540037FD8E /* durationinterface.cpp */; }; 4D1694001E3A44F300569BF4 /* toolkit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EBF188539540037FD8E /* toolkit.cpp */; }; 4D1694011E3A44F300569BF4 /* MidiEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D1BE7671C688F5A0086DC0E /* MidiEvent.cpp */; }; - 4D1694021E3A44F300569BF4 /* io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* io.cpp */; }; + 4D1694021E3A44F300569BF4 /* iobase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* iobase.cpp */; }; 4D1694031E3A44F300569BF4 /* harm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D796B5D1D78641900A15238 /* harm.cpp */; }; 4D1694041E3A44F300569BF4 /* space.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DB3072E1AC9ED2500EE0982 /* space.cpp */; }; 4D1694051E3A44F300569BF4 /* iodarms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC1188539540037FD8E /* iodarms.cpp */; }; @@ -926,7 +926,7 @@ 8F086EE9188539540037FD8E /* doc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EBD188539540037FD8E /* doc.cpp */; }; 8F086EEA188539540037FD8E /* durationinterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EBE188539540037FD8E /* durationinterface.cpp */; }; 8F086EEB188539540037FD8E /* toolkit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EBF188539540037FD8E /* toolkit.cpp */; }; - 8F086EEC188539540037FD8E /* io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* io.cpp */; }; + 8F086EEC188539540037FD8E /* iobase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* iobase.cpp */; }; 8F086EED188539540037FD8E /* iodarms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC1188539540037FD8E /* iodarms.cpp */; }; 8F086EEE188539540037FD8E /* iomei.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC2188539540037FD8E /* iomei.cpp */; }; 8F086EEF188539540037FD8E /* iomusxml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC3188539540037FD8E /* iomusxml.cpp */; }; @@ -960,7 +960,7 @@ 8F3DD31E18854AFB0051330C /* bboxdevicecontext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EB9188539540037FD8E /* bboxdevicecontext.cpp */; }; 8F3DD32018854AFB0051330C /* devicecontext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EBC188539540037FD8E /* devicecontext.cpp */; }; 8F3DD32218854AFB0051330C /* svgdevicecontext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086ED5188539540037FD8E /* svgdevicecontext.cpp */; }; - 8F3DD32418854B090051330C /* io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* io.cpp */; }; + 8F3DD32418854B090051330C /* iobase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* iobase.cpp */; }; 8F3DD32618854B090051330C /* iodarms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC1188539540037FD8E /* iodarms.cpp */; }; 8F3DD32818854B090051330C /* iomei.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC2188539540037FD8E /* iomei.cpp */; }; 8F3DD32A18854B090051330C /* iomusxml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC3188539540037FD8E /* iomusxml.cpp */; }; @@ -1006,7 +1006,7 @@ 8F59293B18854BF800FE51AD /* doc.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291418854BF800FE51AD /* doc.h */; }; 8F59293C18854BF800FE51AD /* durationinterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291518854BF800FE51AD /* durationinterface.h */; }; 8F59293D18854BF800FE51AD /* toolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291618854BF800FE51AD /* toolkit.h */; }; - 8F59293E18854BF800FE51AD /* io.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291718854BF800FE51AD /* io.h */; }; + 8F59293E18854BF800FE51AD /* iobase.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291718854BF800FE51AD /* iobase.h */; }; 8F59293F18854BF800FE51AD /* iodarms.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291818854BF800FE51AD /* iodarms.h */; }; 8F59294018854BF800FE51AD /* iomei.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291918854BF800FE51AD /* iomei.h */; }; 8F59294118854BF800FE51AD /* iomusxml.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291A18854BF800FE51AD /* iomusxml.h */; }; @@ -1068,8 +1068,8 @@ BB4C4AAA22A932A0001F6AF0 /* devicecontextbase.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D797B041A67C55F007637BD /* devicecontextbase.h */; settings = {ATTRIBUTES = (Public, ); }; }; BB4C4AAB22A932A0001F6AF0 /* svgdevicecontext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086ED5188539540037FD8E /* svgdevicecontext.cpp */; }; BB4C4AAC22A932A0001F6AF0 /* svgdevicecontext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59292C18854BF800FE51AD /* svgdevicecontext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BB4C4AAD22A932A6001F6AF0 /* io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* io.cpp */; }; - BB4C4AAE22A932A6001F6AF0 /* io.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291718854BF800FE51AD /* io.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BB4C4AAD22A932A6001F6AF0 /* iobase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC0188539540037FD8E /* iobase.cpp */; }; + BB4C4AAE22A932A6001F6AF0 /* iobase.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F59291718854BF800FE51AD /* iobase.h */; settings = {ATTRIBUTES = (Public, ); }; }; BB4C4AAF22A932A6001F6AF0 /* ioabc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 402197931F2E09DA00182DF1 /* ioabc.cpp */; }; BB4C4AB022A932A6001F6AF0 /* ioabc.h in Headers */ = {isa = PBXBuildFile; fileRef = 402197921F2E09CB00182DF1 /* ioabc.h */; settings = {ATTRIBUTES = (Public, ); }; }; BB4C4AB122A932A6001F6AF0 /* iodarms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F086EC1188539540037FD8E /* iodarms.cpp */; }; @@ -2093,7 +2093,7 @@ 8F086EBD188539540037FD8E /* doc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = doc.cpp; path = src/doc.cpp; sourceTree = ""; }; 8F086EBE188539540037FD8E /* durationinterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = durationinterface.cpp; path = src/durationinterface.cpp; sourceTree = ""; }; 8F086EBF188539540037FD8E /* toolkit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = toolkit.cpp; path = src/toolkit.cpp; sourceTree = ""; }; - 8F086EC0188539540037FD8E /* io.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = io.cpp; path = src/io.cpp; sourceTree = ""; }; + 8F086EC0188539540037FD8E /* iobase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iobase.cpp; path = src/iobase.cpp; sourceTree = ""; }; 8F086EC1188539540037FD8E /* iodarms.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iodarms.cpp; path = src/iodarms.cpp; sourceTree = ""; }; 8F086EC2188539540037FD8E /* iomei.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; name = iomei.cpp; path = src/iomei.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; 8F086EC3188539540037FD8E /* iomusxml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iomusxml.cpp; path = src/iomusxml.cpp; sourceTree = ""; }; @@ -2134,7 +2134,7 @@ 8F59291418854BF800FE51AD /* doc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = doc.h; path = include/vrv/doc.h; sourceTree = ""; }; 8F59291518854BF800FE51AD /* durationinterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = durationinterface.h; path = include/vrv/durationinterface.h; sourceTree = ""; }; 8F59291618854BF800FE51AD /* toolkit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = toolkit.h; path = include/vrv/toolkit.h; sourceTree = ""; }; - 8F59291718854BF800FE51AD /* io.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = io.h; path = include/vrv/io.h; sourceTree = ""; }; + 8F59291718854BF800FE51AD /* iobase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iobase.h; path = include/vrv/iobase.h; sourceTree = ""; }; 8F59291818854BF800FE51AD /* iodarms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iodarms.h; path = include/vrv/iodarms.h; sourceTree = ""; }; 8F59291918854BF800FE51AD /* iomei.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = iomei.h; path = include/vrv/iomei.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 8F59291A18854BF800FE51AD /* iomusxml.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iomusxml.h; path = include/vrv/iomusxml.h; sourceTree = ""; }; @@ -2833,8 +2833,8 @@ 8F086F37188539C50037FD8E /* io */ = { isa = PBXGroup; children = ( - 8F086EC0188539540037FD8E /* io.cpp */, - 8F59291718854BF800FE51AD /* io.h */, + 8F086EC0188539540037FD8E /* iobase.cpp */, + 8F59291718854BF800FE51AD /* iobase.h */, 402197931F2E09DA00182DF1 /* ioabc.cpp */, 402197921F2E09CB00182DF1 /* ioabc.h */, 8F086EC1188539540037FD8E /* iodarms.cpp */, @@ -3194,7 +3194,7 @@ 4DEC4DD621C8295700D1D273 /* del.h in Headers */, 4DB726C81B8BB0F30040231B /* text.h in Headers */, 4D308101203DB69D00BC44F6 /* ref.h in Headers */, - 8F59293E18854BF800FE51AD /* io.h in Headers */, + 8F59293E18854BF800FE51AD /* iobase.h in Headers */, 4DF092A22497706600239195 /* phrase.h in Headers */, 8F59293F18854BF800FE51AD /* iodarms.h in Headers */, 4DDBBB571C7AE43E00054AFF /* hairpin.h in Headers */, @@ -3442,7 +3442,7 @@ 4DACC9E92990F29A00B55913 /* atts_fingering.h in Headers */, BB4C4A9422A9328F001F6AF0 /* doc.h in Headers */, BB4C4A9922A9328F001F6AF0 /* horizontalaligner.h in Headers */, - BB4C4AAE22A932A6001F6AF0 /* io.h in Headers */, + BB4C4AAE22A932A6001F6AF0 /* iobase.h in Headers */, BB4C4BAA22A932EB001F6AF0 /* view.h in Headers */, 4DACC9DD2990F29A00B55913 /* atts_header.h in Headers */, BB4C4BC422A9330D001F6AF0 /* pugixml.hpp in Headers */, @@ -3920,7 +3920,7 @@ E722106828F85981002CD6E9 /* findlayerelementsfunctor.cpp in Sources */, 4D1694011E3A44F300569BF4 /* MidiEvent.cpp in Sources */, 4DA0EAEF22BB77C300A7EBEB /* editortoolkit_cmn.cpp in Sources */, - 4D1694021E3A44F300569BF4 /* io.cpp in Sources */, + 4D1694021E3A44F300569BF4 /* iobase.cpp in Sources */, 4D1694031E3A44F300569BF4 /* harm.cpp in Sources */, 4D79641926C1522B0026288B /* pageelement.cpp in Sources */, 4DB3D8DE1F83D15200B5FC2B /* btrem.cpp in Sources */, @@ -4214,7 +4214,7 @@ 4D1BE76D1C688F5A0086DC0E /* MidiEvent.cpp in Sources */, E75EA9FD29CC3A88003A97A7 /* calcarticfunctor.cpp in Sources */, 35FDEBD124B6DC5B00AC1696 /* fing.cpp in Sources */, - 8F086EEC188539540037FD8E /* io.cpp in Sources */, + 8F086EEC188539540037FD8E /* iobase.cpp in Sources */, E75A69A029CCF8A200414819 /* adjustbeamsfunctor.cpp in Sources */, 4DACC9762990F29A00B55913 /* atts_neumes.cpp in Sources */, 4D796B5E1D78641900A15238 /* harm.cpp in Sources */, @@ -4638,7 +4638,7 @@ E7E9C11729B0A20400CFCE2F /* adjustaccidxfunctor.cpp in Sources */, 8F3DD33818854B250051330C /* system.cpp in Sources */, 4D72A5E1208A37F0009DEC1E /* mnum.cpp in Sources */, - 8F3DD32418854B090051330C /* io.cpp in Sources */, + 8F3DD32418854B090051330C /* iobase.cpp in Sources */, 4DACC9D62990F29A00B55913 /* atts_pagebased.cpp in Sources */, 4DA0EACD22BB779400A7EBEB /* zone.cpp in Sources */, 4DEC4DBC21C8288900D1D273 /* choice.cpp in Sources */, @@ -4791,7 +4791,7 @@ BB4C4A9322A9328F001F6AF0 /* doc.cpp in Sources */, 4D8135212322C41800F59C01 /* keyaccid.cpp in Sources */, BB4C4B9922A932E5001F6AF0 /* linkinginterface.cpp in Sources */, - BB4C4AAD22A932A6001F6AF0 /* io.cpp in Sources */, + BB4C4AAD22A932A6001F6AF0 /* iobase.cpp in Sources */, E74A806A28BC9843005274E7 /* functorinterface.cpp in Sources */, 4DD7C0FD27A55CEA00B9C017 /* timemap.cpp in Sources */, E78833632994EC7E00D44B01 /* calcchordnoteheadsfunctor.cpp in Sources */, diff --git a/bindings/iOS/all.h b/bindings/iOS/all.h index 0a5dbb6b62d..d99cbd442f9 100644 --- a/bindings/iOS/all.h +++ b/bindings/iOS/all.h @@ -121,7 +121,7 @@ #import #import #import -#import +#import #import #import #import diff --git a/include/vrv/ioabc.h b/include/vrv/ioabc.h index 4e830c11bba..f29892b0549 100644 --- a/include/vrv/ioabc.h +++ b/include/vrv/ioabc.h @@ -13,7 +13,7 @@ //---------------------------------------------------------------------------- -#include "io.h" +#include "iobase.h" #include "pugixml.hpp" #include "vrvdef.h" diff --git a/include/vrv/io.h b/include/vrv/iobase.h similarity index 97% rename from include/vrv/io.h rename to include/vrv/iobase.h index f07c2bf1448..e5cfc903acc 100644 --- a/include/vrv/io.h +++ b/include/vrv/iobase.h @@ -1,12 +1,12 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: io.h +// Name: iobase.h // Author: Laurent Pugin // Created: 2012 // Copyright (c) Authors and others. All rights reserved. ///////////////////////////////////////////////////////////////////////////// -#ifndef __VRV_IO_H__ -#define __VRV_IO_H__ +#ifndef __VRV_IOBASE_H__ +#define __VRV_IOBASE_H__ #include #include diff --git a/include/vrv/iodarms.h b/include/vrv/iodarms.h index ad3ad31820a..bd1f4bb8d56 100644 --- a/include/vrv/iodarms.h +++ b/include/vrv/iodarms.h @@ -11,7 +11,7 @@ //---------------------------------------------------------------------------- #include "attdef.h" -#include "io.h" +#include "iobase.h" namespace vrv { diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index e90fbe07f90..cc5a009737b 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -25,7 +25,7 @@ #include "fing.h" #include "ftrem.h" #include "harm.h" -#include "io.h" +#include "iobase.h" #include "keysig.h" #include "label.h" #include "metersig.h" diff --git a/include/vrv/iomei.h b/include/vrv/iomei.h index 6b0d7800119..12f530bdacf 100644 --- a/include/vrv/iomei.h +++ b/include/vrv/iomei.h @@ -14,7 +14,7 @@ //---------------------------------------------------------------------------- #include "doc.h" -#include "io.h" +#include "iobase.h" //---------------------------------------------------------------------------- diff --git a/include/vrv/iomusxml.h b/include/vrv/iomusxml.h index 2d6decc245c..946f0d95b74 100644 --- a/include/vrv/iomusxml.h +++ b/include/vrv/iomusxml.h @@ -17,7 +17,7 @@ //---------------------------------------------------------------------------- #include "attdef.h" -#include "io.h" +#include "iobase.h" #include "metersig.h" #include "vrvdef.h" diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index 77313365e15..b02f0620adc 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -27,7 +27,7 @@ #include "atts_cmn.h" #include "clef.h" -#include "io.h" +#include "iobase.h" #include "keysig.h" #include "mensur.h" #include "metersig.h" diff --git a/src/io.cpp b/src/iobase.cpp similarity index 95% rename from src/io.cpp rename to src/iobase.cpp index 87c27c38159..3071a1c5599 100644 --- a/src/io.cpp +++ b/src/iobase.cpp @@ -1,11 +1,11 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: io.cpp +// Name: iobase.cpp // Author: Laurent Pugin // Created: 2012 // Copyright (c) Laurent Pugin. All rights reserved. ///////////////////////////////////////////////////////////////////////////// -#include "io.h" +#include "iobase.h" //---------------------------------------------------------------------------- diff --git a/src/object.cpp b/src/object.cpp index 086e4589db2..d23e54181bf 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -28,7 +28,7 @@ #include "editorial.h" #include "featureextractor.h" #include "findfunctor.h" -#include "io.h" +#include "iobase.h" #include "keysig.h" #include "layer.h" #include "linkinginterface.h" From 2a618adedc602fbbac44f0d65f45c11ad040d0c6 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Tue, 23 Apr 2024 09:18:31 +0200 Subject: [PATCH 259/383] update midifile --- include/midi/Binasc.h | 8 ++- include/midi/MidiEvent.h | 7 ++ include/midi/MidiEventList.h | 2 + include/midi/MidiFile.h | 23 ++++--- include/midi/MidiMessage.h | 11 +++ src/midi/Binasc.cpp | 31 ++++++++- src/midi/MidiEvent.cpp | 16 ++++- src/midi/MidiEventList.cpp | 43 ++++++------ src/midi/MidiFile.cpp | 78 ++++++++++----------- src/midi/MidiMessage.cpp | 127 ++++++++++++++++++++++++++++------- 10 files changed, 248 insertions(+), 98 deletions(-) diff --git a/include/midi/Binasc.h b/include/midi/Binasc.h index 6aa57f09478..686cc407c7c 100644 --- a/include/midi/Binasc.h +++ b/include/midi/Binasc.h @@ -13,11 +13,11 @@ #ifndef _BINASC_H_INCLUDED #define _BINASC_H_INCLUDED -#include +#include #include +#include #include -#include -#include /* needed for MinGW */ + namespace smf { @@ -149,6 +149,8 @@ class Binasc { int getWord (std::string& word, const std::string& input, const std::string& terminators, int index); + static const char *GMinstrument[128]; + }; } // end of namespace smf diff --git a/include/midi/MidiEvent.h b/include/midi/MidiEvent.h index 39f141ee9b1..f8141d17bba 100644 --- a/include/midi/MidiEvent.h +++ b/include/midi/MidiEvent.h @@ -15,8 +15,11 @@ #define _MIDIEVENT_H_INCLUDED #include "MidiMessage.h" + +#include #include + namespace smf { class MidiEvent : public MidiMessage { @@ -63,6 +66,10 @@ class MidiEvent : public MidiMessage { }; + +std::ostream& operator<<(std::ostream& out, MidiEvent& event); + + } // end of namespace smf #endif /* _MIDIEVENT_H_INCLUDED */ diff --git a/include/midi/MidiEventList.h b/include/midi/MidiEventList.h index e3bfb13cf5b..f9b1e8538e2 100644 --- a/include/midi/MidiEventList.h +++ b/include/midi/MidiEventList.h @@ -14,8 +14,10 @@ #define _MIDIEVENTLIST_H_INCLUDED #include "MidiEvent.h" + #include + namespace smf { class MidiEventList { diff --git a/include/midi/MidiFile.h b/include/midi/MidiFile.h index 4a363da5658..4972218f627 100644 --- a/include/midi/MidiFile.h +++ b/include/midi/MidiFile.h @@ -16,19 +16,24 @@ #include "MidiEventList.h" -#include -#include -#include #include +#include +#include +#include -#define TIME_STATE_DELTA 0 -#define TIME_STATE_ABSOLUTE 1 - -#define TRACK_STATE_SPLIT 0 -#define TRACK_STATE_JOINED 1 namespace smf { +enum { + TRACK_STATE_SPLIT = 0, // Tracks are separated into separate vector postions. + TRACK_STATE_JOINED = 1 // Tracks are merged into a single vector position, +}; // like a Type-0 MIDI file, but reversible. + +enum { + TIME_STATE_DELTA = 0, // MidiMessage::ticks are in delta time format (like MIDI file). + TIME_STATE_ABSOLUTE = 1 // MidiMessage::ticks are in absolute time format (0=start time). +}; + class _TickTime { public: int tick; @@ -217,7 +222,7 @@ class MidiFile { MidiEvent* addTempo (int aTrack, int aTick, double aTempo); MidiEvent* addKeySignature (int aTrack, int aTick, - int key, bool mode = 0); + int fifths, bool mode = 0); MidiEvent* addTimeSignature (int aTrack, int aTick, int top, int bottom, int clocksPerClick = 24, diff --git a/include/midi/MidiMessage.h b/include/midi/MidiMessage.h index 49bd88a73e7..b298e15b9a0 100644 --- a/include/midi/MidiMessage.h +++ b/include/midi/MidiMessage.h @@ -14,10 +14,12 @@ #ifndef _MIDIMESSAGE_H_INCLUDED #define _MIDIMESSAGE_H_INCLUDED +#include #include #include #include + namespace smf { typedef unsigned char uchar; @@ -122,6 +124,12 @@ class MidiMessage : public std::vector { void makePatchChange (int channel, int patchnum); void makeTimbre (int channel, int patchnum); void makeController (int channel, int num, int value); + void makePitchBend (int channel, int lsb, int msb); + void makePitchBend (int channel, int value); + void makePitchBendDouble (int channel, double value); + void makePitchbend (int channel, int lsb, int msb) { makePitchBend(channel, lsb, msb); } + void makePitchbend (int channel, int value) { makePitchBend(channel, value); } + void makePitchbendDouble (int channel, double value) { makePitchBendDouble(channel, value); } // helper functions to create various continuous controller messages: void makeSustain (int channel, int value); @@ -199,6 +207,9 @@ class MidiMessage : public std::vector { }; +std::ostream& operator<<(std::ostream& out, MidiMessage& event); + + } // end of namespace smf diff --git a/src/midi/Binasc.cpp b/src/midi/Binasc.cpp index f49aa563201..446dc03b9b0 100644 --- a/src/midi/Binasc.cpp +++ b/src/midi/Binasc.cpp @@ -11,12 +11,37 @@ #include "Binasc.h" +#include #include -#include namespace smf { +const char* Binasc::GMinstrument[128] = { + "acoustic grand piano", "bright acoustic piano", "electric grand piano", "honky-tonk piano", "rhodes piano", "chorused piano", + "harpsichord", "clavinet", "celeste", "glockenspiel", "music box", "vibraphone", + "marimba", "xylophone", "tubular bells", "dulcimer", "hammond organ", "percussive organ", + "rock organ", "church organ", "reed organ", "accordion", "harmonica", "tango accordion", + "nylon guitar", "steel guitar", "jazz guitar", "clean guitar", "muted guitar", "overdriven guitar", + "distortion guitar", "guitar harmonics", "acoustic bass", "fingered electric bass", "picked electric bass", "fretless bass", + "slap bass 1", "slap bass 2", "synth bass 1", "synth bass 2", "violin", "viola", + "cello", "contrabass", "tremolo strings", "pizzcato strings", "orchestral harp", "timpani", + "string ensemble 1", "string ensemble 2", "synth strings 1", "synth strings 1", "choir aahs", "voice oohs", + "synth voices", "orchestra hit", "trumpet", "trombone", "tuba", "muted trumpet", + "frenc horn", "brass section", "syn brass 1", "synth brass 2", "soprano sax", "alto sax", + "tenor sax", "baritone sax", "oboe", "english horn", "bassoon", "clarinet", + "piccolo", "flute", "recorder", "pan flute", "bottle blow", "shakuhachi", + "whistle", "ocarina", "square wave", "saw wave", "calliope lead", "chiffer lead", + "charang lead", "voice lead", "fifths lead", "brass lead", "newage pad", "warm pad", + "polysyn pad", "choir pad", "bowed pad", "metallic pad", "halo pad", "sweep pad", + "rain", "soundtrack", "crystal", "atmosphere", "brightness", "goblins", + "echoes", "sci-fi", "sitar", "banjo", "shamisen", "koto", + "kalimba", "bagpipes", "fiddle", "shanai", "tinkle bell", "agogo", + "steel drums", "woodblock", "taiko drum", "melodoc tom", "synth drum", "reverse cymbal", + "guitar fret noise", "breath noise", "seashore", "bird tweet", "telephone ring", "helicopter", + "applause", "gunshot" +}; + ////////////////////////////// // // Binasc::Binasc -- Constructor: set the default option values. @@ -717,7 +742,9 @@ int Binasc::readMidiEvent(std::ostream& out, std::istream& infile, output << " '" << std::dec << (int)byte1; if (m_commentsQ) { output << "\t"; - comment += "patch-change"; + comment += "patch-change ("; + comment += GMinstrument[byte1 & 0x7f]; + comment += ")"; } break; case 0xD0: // channel pressure: 1 bytes diff --git a/src/midi/MidiEvent.cpp b/src/midi/MidiEvent.cpp index 945e3c532eb..0cf590d8c97 100644 --- a/src/midi/MidiEvent.cpp +++ b/src/midi/MidiEvent.cpp @@ -13,7 +13,7 @@ #include "MidiEvent.h" -#include +#include namespace smf { @@ -278,6 +278,20 @@ double MidiEvent::getDurationInSeconds(void) const { } + +////////////////////////////// +// +// operator<<(MidiMessage) -- Print tick value followed by MIDI bytes for event. +// The tick value will be either relative or absolute depending on the state +// of the MidiFile object containing it. +// + +std::ostream& operator<<(std::ostream& out, MidiEvent& event) { + out << event.tick << '(' << static_cast(event) << ')'; + return out; +} + + } // end namespace smf diff --git a/src/midi/MidiEventList.cpp b/src/midi/MidiEventList.cpp index 52749c5d837..f38fefbc2ca 100644 --- a/src/midi/MidiEventList.cpp +++ b/src/midi/MidiEventList.cpp @@ -10,15 +10,14 @@ // Description: A class which stores a MidiEvents for a MidiFile track. // - #include "MidiEventList.h" -#include #include +#include #include #include +#include -#include namespace smf { @@ -54,8 +53,8 @@ MidiEventList::MidiEventList(const MidiEventList& other) { // MidiEventList::MidiEventList(MidiEventList&& other) { - list = std::move(other.list); - other.list.clear(); + list = std::move(other.list); + other.list.clear(); } @@ -124,12 +123,12 @@ const MidiEvent& MidiEventList::last(void) const { // MidiEvent& MidiEventList::getEvent(int index) { - return *list[index]; + return *list[index]; } const MidiEvent& MidiEventList::getEvent(int index) const { - return *list[index]; + return *list[index]; } @@ -141,10 +140,10 @@ const MidiEvent& MidiEventList::getEvent(int index) const { // void MidiEventList::clear(void) { - for (int i=0; i<(int)list.size(); i++) { - if (list[i] != NULL) { - delete list[i]; - list[i] = NULL; + for (auto& item : list) { + if (item != NULL) { + delete item; + item = NULL; } } list.resize(0); @@ -245,10 +244,10 @@ int MidiEventList::push_back(MidiEvent& event) { void MidiEventList::removeEmpties(void) { int count = 0; - for (int i=0; i<(int)list.size(); i++) { - if (list[i]->empty()) { - delete list[i]; - list[i] = NULL; + for (auto& item : list) { + if (item->empty()) { + delete item; + item = NULL; count++; } } @@ -257,9 +256,9 @@ void MidiEventList::removeEmpties(void) { } std::vector newlist; newlist.reserve(list.size() - count); - for (int i=0; i<(int)list.size(); i++) { - if (list[i]) { - newlist.push_back(list[i]); + for (auto& item : list) { + if (item) { + newlist.push_back(item); } } list.swap(newlist); @@ -292,8 +291,8 @@ int MidiEventList::linkNotePairs(void) { // dimension 3: List of active note-ons or note-offs. std::vector>> noteons; noteons.resize(16); - for (int i=0; i<(int)noteons.size(); i++) { - noteons[i].resize(128); + for (auto& noteon : noteons) { + noteon.resize(128); } // Controller linking: The following General MIDI controller numbers are @@ -433,7 +432,7 @@ void MidiEventList::clearLinks(void) { ////////////////////////////// // -// MidiEventList::clearSequence -- Remove any seqence serial numbers from +// MidiEventList::clearSequence -- Remove any sequence serial numbers from // MidiEvents in the list. This will cause the default ordering by // sortTracks() to be used, in which case the ordering of MidiEvents // occurring at the same tick may switch their ordering. @@ -454,7 +453,7 @@ void MidiEventList::clearSequence(void) { // to preseve the order of MIDI messages in a track when they occur // at the same tick time. Particularly for use with joinTracks() // or sortTracks(). markSequence will be done automatically when -// a MIDI file is read, in case the ordering of events occuring at +// a MIDI file is read, in case the ordering of events occurring at // the same time is important. Use clearSequence() to use the // default sorting behavior of sortTracks() when events occur at the // same time. Returns the next serial number that has not yet been diff --git a/src/midi/MidiFile.cpp b/src/midi/MidiFile.cpp index fa1534d79df..55956c8ce79 100644 --- a/src/midi/MidiFile.cpp +++ b/src/midi/MidiFile.cpp @@ -15,14 +15,14 @@ #include "MidiFile.h" #include "Binasc.h" -#include -#include -#include -#include +#include #include -#include +#include +#include #include -#include +#include +#include +#include namespace smf { @@ -71,21 +71,21 @@ const char* MidiFile::GMinstrument[128] = { ////////////////////////////// // -// MidiFile::MidiFile -- Constuctor. +// MidiFile::MidiFile -- Constructor. // MidiFile::MidiFile(void) { m_events.resize(1); - for (int i=0; i<(int)m_events.size(); i++) { - m_events[i] = new MidiEventList; + for (auto &event : m_events) { + event = new MidiEventList; } } MidiFile::MidiFile(const std::string& filename) { m_events.resize(1); - for (int i=0; i<(int)m_events.size(); i++) { - m_events[i] = new MidiEventList; + for (auto &event : m_events) { + event = new MidiEventList; } read(filename); } @@ -93,8 +93,8 @@ MidiFile::MidiFile(const std::string& filename) { MidiFile::MidiFile(std::istream& input) { m_events.resize(1); - for (int i=0; i<(int)m_events.size(); i++) { - m_events[i] = new MidiEventList; + for (auto &event : m_events) { + event = new MidiEventList; } read(input); } @@ -507,7 +507,7 @@ bool MidiFile::readSmf(std::istream& input) { m_events[i]->clear(); // Read MIDI events in the track, which are pairs of VLV values - // and then the bytes for the MIDI message. Running status messags + // and then the bytes for the MIDI message. Running status messages // will be filled in with their implicit command byte. // The timestamps are converted from delta ticks to absolute ticks, // with the absticks variable accumulating the VLV tick values. @@ -597,7 +597,7 @@ bool MidiFile::write(std::ostream& out) { shortdata = static_cast(getNumTracks()); writeBigEndianUShort(out, shortdata); - // 5. write out the number of ticks per quarternote. (avoiding SMTPE for now) + // 5. write out the number of ticks per quarternote. (avoiding SMPTE for now) shortdata = static_cast(getTicksPerQuarterNote()); writeBigEndianUShort(out, shortdata); @@ -774,7 +774,7 @@ bool MidiFile::writeHex(std::ostream& out, int width) { int wordcount = 1; int linewidth = width >= 0 ? width : 25; for (int i=0; iremoveEmpties(); + for (auto &event : m_events) { + event->removeEmpties(); } } @@ -954,7 +954,7 @@ void MidiFile::removeEmpties(void) { // a track when they occur at the same tick time. Particularly // for use with joinTracks() or sortTracks(). markSequence will // be done automatically when a MIDI file is read, in case the -// ordering of m_events occuring at the same time is important. +// ordering of m_events occurring at the same time is important. // Use clearSequence() to use the default sorting behavior of // sortTracks(). // @@ -982,7 +982,7 @@ void MidiFile::markSequence(int track, int sequence) { ////////////////////////////// // -// MidiFile::clearSequence -- Remove any seqence serial numbers from +// MidiFile::clearSequence -- Remove any sequence serial numbers from // MidiEvents in the MidiFile. This will cause the default ordering by // sortTracks() to be used, in which case the ordering of MidiEvents // occurring at the same tick may switch their ordering. @@ -1311,7 +1311,7 @@ void MidiFile::deltaTicks(void) { // absolute time, which means that the time field // in the MidiEvent struct represents the exact tick // time to play the event rather than the time since -// the last event to wait untill playing the current +// the last event to wait until playing the current // event. // @@ -1422,7 +1422,7 @@ int MidiFile::getFileDurationInTicks(void) { // in units of quarter notes. If the MidiFile is in delta tick mode, // then temporarily got into absolute tick mode to do the calculations. // Note that this is expensive, so you should normally call this function -// while in aboslute tick (default) mode. +// while in absolute tick (default) mode. // double MidiFile::getFileDurationInQuarters(void) { @@ -1434,7 +1434,7 @@ double MidiFile::getFileDurationInQuarters(void) { ////////////////////////////// // // MidiFile::getFileDurationInSeconds -- returns the duration of the -// logest track in the file. The tracks must be sorted before +// longest track in the file. The tracks must be sorted before // calling this function, since this function assumes that the // last MidiEvent in the track has the highest timestamp. // The file state can be in delta ticks since this function @@ -1906,7 +1906,7 @@ MidiEvent* MidiFile::addTimeSignature(int aTrack, int aTick, int top, int bottom // // MidiFile::addCompoundTimeSignature -- Add a time signature meta message // (meta #0x58), where the clocksPerClick parameter is set to three -// eighth notes for compount meters such as 6/8 which represents +// eighth notes for compound meters such as 6/8 which represents // two beats per measure. // // Default values: @@ -2347,7 +2347,7 @@ int MidiFile::getTicksPerQuarterNote(void) const { // setting for 25 frames a second with 40 subframes // which means one tick per millisecond. When SMPTE is // being used, there is no real concept of the quarter note, - // so presume 60 bpm as a simiplification here. + // so presume 60 bpm as a simplification here. // return 1000; } return m_ticksPerQuarterNote; @@ -2696,7 +2696,7 @@ double MidiFile::linearSecondInterpolationAtTick(int ticktime) { // MidiFile::buildTimeMap -- build an index of the absolute tick values // found in a MIDI file, and their corresponding time values in // seconds, taking into consideration tempo change messages. If no -// tempo messages are given (or untill they are given, then the +// tempo messages are given (or until they are given, then the // tempo is set to 120 beats per minute). If SMPTE time code is // used, then ticks are actually time values. So don't build // a time map for SMPTE ticks, and just calculate the time in @@ -2956,15 +2956,17 @@ int MidiFile::extractMidiData(std::istream& input, std::vector& array, ulong MidiFile::readVLValue(std::istream& input) { uchar b[5] = {0}; - for (int i=0; i<5; i++) { - b[i] = readByte(input); - if (!status()) { return m_rwstatus; } - if (b[i] < 0x80) { - break; - } - } + for (uchar &item : b) { + item = readByte(input); + if (!status()) { + return m_rwstatus; + } + if (item < 0x80) { + break; + } + } - return unpackVLV(b[0], b[1], b[2], b[3], b[4]); + return unpackVLV(b[0], b[1], b[2], b[3], b[4]); } @@ -3005,7 +3007,7 @@ ulong MidiFile::unpackVLV(uchar a, uchar b, uchar c, uchar d, uchar e) { // // MidiFile::writeVLValue -- write a number to the midifile // as a variable length value which segments a file into 7-bit -// values and adds a contination bit to each. Maximum size of input +// values and adds a continuation bit to each. Maximum size of input // aValue is 0x0FFFffff. // @@ -3392,7 +3394,7 @@ std::string MidiFile::base64Encode(const std::string& input) { output.reserve(((input.size()/3) + (input.size() % 3 > 0)) * 4); int vala = 0; int valb = -6; - for (unsigned char c : input) { + for (uchar c : input) { vala = (vala << 8) + c; valb += 8; while (valb >=0) { @@ -3423,7 +3425,7 @@ std::string MidiFile::base64Decode(const std::string& input) { std::string output; int vala = 0; int valb = -8; - for (unsigned char c : input) { + for (uchar c : input) { if (c == '=') { break; } else if (MidiFile::decodeLookup[c] == -1) { diff --git a/src/midi/MidiMessage.cpp b/src/midi/MidiMessage.cpp index 7b9eb33c289..5b19a36eb8a 100644 --- a/src/midi/MidiMessage.cpp +++ b/src/midi/MidiMessage.cpp @@ -14,10 +14,10 @@ #include "MidiMessage.h" #include +#include +#include #include #include -#include - namespace smf { @@ -315,11 +315,13 @@ bool MidiMessage::isMetaMessage(void) const { // bool MidiMessage::isNoteOff(void) const { - if (size() != 3) { + const MidiMessage& message = *this; + const vector& chars = message; + if (message.size() != 3) { return false; - } else if (((*this)[0] & 0xf0) == 0x80) { + } else if ((chars[0] & 0xf0) == 0x80) { return true; - } else if ((((*this)[0] & 0xf0) == 0x90) && ((*this)[2] == 0)) { + } else if (((chars[0] & 0xf0) == 0x90) && (chars[2] == 0x00)) { return true; } else { return false; @@ -676,7 +678,7 @@ bool MidiMessage::isInstrumentName(void) const { ////////////////////////////// // // MidiMessage::isLyricText -- Returns true if message is a meta message -// describing some lyric text (for karakoke MIDI files) +// describing some lyric text (for karaoke MIDI files) // (meta message type 0x05). // @@ -857,7 +859,7 @@ int MidiMessage::getKeyNumber(void) const { ////////////////////////////// // -// MidiMessage::getVelocity -- Return the key veolocity. If the message +// MidiMessage::getVelocity -- Return the key velocity. If the message // is not a note-on or a note-off, then return -1. If the value is // out of the range 0-127, then chop off the high-bits. // @@ -961,7 +963,7 @@ void MidiMessage::setP1(int value) { ////////////////////////////// // -// MidiMessage::setP2 -- Set the second paramter value. +// MidiMessage::setP2 -- Set the second paramater value. // If the MidiMessage is too short, add extra spaces // to allow for P2. The command byte and/or the P1 value // will be undefined if extra space needs to be added and @@ -980,7 +982,7 @@ void MidiMessage::setP2(int value) { ////////////////////////////// // -// MidiMessage::setP3 -- Set the third paramter value. +// MidiMessage::setP3 -- Set the third paramater value. // If the MidiMessage is too short, add extra spaces // to allow for P3. The command byte and/or the P1/P2 values // will be undefined if extra space needs to be added and @@ -1364,7 +1366,7 @@ void MidiMessage::setSpelling(int base7, int accidental) { // pc + octave * 7 // where pc is the numbers 0 through 6 representing the pitch classes // C through B, the octave is MIDI octave (not the scientific pitch -// octave which is one less than the MIDI ocatave, such as C4 = middle C). +// octave which is one less than the MIDI octave, such as C4 = middle C). // The second number is the accidental for the base-7 pitch. // @@ -1544,8 +1546,8 @@ void MidiMessage::setMetaContent(const std::string& content) { // add the size of the meta message data (VLV) int dsize = (int)content.size(); std::vector vlv = intToVlv(dsize); - for (int i=0; i<(int)vlv.size(); i++) { - this->push_back(vlv[i]); + for (uchar item : vlv) { + this->push_back(item); } std::copy(content.begin(), content.end(), std::back_inserter(*this)); } @@ -1764,6 +1766,61 @@ void MidiMessage::makeController(int channel, int num, int value) { +///////////////////////////// +// +// MidiMessage::makePitchBend -- Create a pitch-bend message. lsb is +// least-significant 7 bits of the 14-bit range, and msb is the +// most-significant 7 bits of the 14-bit range. The range depth +// is determined by a setting in the synthesizer. Typically it is +// +/- two semitones by default. See MidiFile::setPitchBendRange() +// to change the default (or change to the typical default). +// + +void MidiMessage::makePitchBend(int channel, int lsb, int msb) { + resize(0); + push_back(0xe0 | (0x0e & channel)); + push_back(0x7f & lsb); + push_back(0x7f & msb); +} + +// +// value is a 14-bit number, where 0 is the lowest pitch of the range, and +// 2^15-1 is the highest pitch of the range. +// + +void MidiMessage::makePitchBend(int channel, int value) { + resize(0); + int lsb = value & 0x7f; + int msb = (value >> 7) & 0x7f; + push_back(0xe0 | (0x7f & channel)); + push_back(lsb); + push_back(msb); +} + +// +// Input value is a number between -1.0 and +1.0. +// + +void MidiMessage::makePitchBendDouble(int channel, double value) { + // value is in the range from -1 for minimum and 2^18 - 1 for the maximum + resize(0); + double dvalue = (value + 1.0) * (pow(2.0, 15.0)); + if (dvalue < 0.0) { + dvalue = 0.0; + } + if (dvalue > pow(2.0, 15.0) - 1.0) { + dvalue = pow(2.0, 15.0) - 1.0; + } + ulong uivalue = (ulong)dvalue; + uchar lsb = uivalue & 0x7f; + uchar msb = (uivalue >> 7) & 0x7f; + push_back(0xe0 | (0x7f & channel)); + push_back(lsb); + push_back(msb); +} + + + ///////////////////////////// // // MidiMessage::makeSustain -- Create a sustain pedal message. @@ -1999,8 +2056,8 @@ void MidiMessage::makeSysExMessage(const std::vector& data) { int msize = endindex - startindex + 2; std::vector vlv = intToVlv(msize); - for (int i=0; i<(int)vlv.size(); i++) { - this->push_back(vlv[i]); + for (uchar item : vlv) { + this->push_back(item); } for (int i=startindex; i<=endindex; i++) { this->push_back(data.at(i)); @@ -2095,18 +2152,18 @@ void MidiMessage::makeMts2_KeyTuningsBySemitone(std::vector vlv = intToVlv((int)mapping.size()); - for (int i=0; i<(int)vlv.size(); i++) { - data.push_back(vlv[i]); + for (uchar item : vlv) { + data.push_back(item); } - for (int i=0; i<(int)mapping.size(); i++) { - int keynum = mapping[i].first; + for (auto &item : mapping) { + int keynum = item.first; if (keynum < 0) { keynum = 0; } else if (keynum > 127) { keynum = 127; } data.push_back((uchar)keynum); - double semitones = mapping[i].second; + double semitones = item.second; int sint = (int)semitones; if (sint < 0) { sint = 0; @@ -2120,8 +2177,8 @@ void MidiMessage::makeMts2_KeyTuningsBySemitone(std::vector> 7) & 0x7f; data.push_back(msb); data.push_back(lsb); - } - this->makeSysExMessage(data); + } + this->makeSysExMessage(data); } @@ -2204,8 +2261,8 @@ void MidiMessage::makeTemperamentBad(double maxDeviationCents, int referencePitc maxDeviationCents = 100.0; } std::vector temperament(12); - for (int i=0; i<(int)temperament.size(); i++) { - temperament[i] = ((rand() / (double)RAND_MAX) * 2.0 - 1.0) * maxDeviationCents; + for (double &item : temperament) { + item = ((rand() / (double)RAND_MAX) * 2.0 - 1.0) * maxDeviationCents; } this->makeMts9_TemperamentByCentsDeviationFromET(temperament, referencePitchClass, channelMask); } @@ -2294,6 +2351,30 @@ void MidiMessage::makeTemperamentMeantoneCommaHalf(int referencePitchClass, int } + +////////////////////////////// +// +// operator<<(MidiMessage) -- Print MIDI messages as text. 0x80 and above +// are printed as hex, below as dec (will look strange for meta messages +// and system exclusives which could be dealt with later). +// + +std::ostream& operator<<(std::ostream& out, MidiMessage& message) { + for (int i=0; i<(int)message.size(); i++) { + if (message[i] >= 0x80) { + out << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)message[i]; + out << std::dec << std::setw(0) << std::setfill(' '); + } else { + out << (int)message[i]; + } + if (i<(int)message.size() - 1) { + out << ' '; + } + } + return out; +} + + } // end namespace smf From 677911a94867702e69394212a37a8b41037710c4 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 1 May 2024 12:34:16 +0200 Subject: [PATCH 260/383] Update xcode versions --- .github/workflows/ci_build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index f6bbe77946c..ca515fa61ad 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -100,11 +100,11 @@ jobs: - os: macos-latest compiler: xcode - version: "13.1" + version: "14.3" - os: macos-latest compiler: xcode - version: "14.2" + version: "15.3" - os: macos-11 compiler: g++ From 47bb601cc9503b5a70a878d0b330fe18be2a5186 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sun, 2 Jun 2024 10:51:51 -0700 Subject: [PATCH 261/383] Update humlib. --- include/hum/humlib.h | 37 ++- src/hum/humlib.cpp | 626 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 628 insertions(+), 35 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index fb231506a94..0a7e35d1a83 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Sat May 4 10:07:24 PDT 2024 +// Last Modified: Tue May 14 03:29:40 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -1551,6 +1551,7 @@ class HumdrumToken : public std::string, public HumHash { bool isInstrumentCode (void) { return isInstrumentDesignation(); } bool isInstrumentClass (void); bool isInstrumentGroup (void); + bool isInstrumentNumber (void); bool isModernInstrumentName (void); bool isModernInstrumentAbbreviation(void); bool isOriginalInstrumentName (void); @@ -8209,6 +8210,40 @@ class Tool_imitation : public HumTool { }; +class Tool_instinfo : public HumTool { + public: + Tool_instinfo (void); + ~Tool_instinfo () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (HumdrumFile& infile); + void updateInstrumentLine(HumdrumFile& infile, int inumIndex, + std::map& value, + std::map& track2kindex, + const std::string& prefix); + void insertInstrumentInfo(HumdrumFile& infile, int index, + std::map& info, const std::string& prefix, + const std::string& key, std::map& track2kindex); + void printLine (HumdrumFile& infile, int index); + void printLine (HumdrumFile& infile, int index, + const std::string& key); + + private: + std::map m_icode; // instrument code, e.g., *Iflt + std::map m_iclass; // instrument class, e.g., *Iww + std::map m_iname; // instrument name, e.g., *I"flute + std::map m_iabbr; // instrument name, e.g., *I'flt. + std::map m_inum; // instrument number, e.g., *I#2 + +}; + + class Tool_kern2mens : public HumTool { public: Tool_kern2mens (void); diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 9eee4f4bb42..4d57aba0df3 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Sat May 4 10:07:24 PDT 2024 +// Last Modified: Tue May 14 03:29:40 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -33941,7 +33941,8 @@ bool HumdrumToken::isInstrumentGroup(void) { ////////////////////////////// // -// HumdrumToken::isInstrumentName -- True if an instrument name token. +// HumdrumToken::isInstrumentName -- True if an instrument name token, +// such as *I"Flute 1 // bool HumdrumToken::isInstrumentName(void) { @@ -33954,6 +33955,23 @@ bool HumdrumToken::isInstrumentName(void) { +////////////////////////////// +// +// HumdrumToken::isInstrumentNumber -- True if an instrument number token, +// such as *I#2 for a second instrument (Such as Flute 2) +// + +bool HumdrumToken::isInstrumentNumber(void) { + HumRegex hre; + if (hre.search(this, "^\\*I#\\d+$")) { + return true; + } else { + return false; + } +} + + + ////////////////////////////// // // HumdrumToken::isModernInstrumentName -- True if a modern instrument name token. @@ -51502,7 +51520,7 @@ int Options::getRegIndex(const string& optionName) { print(cout); exit(0); #endif - return +1; + return -1; } auto it = m_optionList.find(optionName); @@ -51560,7 +51578,6 @@ bool Options::isOption(const string& aString, int& argp) { #define OPTION_FORM_CONTINUE 2 int Options::storeOption(int index, int& position, int& running) { -cerr << "STORING OPTION INDEX " << index << endl; int optionForm; char tempname[1024]; char optionType = '\0'; @@ -51640,11 +51657,7 @@ cerr << "STORING OPTION INDEX " << index << endl; if (index >= (int)m_argv.size()) { m_error << "Error: last option requires a parameter" << endl; - #ifdef __EMSCRIPTEN__ - return +1; - #else return -1; - #endif } setModified(tempname, &m_argv[index][position]); @@ -53644,7 +53657,7 @@ int Tool_addlabels::getExpansionIndex(HumdrumFile& infile) { if ((instIndex < 0) && token->compare(0, 3, "*I\"") == 0) { instIndex = i; } - if ((abbrIndex < 0) && token->compare(0, 3, "*I\"") == 0) { + if ((abbrIndex < 0) && token->compare(0, 3, "*I'") == 0) { abbrIndex = i; } if ((keySigIndex < 0) && token->isKeySignature()) { @@ -57898,7 +57911,7 @@ Tool_chint::Tool_chint(void) { define("i|intervals=b", "display interval names"); define("B|no-color-bottom=b", "do not color top analysis staff"); define("T|no-color-top=b", "do not color bottom analysis staff"); - define("8|octave=b", "do not collapse P8 to P1"); + define("8|preserve-octave=b", "do not collapse P8 to P1"); } @@ -57970,7 +57983,7 @@ void Tool_chint::initialize(void) { m_noColorBotQ = getBoolean("no-color-bottom"); m_noColorTopQ = getBoolean("no-color-top"); m_negativeQ = getBoolean("negative"); - m_octaveQ = getBoolean("octave"); + m_octaveQ = getBoolean("preserve-octave"); m_middleQ = getBoolean("middle"); } @@ -58273,7 +58286,7 @@ string Tool_chint::getIntervalToken(int interval, HumdrumFile& infile, int line) if (botTieQ && topTieQ) { return "."; } - + if (interval > 40) { // Above an octave is not handled. return "."; @@ -58403,7 +58416,7 @@ void Tool_chint::dissonanceColoring(void) { m_color[3] = "white"; // unused m_color[4] = "red"; // dd2 m_color[5] = "orange"; // m2 - m_color[6] = "limegreen"; // M2 + m_color[6] = "lawngreen"; // M2 m_color[7] = "royalblue"; // A2 m_color[8] = "steelblue"; // AA2 m_color[9] = "white"; // AA1 @@ -82054,7 +82067,6 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { #ifdef __EMSCRIPTEN__ bool optionList = getBoolean("options"); if (optionList) { - cerr << "GOT HERE BEFORE PRINT EMSCDRIPTEN" << endl; printEmscripten(m_humdrum_text); m_humdrum_text << infile; } @@ -82120,6 +82132,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(humtr, infile, commands[i].second, status); } else if (commands[i].first == "imitation") { RUNTOOL(imitation, infile, commands[i].second, status); + } else if (commands[i].first == "instinfo") { + RUNTOOL(instinfo, infile, commands[i].second, status); } else if (commands[i].first == "kern2mens") { RUNTOOL(kern2mens, infile, commands[i].second, status); } else if (commands[i].first == "kernify") { @@ -89199,6 +89213,559 @@ int Tool_imitation::compareSequences(vector& attack1, +///////////////////////////////// +// +// Tool_instinfo::Tool_instinfo -- Set the recognized options for the tool. +// + +Tool_instinfo::Tool_instinfo(void) { + define("c|instrument-class=s", "instrument class by kern spine"); + define("i|instrument-code=s", "instrument codes by kern spine"); + define("m|instrument-number=s", "instrument number by kern spine"); + define("n|instrument-name=s", "instrument name by kern spine"); + define("a|instrument-abbreviation=s", "instrument class by kern spine"); +} + + + +///////////////////////////////// +// +// Tool_instinfo::run -- Do the main work of the tool. +// + +bool Tool_instinfo::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i kernstarts; + kernstarts = infile.getKernSpineStartList(); + int ksize = (int)kernstarts.size(); + + + // Store instrument class, such as *Iww + // Separated by semicolons and/or spaces + string iclass = Convert::trimWhiteSpace(getString("instrument-class")); + vector pieces; + hre.split(pieces, iclass, "[\\s:;,]+"); + if ((iclass.find(":") == string::npos) && ((int)pieces.size() == ksize)) { + for (int i=0; i kspines; + kspines = infile.getKernSpineStartList(); + vector ktracks(kspines.size(), -1);; + for (int i=0; i<(int)kspines.size(); i++) { + ktracks[i] = kspines[i]->getTrack(); + } + map track2kindex; + for (int i=0; i<(int)ktracks.size(); i++) { + track2kindex[ktracks[i]] = i+1; + } + + int gpsIndex = -1; + int exinterpIndex = -1; + int iclassIndex = -1; + int icodeIndex = -1; + int inameIndex = -1; + int iabbrIndex = -1; + int inumIndex = -1; + + HumRegex hre; + + for (int i=0; iisKern()) { + continue; + } + if (hre.search(token, "^\\*staff\\d")) { + gpsIndex = i; + } + if (hre.search(token, "^\\*part\\d")) { + gpsIndex = i; + } + if (hre.search(token, "^\\*group\\d")) { + gpsIndex = i; + } + if (token->isInstrumentClass()) { + iclassIndex = i; + } + if (token->isInstrumentCode()) { + icodeIndex = i; + } + if (token->isInstrumentName()) { + inameIndex = i; + } + if (token->isInstrumentAbbreviation()) { + iabbrIndex = i; + } + if (token->isInstrumentNumber()) { + inumIndex = i; + } + } + } + + if ((iclassIndex > 0) && !m_iclass.empty()) { + updateInstrumentLine(infile, iclassIndex, m_iclass, track2kindex, "*IC"); + } + if ((icodeIndex > 0) && !m_icode.empty()) { + updateInstrumentLine(infile, icodeIndex, m_icode, track2kindex, "*I"); + } + if ((inumIndex > 0) && !m_inum.empty()) { + updateInstrumentLine(infile, inumIndex, m_inum, track2kindex, "*I"); + } + if ((inameIndex > 0) && !m_iname.empty()) { + updateInstrumentLine(infile, inameIndex, m_iname, track2kindex, "*I\""); + } + if ((iabbrIndex > 0) && !m_iabbr.empty()) { + updateInstrumentLine(infile, iabbrIndex, m_iabbr, track2kindex, "*I'"); + } + + // Insertion line of instrument info after given line; + // Add above given index: + int aclassIndex = -1; + int acodeIndex = -1; + int anumIndex = -1; + int anameIndex = -1; + int aabbrIndex = -1; + // Or add below given index: + int bclassIndex = -1; + int bcodeIndex = -1; + int bnumIndex = -1; + int bnameIndex = -1; + int babbrIndex = -1; + + // Where to place instrument class: + if ((iclassIndex < 0) && !m_iclass.empty()) { + if (icodeIndex > 0) { + aclassIndex = icodeIndex; + } else if (inumIndex > 0) { + aclassIndex = inumIndex; + } else if (inameIndex > 0) { + aclassIndex = inameIndex; + } else if (iabbrIndex > 0) { + aclassIndex = iabbrIndex; + } else if (gpsIndex > 0) { + bclassIndex = gpsIndex; + } else if (exinterpIndex >= 0) { + bclassIndex = exinterpIndex; + } + } + + // Where to place instrument code: + if ((icodeIndex < 0) && !m_icode.empty()) { + if (iclassIndex > 0) { + bcodeIndex = iclassIndex; + } else if (inumIndex > 0) { + acodeIndex = inumIndex; + } else if (inameIndex > 0) { + acodeIndex = inameIndex; + } else if (iabbrIndex > 0) { + acodeIndex = iabbrIndex; + } else if (gpsIndex > 0) { + bcodeIndex = gpsIndex; + } else if (exinterpIndex >= 0) { + bcodeIndex = exinterpIndex; + } + } + + // Where to place instrument number: + if ((inumIndex < 0) && !m_inum.empty()) { + if (icodeIndex > 0) { + bnumIndex = icodeIndex; + } else if (iclassIndex > 0) { + bnumIndex = iclassIndex; + } else if (inameIndex > 0) { + anumIndex = inameIndex; + } else if (iabbrIndex > 0) { + anumIndex = iabbrIndex; + } else if (gpsIndex > 0) { + bnumIndex = gpsIndex; + } else if (exinterpIndex >= 0) { + bnumIndex = exinterpIndex; + } + } + + // Where to place instrument name: + if ((inameIndex < 0) && !m_iname.empty()) { + if (inumIndex > 0) { + bnameIndex = inumIndex; + } else if (icodeIndex > 0) { + bnameIndex = icodeIndex; + } else if (iclassIndex > 0) { + bnameIndex = iclassIndex; + } else if (iabbrIndex > 0) { + anameIndex = iabbrIndex; + } else if (gpsIndex > 0) { + bnameIndex = gpsIndex; + } else if (exinterpIndex >= 0) { + bnameIndex = exinterpIndex; + } + } + + // Where to place instrument abbreviation: + if ((iabbrIndex < 0) && !m_iabbr.empty()) { + if (inameIndex > 0) { + babbrIndex = inameIndex; + } else if (inumIndex > 0) { + babbrIndex = inumIndex; + } else if (icodeIndex > 0) { + babbrIndex = icodeIndex; + } else if (iclassIndex > 0) { + babbrIndex = iclassIndex; + } else if (gpsIndex > 0) { + babbrIndex = gpsIndex; + } else if (exinterpIndex >= 0) { + babbrIndex = exinterpIndex; + } + } + + if (aclassIndex > 0) { + insertInstrumentInfo(infile, aclassIndex, m_iclass, "*IC", "above-class", track2kindex); + } else if (bclassIndex >= 0) { + insertInstrumentInfo(infile, bclassIndex, m_iclass, "*IC", "below-class", track2kindex); + } + + if (acodeIndex > 0) { + insertInstrumentInfo(infile, acodeIndex, m_icode, "*I", "above-code", track2kindex); + } else if (bcodeIndex >= 0) { + insertInstrumentInfo(infile, bcodeIndex, m_icode, "*I", "below-code", track2kindex); + } + + if (anumIndex > 0) { + insertInstrumentInfo(infile, anumIndex, m_inum, "*I", "above-num", track2kindex); + } else if (bnumIndex >= 0) { + insertInstrumentInfo(infile, bnumIndex, m_inum, "*I", "below-num", track2kindex); + } + + if (anameIndex > 0) { + insertInstrumentInfo(infile, anameIndex, m_iname, "*I\"", "above-name", track2kindex); + } else if (bnameIndex >= 0) { + insertInstrumentInfo(infile, bnameIndex, m_iname, "*I\"", "below-name", track2kindex); + } + + if (aabbrIndex > 0) { + insertInstrumentInfo(infile, aabbrIndex, m_iabbr, "*I'", "above-abbr", track2kindex); + } else if (babbrIndex >= 0) { + insertInstrumentInfo(infile, babbrIndex, m_iabbr, "*I'", "below-abbr", track2kindex); + } + + infile.createLinesFromTokens(); + + bool dataQ = false; + for (int i=0; i& info, const string& prefix, const string& key, map& track2kindex) { + + for (int j=0; jisKern()) { + token->setValue("auto", key, "*"); + continue; + } + int track = token->getTrack(); + int kindex = track2kindex[track] - 1; + if (kindex < 0) { + token->setValue("auto", key, "*"); + continue; + } + if (((key == "above-class") || (key == "below-class")) && info[kindex].empty()) { + token->setValue("auto", key, "*"); + continue; + } + if (((key == "above-code") || (key == "below-code")) && info[kindex].empty()) { + token->setValue("auto", key, "*"); + continue; + } + if (((key == "above-num") || (key == "below-num")) && info[kindex].empty()) { + token->setValue("auto", key, "*"); + continue; + } + string newtext = prefix + info[kindex]; + token->setValue("auto", key, newtext); + } +} + + + +////////////////////////////// +// +// Tool_instinfo::printLine -- +// + +void Tool_instinfo::printLine(HumdrumFile& infile, int index) { + HTp first = infile.token(index, 0); + + if (!first->getValue("auto", "above-class").empty()) { + printLine(infile, index, "above-class"); + } + if (!first->getValue("auto", "above-code").empty()) { + printLine(infile, index, "above-code"); + } + if (!first->getValue("auto", "above-num").empty()) { + printLine(infile, index, "above-num"); + } + if (!first->getValue("auto", "above-name").empty()) { + printLine(infile, index, "above-name"); + } + if (!first->getValue("auto", "above-abbr").empty()) { + printLine(infile, index, "above-abbr"); + } + + m_humdrum_text << infile[index] << endl; + + if (!first->getValue("auto", "below-class").empty()) { + printLine(infile, index, "below-class"); + } + if (!first->getValue("auto", "below-code").empty()) { + printLine(infile, index, "below-code"); + } + if (!first->getValue("auto", "below-num").empty()) { + printLine(infile, index, "below-num"); + } + if (!first->getValue("auto", "below-name").empty()) { + printLine(infile, index, "below-name"); + } + if (!first->getValue("auto", "below-abbr").empty()) { + printLine(infile, index, "below-abbr"); + } + +} + + + +////////////////////////////// +// +// Tool_instinfo::printLine -- +// + +void Tool_instinfo::printLine(HumdrumFile& infile, int index, const string& key) { + for (int j=0; jgetValue("auto", key); + if (value.empty()) { + value = "*"; + } + m_humdrum_text << value; + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; +} + + + +////////////////////////////// +// +// Tool_instinfo::updateInstrumentLine -- +// + +void Tool_instinfo::updateInstrumentLine(HumdrumFile& infile, int index, + map& values, map& track2kindex, + const string& prefix) { + for (int j = 0; jisKern()) { + continue; + } + int track = token->getTrack(); + int kindex = track2kindex[track] - 1; + if (kindex < 0) { + continue; + } + string value = values[kindex]; + if (value.empty()) { + continue; + } + value = prefix + value; + token->setText(value); + } +} + + + + ///////////////////////////////// // // Tool_kern2mens::Tool_kern2mens -- Set the recognized options for the tool. @@ -116826,29 +117393,20 @@ void Tool_shed::initializeSegment(HumdrumFile& infile) { // vector Tool_shed::addToExInterpList(void) { - vector output(1); string elist = getString("exclusive-interpretations"); - for (int i=0; i<(int)elist.size(); i++) { - if (isspace(elist[i]) || (elist[i] == ',')) { - if (!output.back().empty()) { - output.push_back(""); - } - } else { - output.back() += elist[i]; - } - } - if (output.back().empty()) { - output.resize((int)output.size() - 1); - } - for (int i=0; i<(int)output.size(); i++) { - if (output[i].compare(0, 2, "**") == 0) { - continue; - } - if (output[i].compare(0, 1, "*") == 0) { - output[i] = "*" + output[i]; + elist = Convert::trimWhiteSpace(elist); + HumRegex hre; + hre.replaceDestructive(elist, "", "^[,;\\s*]+"); + hre.replaceDestructive(elist, "", "[,;\\s*]+$"); + vector pieces; + hre.split(pieces, elist, "[,;\\s*]+"); + + vector output; + for (int i=0; i Date: Sun, 2 Jun 2024 20:39:30 +0200 Subject: [PATCH 262/383] Take into account the glyph anchors in beams --- src/beam.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/beam.cpp b/src/beam.cpp index 5e30dfde1c5..6e643e4ac13 100644 --- a/src/beam.cpp +++ b/src/beam.cpp @@ -1882,9 +1882,16 @@ void BeamElementCoord::SetDrawingStemDir(data_STEMDIRECTION stemDir, const Staff m_stem->SetDrawingStemDir(stemDir); m_yBeam = m_element->GetDrawingY(); - m_x += (STEMDIRECTION_up == stemDir) - ? 2 * m_element->GetDrawingRadius(doc) - doc->GetDrawingStemWidth(staff->m_drawingStaffSize) / 2 - : doc->GetDrawingStemWidth(staff->m_drawingStaffSize) / 2; + + // Move and take into account the glyph cut-outs + if (STEMDIRECTION_up == stemDir) { + m_x += stemInterface->GetStemUpSE(doc, staff->m_drawingStaffSize, interface->m_cueSize).x; + m_x -= doc->GetDrawingStemWidth(staff->m_drawingStaffSize) / 2; + } + else { + m_x += stemInterface->GetStemDownNW(doc, staff->m_drawingStaffSize, interface->m_cueSize).x; + m_x += doc->GetDrawingStemWidth(staff->m_drawingStaffSize) / 2; + } if (m_tabDurSym && !m_closestNote) { m_yBeam = m_tabDurSym->GetDrawingY(); From e71c328ddec4347ae16e227ae90a2777b4316a3e Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 3 Jun 2024 09:31:07 -0400 Subject: [PATCH 263/383] Fix empty syl not showing for neume lines Refs: https://github.com/DDMAL/Neon/issues/1218 --- src/iomei.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iomei.cpp b/src/iomei.cpp index 5d1e8ae1200..019e414320c 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -6985,7 +6985,7 @@ bool MEIInput::ReadStem(Object *parent, pugi::xml_node stem) bool MEIInput::ReadSyl(Object *parent, pugi::xml_node syl) { // Add empty text node for empty syl element for invisible bbox in neume notation - if (!syl.first_child() && m_doc->IsFacs() && (m_doc->m_notationType == NOTATIONTYPE_neume)) { + if (!syl.first_child() && m_doc->HasFacsimile() && m_doc->IsNeumeLines()) { syl.text().set(""); } Syl *vrvSyl = new Syl(); From 85288bfb6f6c64d623251742873295279a3140a5 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 3 Jun 2024 21:06:45 +0200 Subject: [PATCH 264/383] activate missing tablature duration --- src/view_tab.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/view_tab.cpp b/src/view_tab.cpp index c1172885fb7..dd28a98fe09 100644 --- a/src/view_tab.cpp +++ b/src/view_tab.cpp @@ -177,8 +177,7 @@ void View::DrawTabDurSym(DeviceContext *dc, LayerElement *element, Layer *layer, if (!tabGrp->IsInBeam() && !staff->IsTabGuitar()) { int symc = 0; switch (drawingDur) { - // TODO SMUFL_EBA6_luteDurationDoubleWhole is defined by SMUFL but not yet implemented in Verovio - /* case DUR_1: symc = SMUFL_EBA6_luteDurationDoubleWhole; break; // 1 back flag */ + case DUR_1: symc = SMUFL_EBA6_luteDurationDoubleWhole; break; // 1 back flag */ case DUR_2: symc = SMUFL_EBA7_luteDurationWhole; break; // 0 flags case DUR_4: symc = SMUFL_EBA8_luteDurationHalf; break; // 1 flag case DUR_8: symc = SMUFL_EBA9_luteDurationQuarter; break; // 2 flags From 17042233b97e3cc529534ff75a839cc1298cd98a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 4 Jun 2024 08:55:13 +0200 Subject: [PATCH 265/383] Update Leipzig to 5.2.89 [skip-ci] --- data/Leipzig.css | 2 +- data/Leipzig.xml | 10 ++++++++-- fonts/Leipzig/Leipzig.svg | 6 +++--- fonts/Leipzig/leipzig_metadata.json | 22 +++++++++++++++++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/data/Leipzig.css b/data/Leipzig.css index ddea8607924..d5738fe4f23 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index 31e33ecb8ae..ac4a5947a07 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -156,8 +156,14 @@ - - + + + + + + + + diff --git a/fonts/Leipzig/Leipzig.svg b/fonts/Leipzig/Leipzig.svg index 533fab01983..a48594545db 100644 --- a/fonts/Leipzig/Leipzig.svg +++ b/fonts/Leipzig/Leipzig.svg @@ -2,11 +2,11 @@ -Created by FontForge 20230101 at Sat Feb 24 19:01:45 2024 - By Klaus Rettinghaus +Created by FontForge 20220308 at Tue Jun 4 08:52:38 2024 + By Laurent Pugin Created by Etienne Darbellay, Jean-Francois Marti, Laurent Pugin, and Klaus Rettinghaus. This font is licensed under the SIL Open Font License \(http://scripts.sil.org/OFL\). -Version 5.2.88 +Version 5.2.89 diff --git a/fonts/Leipzig/leipzig_metadata.json b/fonts/Leipzig/leipzig_metadata.json index ee909e258c6..8947b2f0545 100644 --- a/fonts/Leipzig/leipzig_metadata.json +++ b/fonts/Leipzig/leipzig_metadata.json @@ -30,7 +30,7 @@ "tupletBracketThickness": 0.16 }, "fontName": "Leipzig", - "fontVersion": "5.2.88", + "fontVersion": "5.2.89", "glyphBBoxes": { "4stringTabClef": { "bBoxNE": [ @@ -7680,6 +7680,26 @@ -0.5 ] }, + "mensuralNoteheadMinimaWhite": { + "stemDownNW": [ + 0.584, + -0.7 + ], + "stemUpSE": [ + 0.664, + 0.7 + ] + }, + "mensuralNoteheadSemiminimaWhite": { + "stemDownNW": [ + 0.584, + -0.7 + ], + "stemUpSE": [ + 0.664, + 0.7 + ] + }, "noteheadBlack": { "cutOutNW": [ 0.144, From f99f6a0ae5b171516ced12251d456f0e7e00b342 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 4 Jun 2024 11:31:14 +0200 Subject: [PATCH 266/383] Add View::DrawObliqueLine method [skip-ci] --- include/vrv/view.h | 2 ++ src/view_graph.cpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/vrv/view.h b/include/vrv/view.h index 485d1d9b1a9..5beb16884b1 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -562,6 +562,8 @@ class View { void DrawVerticalLine(DeviceContext *dc, int y1, int y2, int x1, int width, int dashLength = 0, int gapLength = 0); void DrawHorizontalLine( DeviceContext *dc, int x1, int x2, int y1, int width, int dashLength = 0, int gapLength = 0); + void DrawObliqueLine( + DeviceContext *dc, int x1, int x2, int y1, int y2, int width, int dashLength = 0, int gapLength = 0); void DrawVerticalSegmentedLine( DeviceContext *dc, int x1, SegmentedLine &line, int width, int dashLength = 0, int gapLength = 0); void DrawHorizontalSegmentedLine( diff --git a/src/view_graph.cpp b/src/view_graph.cpp index 8165042c2a0..ac6ede3a9c8 100644 --- a/src/view_graph.cpp +++ b/src/view_graph.cpp @@ -52,6 +52,20 @@ void View::DrawHorizontalLine(DeviceContext *dc, int x1, int x2, int y1, int wid return; } +void View::DrawObliqueLine(DeviceContext *dc, int x1, int x2, int y1, int y2, int width, int dashLength, int gapLength) +{ + assert(dc); + + dc->SetPen(m_currentColor, std::max(1, ToDeviceContextX(width)), AxSOLID, dashLength, gapLength); + dc->SetBrush(m_currentColor, AxSOLID); + + dc->DrawLine(ToDeviceContextX(x1), ToDeviceContextY(y1), ToDeviceContextX(x2), ToDeviceContextY(y2)); + + dc->ResetPen(); + dc->ResetBrush(); + return; +} + void View::DrawVerticalSegmentedLine( DeviceContext *dc, int x1, SegmentedLine &line, int width, int dashLength, int gapLength) { From 398bc102d541a1b53e71d627d70676d5d0dcb40c Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 5 Jun 2024 15:17:00 +0200 Subject: [PATCH 267/383] Update Leipzig to 5.2.90 --- fonts/Leipzig/Leipzig.svg | 30 +++++++++- fonts/Leipzig/leipzig_metadata.json | 90 +++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/fonts/Leipzig/Leipzig.svg b/fonts/Leipzig/Leipzig.svg index a48594545db..26422411775 100644 --- a/fonts/Leipzig/Leipzig.svg +++ b/fonts/Leipzig/Leipzig.svg @@ -2,7 +2,7 @@ -Created by FontForge 20220308 at Tue Jun 4 08:52:38 2024 +Created by FontForge 20220308 at Wed Jun 5 15:10:21 2024 By Laurent Pugin Created by Etienne Darbellay, Jean-Francois Marti, Laurent Pugin, and Klaus Rettinghaus. This font is licensed under the SIL Open Font License \(http://scripts.sil.org/OFL\). @@ -2437,5 +2437,33 @@ d="M120.146 498.265c0.224774 0 17.1897 -1.82497 17.1897 -14.4827c0 -13.3186 -15. /> + + + + + + + + + diff --git a/fonts/Leipzig/leipzig_metadata.json b/fonts/Leipzig/leipzig_metadata.json index 8947b2f0545..113fe16ddb9 100644 --- a/fonts/Leipzig/leipzig_metadata.json +++ b/fonts/Leipzig/leipzig_metadata.json @@ -4462,6 +4462,96 @@ -0.188 ] }, + "mensuralProportion1": { + "bBoxNE": [ + 0.72, + 0.76 + ], + "bBoxSW": [ + -0.0006, + -0.8 + ] + }, + "mensuralProportion2": { + "bBoxNE": [ + 1.0161, + 0.7601 + ], + "bBoxSW": [ + 0.0, + -0.8 + ] + }, + "mensuralProportion3": { + "bBoxNE": [ + 0.864, + 0.76 + ], + "bBoxSW": [ + 0.004, + -0.804 + ] + }, + "mensuralProportion4": { + "bBoxNE": [ + 1.016, + 0.72 + ], + "bBoxSW": [ + 0.0, + -0.808 + ] + }, + "mensuralProportion5": { + "bBoxNE": [ + 1.016, + 0.768 + ], + "bBoxSW": [ + -0.008, + -0.8001 + ] + }, + "mensuralProportion6": { + "bBoxNE": [ + 0.912, + 0.76 + ], + "bBoxSW": [ + -0.0213, + -0.8 + ] + }, + "mensuralProportion7": { + "bBoxNE": [ + 0.932, + 0.76 + ], + "bBoxSW": [ + 0.0, + -0.8 + ] + }, + "mensuralProportion8": { + "bBoxNE": [ + 0.932, + 0.76 + ], + "bBoxSW": [ + 0.0, + -0.8 + ] + }, + "mensuralProportion9": { + "bBoxNE": [ + 0.912, + 0.76 + ], + "bBoxSW": [ + -0.0213, + -0.8 + ] + }, "mensuralProportionProportioDupla1": { "bBoxNE": [ 2.2, From b0e16de93c57f6819611f98540b6c6c9040f5bb4 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 5 Jun 2024 15:17:32 +0200 Subject: [PATCH 268/383] Add proportion number glyphs --- data/Bravura.css | 2 +- data/Bravura.xml | 5 +++++ data/Bravura/E926.xml | 1 + data/Bravura/E927.xml | 1 + data/Bravura/E928.xml | 1 + data/Bravura/E929.xml | 1 + data/Bravura/F43D.xml | 1 + data/Gootville.css | 2 +- data/Leipzig.css | 2 +- data/Leipzig.xml | 9 +++++++++ data/Leipzig/E926.xml | 1 + data/Leipzig/E927.xml | 1 + data/Leipzig/E928.xml | 1 + data/Leipzig/E929.xml | 1 + data/Leipzig/EE90.xml | 1 + data/Leipzig/EE91.xml | 1 + data/Leipzig/EE92.xml | 1 + data/Leipzig/EE93.xml | 1 + data/Leipzig/EE94.xml | 1 + data/Leland.css | 2 +- data/Petaluma.css | 2 +- fonts/supported.xml | 18 +++++++++--------- include/vrv/smufl.h | 11 ++++++++++- 23 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 data/Bravura/E926.xml create mode 100644 data/Bravura/E927.xml create mode 100644 data/Bravura/E928.xml create mode 100644 data/Bravura/E929.xml create mode 100644 data/Bravura/F43D.xml create mode 100644 data/Leipzig/E926.xml create mode 100644 data/Leipzig/E927.xml create mode 100644 data/Leipzig/E928.xml create mode 100644 data/Leipzig/E929.xml create mode 100644 data/Leipzig/EE90.xml create mode 100644 data/Leipzig/EE91.xml create mode 100644 data/Leipzig/EE92.xml create mode 100644 data/Leipzig/EE93.xml create mode 100644 data/Leipzig/EE94.xml diff --git a/data/Bravura.css b/data/Bravura.css index 135c1441c3e..f3bcd5d1a3f 100644 --- a/data/Bravura.css +++ b/data/Bravura.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Bravura'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAOgQAA0AAAACnEgAAOe2AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4GVgCVNBEIComkMIerRQuaVAABNgIkA5pIBCAFgwYHwkpbohFyR9z2iQISle6EkpVRVfzxBWPXg7tVdRwSw69Qtu2K5W5ViXgyTbL/////zKQyhjYFEgoK4tx+v31GEAX0VE1hrfVmW7d9BExoe4QdAzU4elC1uvRqhuEygbYa1oY6H5F0wenQ4rk6RnHb2XvvsCEHKGPPNspRvvUSPkxtzgndWnEbCqsz80T28MJGASt7jOE0i+KOqJ5+2glns8h2+L8JK/3CCwlDqKTbIdQ8sxvxT+6qoks6kslpFJ03fREr7dNtl/OpdOhXScRNzm1SCm2/Mq32uJP+y/m6b74uuKdFyszNjN/SlgFl3Howso5cWXny9UO8+jeTdNmTv2ic0T0oQ7SczeqJ7bnnLjmxiN55xC5mRC6ihBAlQNDgXijWFinmLbSUGm0ppXVaqrR8i1SMpxz/bekMjz2ZWeKwVROWF47kiIjzw6R/M+nfsnsSa4vUJPHfiv2dUFGe//6InfvfbxVuIgmXFbzJNqDEE8g4ahJIuzocixUbEu3fd+09IUfPozLTbnpez37KJW1TggPsf4BSmQ2+DsC+wennHYKOoTYF/HMBMLBNH3JFTUdKP7RpFSbBaDTSgtfOrlGxE4cYD4Cr67/86ouWmuYHt/lQZ4qYD0r84DaJ+SW0qeJ+pwLOvB0gT9JfqUAF6cA2fa6Y2xDt4SiD+4gzzAphGyiuPm+IlrMRW7Hb08TvIgq5XAQSJHcXxaMEKUVCUAlaLEBQrWHFSynSwlOh1IRSoUq/tNQMSsX4v29qX9144JyqXe7qEhgCAstOonkhGA9yurssKWRr4PECZeIfaZels3/qbkEmzrzIssCSHbItNA0ZuGEJ/nc/H/1fTqsqe7Bq9ibZnc0MpzFNA8QNgqQ9BIfrLle9vZWuv35JVtxokGOH2G43DSbNsrwEl+P+ry5VXjZp/XqnvDjAvgDABFiQk7mdps53qxxUCPwiBdEubHQg1YH+ay3f/AOyswF2qRgVGWGQjInsjT07e0hRSEZSDBDkiQnHK22YJvP4OnP9H9BZ4BJxVVzpvAfpfhmWgyulvj3t7b8I9AvCXwVZxUUSBguwL5KuCcEViSpcgA/9OvaV0r8k8/yBMxEy2EapLldK6UDSJ56HydluTIaxtLJlmkqdM00Zxr71raxbtqZKMylP12Q4luVT1ncTVkIsL1lZ6hyEFXD8MXL6/5upbzu7ixVBSvoH/HSgs76dO7voqOiYylhU7ubd+2bfzL0zu7NvZkHsDBZhl6TABSSRC1AB4Kc2EMDsACQBkJJB8gdKP6RIUPL5n/qRcgxFFWIunZvOreQo/cpF6T43tVsXXeOqs9+7Vtn6s5O3VQPqgFg4InfObXoo6a63nX2dJVSHqBjsOcNg9PH/jMmvftw9Jl9caO0PRBhYZhNSYq6XQtzjha3ab2/kjkLco0eCBCk2uGLFWhGRh4gN9vjs14JbP/azaquoR9BG6XHsZAAE9EL+3L+bZC6bsERe6NfieLasBLIhVjqm3t1Xcsic/42C4H5jO9s6gKw7kstCVP6YP/sth7311/qiVKQeVlwgqDcMnd9fFLRj3f+73VYzzCLGjA0BAgR4QQAoEf+unj9NGfi8TagV1wV649x4VUSUgoDWB4BoZVYLQOal6yLAGr5lKJAjMNh7kisv03Z4CsVq56o/m1C1RjVHO/dxd8SjSMioPIa/9RwJbUrUupbQWzQNgPfpqMD5VPLOpW76Gmerk8677l9g1kMIf9yFb3BAEQzBfIxgE76J/+hwII0yqISm0iJ6ht4gKd5EigiJGjkhb0pSf+pvtrOJ83knX+GeJ23S7XqvPmI0Zqc5ZG4Ytl920p6xl91PrfO0u8IQRpTDH1KHTSnIqKpeuHOhgz4GmexA7BNX/Qx0QD3ocQPewQxeaIQ56MVG3IXv4h0zgYiKqY3m0w46St+LJ2GPxhXLfvm+7NWv+o2JCU7m1XyU73DcEzXfu4uqT5bH8B3rQUxgLH7++IGovkGdMSPDOF9DDW+cjrOHDaqQ7+3bF+n8nws94BQssM/jaL2+2Cpp/nSzTqrrayf8GgKgAtHFmtRkpieDmZddeSs38mVaOHlw/nylf1xjsQU2vBGNChAVJjjNE5uOmTmTs3Wuz++bs907defuEkDO20v3xn1yX9539q8HyQdBQJtwMHDw1sGNw62H08VY3yq85/sBEt/vyHPtc7/3A4gwoYwLqbSxnv8DAKhD5VobJ2mWF7y3uapLPaAsbdcP4zQv67Yf53U/7v0AEIIRFMMDG5pD0QzL8YIoyYqjbhWabpiW7bh/YIrBRrxJePMFbAFjYCvYBrZX3lGUVd20cb7amtoP4zQvv3Xbj/O6n/cDQAhGUAwnSIpmWI7/AwCsESVZUTXdMC3bcT0/CKM4SbO8KKu6abt+GKd5Wbf9OK/7eX8fgAkA4FVMKONCKm0SAMC7zoeYZnlRVnXTdkCX6+1e8FdTD+iRhFSsjXU+xJRLbUPXjdO8rNt+yB+ErFCq1DU0tbR1dPXp6QdACEZQDCdIimZYHl8gDDJiiVQmVyhVao1WpzcYTWaL1WZ3OF1uD+f1AYAgMAQKgyOQkUVjsHhCAtGpr0aeTKHS6Awmi83h8vgCoUgskcrkCqVKrdHq9AajyWzxs9rsDqfL7fH6AEAQGAKFwRFIFBqDxeEJRBLkMoVKozOYLDaHy+MLIqFILJHK5AqlSq3R6vQGo8lssdrsDmdR5HUeEW6P188HgBCMoBhOkBTNsBwviFKZXKFUqTVanT7Wf575N5rMFqvN7nC63Bh1JpvLF4qlcqVaa2hqHlla29o7OvkCMEFSdAaTxeZw+Xj8AAjBCIrhBEnRDMvjC4QisUQqkyuUKrWm26PV6Q1Gk9litdkdTpfbw3l9ACAIDIHC4AgkCo3B4vAEIolModLoDCaLzeHy+AKhSDzHnHPNbe7zmOe85j0fvcFoMlv8rDa7w+lye7w+ABAEhkBhcAQShcZgcXgCkUSmUGl0BpPF5nB5fIFQNF+JVCZXKFVqjVanN8zPZLZYbXaH0+X2eLf5+wAQghEUwwmSohmW4wVRKpMrlCq1RqvTG4wms8Vqszuc1qowzbAcL4iSrKiawWgyW6w2u8PJzJgkiqrphmnZ0YTuuJ4PgFAAwhNBMZwgKZpheXyBUCSWSGVyhVKl1mh1eoPRZLZYbXaH0+X2cF4fH7+AoJCwiGw5cuXJV6BQkagSpYqDTD/J0YmvD3zLA+J9mr0LqRYtEV2K5ZpOcM3duV9I++RpYceQfPgKUXgi8qnsCHwLX5BY1IFkIGCBwQNOPlRRJIoWGT3Ej/NMPW6MoqIfF5iqjOPMfzG4h2HQh0lZKDNg4AsCxPmIhC48utqIiyIQVFSwm1dsE1LtkJZp/mF+HWF5xLGkMQxPsLm6spDbg4VBWhsWDctsls7SuvbhYPnwikLWbqYCnbF+rh9htoD0puNbVIxLIukgAjJQouSiSDPJnmyyR2b60y0qiGK5Zw/NtSfnEz9nqBNT658e0BXTXcW64NwfOcCRbwyU8TxPil0dOpKjw8V4QIECBQkeMAqZnHvZX5zbEocpjtHPE6rQeQlOaKHjJd+4KzskzAZBGxqImVkYoy9bmy/Nt0ByEA1AE9Qlv4LJwTM76nkCRH4EAfKFK/bUxhWek7w/a7OnQfEvg0ywmuagU6u/wcdqJZ8LxHQ41fm+u651rEDnmnoi9RxOZkUluxk7nFpgG40VlZLLHtZdB3kxQBEiJc+mRMFyqXqJUigggi1Mbp5WSHzEERCMd60/nFjrZduhMbPh8B42nA8h9JBn80EE1TVQEiFUi5LxLDOkGqLIsZynemfkU9qmtZaUhjFSNWYUHmJzLJrDcEaP4JJGbfmag+RZoBXq3CecNA1LwzkQwNLW1hc1o0kMhyiYto+ECVLBIig1AwjEAqJUsKBDAL2FNHANQi3sBGoFGWALrpKhEj/j+iKZZ+v2jsi4SAPPnXUQmGwQ+H6F6qYOd+5Tax6bWs3Xu79tE+ZZ/RzFZlvcfPWHJp0aTqeShDFEKaMIIA4YA5iAyE3w9Yw2pFSM5h28Q5QTnsTQiljkSRnmOb2DFWOsBcjnAQCJ9lsz1z///K+9WhDEOTmwgWHIYo0xglyzFsnQP92ikMs7D+pKCxXiPTpkh0DK7T4EzLCIjZURrFKWaoP6J6VEMtM++jJW8RbpClzEZV+j9R+UFmWsf/2pUrlq0VTRVlEVTTF4SgErUZj2EkfSOi5jg3k0qSSSRDeMFhl5+XmFTqcvpyERHZxuq2qa1Tg5OHfH7Or6/uVTu6Ytr6ofGaqZP6eTLlPtRAivl8+nqrrh8FSnTHirxLJgkLKpzwp14U50dm+60HvbZEYuUzYjrluY2Tafz82A5jJZiDKH+UYsR2GKLR1p6zRAQ7IjFbun55annJhrU+CElADpdLoY3OhCNSwZf6wD02kHQlTHFShwDD1B+zozEFE2GwnRYI3mgOjJ9mWbvMyYsRhmizTKmaCXEHFycHNzVfRt+j69Umg4v9mMOHs+qtcDr6WlCvy4mtioSKS3sYDkn1LVKB1kjtMADtFR5FatYM35zf3h+aWtw3GGuSFYYWxq+dAD2SvXWK4bMMACnNnD2LlSKbZElCTd3XkIK93DMy1seF11dN78EA65wgTEbHe8hS1rEAfGIUqIxsLpyPwQTAegBqFdO9uMqYjdA3InD5O8NtEZyIasQ1+rPTF9ZRhbm+Q9bMQLZwpqpkW0NlDe4KWi+HCOaovNa8keCnblv35j3B+ncM0aM6PzeGwU9toDT8HT8CREqZRm8V4S3FscHV36BWKb50RukLEtzicNEYafEDyXypdq6O0Lz/ZMJZApqQGyhWwYv07Zyds9m1V3GmOhILZlPIoigGLAUIFjBEA8oqvbk44ExGijia1Gy9g6eGOTxGirxf/w2wKcRALczjBeKMtU0Zfc49yTYZiBScFbJaxlcPvCLXAL1EEdxBE4AgeGBw0GPeuPJhcH+GaIOMSlFKBXEkwbzuPTu8X5lf0Th+IrrQioVhkjWnSmIWqL2/DqHSRuldx0pR7dKUY0OIRJa2FUKZghcQJDAALXtNJFQw0St0ATXAGvYBDHg0FEwRKQjwroA7O7g12GE+83ZEfJEhm1gs7AaBjA3o+qT7ibT0RMza7eYY9s4CigeQb3rCEWG/NBprsMQVFCFYJPGgWxwLm45r1OF7PpYiYjRGvvamyjF1ZDwn4Y3UXnAgAY+xaEahjw/Lhwx9gceXs1z8FD+54jcfFfFE+fJIbggmpi/hY5uYvgsOEBrBzQVowY27BBalhbKBMTNiSIzbGd3JDIEEMGJgUUrBJgjSjjIr4eEe4yLWsw8xD0iT89aNyJ9HpzXBuMQDoKrAqAYuNpWO1eAPHCCLMiQYRBNfkh2UbrLAYReSR9J0BEZrNaWWxNg1ggVwwxGiBgFmGdGmE8NdOKQwMgZcYq3gCxQQj/YAeqwqrxljZNbzLk/+U3vRKxbKmaXmMKNS3HK8RKsWI4xBAwvBfFHB7WSHXn+wi+hQnZmAFHkZi2b6gZ01W9AOJDnxKVhu/LLBC7NnGUuzQr27rQpVV1gm7vmwiD9b1vEilJUAmbTa+/XzsgC8Ta8uefnjCcsCeDROPYBMcLvgleg6hj++wjj6dYlWwUXRnSlJ/9txJp5DhhatMZjUAoeBXfItYPs6ZXJ1nJEJtAwJ8FQNToumn2RAjzRhWrBcINsSoSATGd9KZB9qw1UaUEawywZkHY35gBEdGyRkbLfjNk/Jc44b7NqSSRVGNlbjFM04ZEYinAWUthW+VnVq22diZHcm/7VZ5+QVz7cbF4745xR2bNL80H1bq3pECduJvUKLrWQqRWkemYElOOFCAE+KoBTg1uNZuSFjfrxYScoOgC586qIpJTip1lcoVk5EzZZBB/vWrcwXqivIb1O4S3vVbarCJWQrJUWoAYtc+vsGIP//PAbNpFqp23jBDRk8Yd09VSndiCW1oXcKux9ZCrecRs36uJuQfQpxRR6V7OofZg6zVjrDwDyQU7JpatLKbcy3SzOslcDleho7UkQEHaJvVEu1RFyHRKQvw84UWBRKHVTBCKS9OpFSvPCLM1OgFsAIYuKAd1xfYWYVWMtb1Pa2KvN9GHnFQF/vnpKyA+WSiMKOL81qXvmtLAWgxNTVEaOhc0aF6h2ythdYlNvVerdKEmFhQKX5TlQmaoc6OLtDN6uuQk/yRQV58UH9sgEgQtF7S+e4zVWn5Ia2dXf2c8VcQDsbl1G0RBTah+KkxifqOLxzZzFncQkTc80M9NwAROeTeplI4pA1ESsY19+9qCzi1M0XGtBsqTaUkUvKKbe3c6sZjQwSqisA1Uth5CZLiBu0YYx+zpUZTN9D8CCPNpkvBZAm2miIB4ZGRnwHpRLgq5NkTs5dU1+aPyQqzGNbZEi615DdELKZVGiN++PnHR6Bo/tyCDmXyEoTzlRMj4lijZ4MW1ARQSroqIpGbbDuHNZ+l7mSAWeXVxX0E61vJ0rUoqsOzEOs9tcZoZjceWHzwLwikqVnuDnWdMla0374C0jJYP73WfOUrZJvmuVHaSzTKbdIBuf64OK3z/RRSI5/nwScNowwQH5bXpZYCU4Cn/oPoz61mo6xwK3CZrysj25lvtSy0wcqk/Quw0T+frGhzl939TyFcBJ1zH985M8J8uX3zblqrh8FDFN3kRLWP5HVJ/Qm8ja+Ck7xijmDAnsRfrdVDWqRK6/Z2FbMBjYs7KUs/FsGa+QtQs9YBQSNln+uZ8dQGmYlWq/Y39orW8vbOK+HXp0YvdIVZXpqAApnGBi53dUHXyWOdYqnA7i1SOOJ0xsv9iyLLKKazFa8obTNZJhmfxpKE4w1ckpRQiRBL8/KZViPdP30/46lUbS7afrUgOOfJqYqhTMeLi+7GWfy0QG1/vl2JCqbVSIzY/APEbdycJt8aq0vSPUcw1AImbxM+MLyeDUh/bGtaf1++Zy1V9ymfxasn6TtEYnidFipxp/5PC41aPTmi6ott+FLlB0UnBVJQlhUPVxm65i2jqEIhWcEhq5rR/NFSnGlepQgcKwIwc7Yc7H6ezF++peNghsW1uInp/ktSstIrPMcqjlXg5I2AUuRlogoHLjyvgy2yfSvumAcAB6ygIITD7q09oYRPEV++A7MtUPl8sRbNrnSsPM5qYWbLlmkO2KJmCKaEanLhvwHRYYwVk70xy9ifClLX7RGv5rVurrc5rnZWMJlwyIvEtYLLHsGBhfY6YufTA6zN7l/U3QBpYH39nIDytspBgCRlJkBr7vKN12SUMxsgZrFYEYt7gyv51yz4ECelPQRkjtLLQ+JZoi/2QvWVhdccwbgvzVR2B41WDWxFOZHyaOtsA+UbLhKVX+iLZC/VlDS7siEX8jICYNHx8+aTbvwXxLUevisEV/rK9NWam4zURPdUfxEqOGzOD1Bzxhpt0i95mzpVTAmcfuvkIf1KtYJdTMkyNxsm29WK+Koma9z/X5W3txmxyf+je8rN5MVBLTvAPSn1qSD0dMmRVf/nF4wpPqRTWtdpdEDK5QIwuITEBPkU39KXJJwWjvZ5KaVf2dmVp9WtizEuR6prqGNZlF5cfhrPeamtnBh3EqFBVenWlz4DgE0aoYjpXpgGPbkXusRpWnZyG2D7FYQ56636MBkPfgbhP1JQxeX4Zq0OkUECV2VHGq0xBY5oOM/5KHnAXJLNGNlF0tFG9VhBz91tlKDs3eLS9wjR4sTDTrLABiChFenoZ63Nhw4R0sYZ2evHxcPfMu9dlbT64twu0z27P5JOMklF+1/87YcXtpBkbFWbSjlsJg0R+TIoB6RCbIx1koa2i0nRQT1bATTLOESJyGewFsak3qobDdOGb+vHvYM5dlECPjFrnxfk81rUG1vkiViC1WO4miHobIVsyv+Eqy3Kw5Tfk+0xBISU1XM+brDwCydbWOEb+H2xA/PKsns4LRPj4Ahew+5BOdc0tKul8vbrU1uwBcYQ+vn0Kmj0hSs/Kbh4VJcDVJPZ18lv7GxEoIDzHY2WA9OyBQFVrAu0QxcKsiNqJwEBSKLT28iUIoqaOfpLYMFwysjJlGDqDDZHnaaXN24ND/JIv2K9vgh7tqbogDo2wSe0knZXrlWc6mHViXhkkDEPwyajQolWSDZoPEFfstoor4+wesp5BwoHEiSbWx1jN3Qy27AUYPuPkFcoPUDhhep/oSu7WRzwxlxAzMhH3PtrG9V1qjUJDXFyMqA3Eamniyb2VboJ0ZmWXpfdlG/nenExz+azxirJ602M0QXXComLucKoJHYtArsznEhOr9QexlmdEbZ07H0SbxTrfMnLsX7IeB0gOPYh2yPe9vTPQn1s4KvQqxaTr1dBcYLREubmABmFzVkCnBidk1pINn0OMZeQGD8l1Aj6fxFx+3YVJccgR5lR663hPgKwOMvFth0y79aavfEDvwdOzhRfCocpqp/3YCPj0lD9qJHI1eWyHi00hx04d99W6CxJiJqJXQWVgjzouIkj82gDaHYt4VwuEdMTrBxrPto4XxrOud8Ip5MDwHD/VhTnF/oGDEhSQjyuFz7uLpOv4j8DmqLh8PoZvmh/JH09+ekbynv6BNbAb+6SAJx7xUhAWw2dEGrwjUQFLp5ApJCok3Mjl5qoJ2Aq3qBtkw5YfrZSMzaZaNBQRHyGyUTmpcHwUqDpnC8UJ30uW5CqcUT2nwyEvIHFqCBCOwgye+rkgVT/xnn/sQy8lgGyvDaf+tpnnye93sMquzgzIcVEkLavcOXZrkQT196dt13Jxv7fPx0eZYOTmCTQol9lPfchid+xoID/siNCRnQnTNMwbOVyEFKMMUVll9Bz1lRHvDTsoU4Ovirw30IAMMlPObSs4uev7+31H5odqKZezQOz37c7d/MJSwMrOSuv5YhB/4UKr4j4BVBIZBVT8443yVdQIVxVG5q6as5VWca2wKvS43gaIoeuWlPwkS762Sf4WS9botbyU8sxGa7Laa79G4FY+UJAS1Uf41QXdKGM13ixMQgP9YnO7BAg0HdtCX8FUCxZATsxbF27pptsuAMRbKw9Km7S/9Dw82r4jpPUmekAi1MJVxonrlMx6LeePibaWqkm7AU00ctsXZb1oVVXmdlLzoTa9bmivdXBZp1BsGJxD4r8MZKnqcAxRZp2pMsjw6aZ5qBNsWEqV6i61p+rHp8UmhUkqB1P9d+LORuivkr8Aq6NjGkV+4CEZd2XSSJg5D6TQcD03GX0/Rc5oXYa9FInD3pAi9bYpQ87YLYfXv5UOIjEb9+4+L1G1dys0NMlchcgoTNLRz+zgR2ogyya3HWIP4sMmd6NYvZJ2hs8CSeNxV6ZoFU1GJh0SLxJNz/g+VRCFzRsK1fWCwI4XGnHD81Qy53BlSlpSr3YpCuSr5AaB4WZd5fiNmtTuVqvOqDe8rygSaucupUGDHo4mPDh8HyAp1U6+EWm75ElQUX8gUyptr/CLx0H5M1jpp6cti6Kbkg8ID4pOjp0V5HQRtKE1R0GOUs5c++VWfhMcAnc5T/z9cxIne/5mUrmYlKw9yQ3w3jhuSnQ1clWSg6RkTYQy6wzaFzWWDpBnoQqwb/PYBjlRzmmS/py9azBmjO4wxJwsbzt1ernxTYGrFPLYrlXguDf5bXMlOFDK/h8Q1H1J/Gju0RdfImDCMHaMbuCCsNnBmW/2pPiDq+ZSuLOYWychFsiyKNAw62hDppfXZrlbvkpRH/mJIGTQm1vfDy7o7t4VJTeE+TbSTT+NfKpo7JOoX/wAjEXAqIkGPIaLTovhJWgVZxSiYR2NjgZ2pg7YtJbajFt5fJ8ue1Kp63m/hrgAQrPEYD7HHC/hRRQy7HyT3XnFg0l4RTvVzdwAQmxQFna6X7KoGIlUkC65i1neT+I1Xvsb+aUxJhPdTHtXWaVLUZ+SWvEG86iNJ9n5LQUYf1s1Zgs+5muKbothVy3eHWnvqgXh019TMNybnGuuyAHsnQHVUUs6gKZ9WWR+E3P7e7GThgQbxZktuIdJLy0kynr/lEOoHLO0W8Ph0FjsHtRvdiVSwsNH0nzng0BTr1ch1pCks1M7IxRhdB6u6Kxb2kq5zm4glA+3pwS11M80bCf1JFzcmx2kts58oYeCL8z1zqnPErXIWDz42O9rY0OmRe2DVGVFl1D0rs5HLlkJjOFSC6l9rfnW9Tc+Fj6wQizdBEOHYBmTrZpvnKIrhK3SDKRW9KpiYJa8AZpctp3YbdJ3rZs99+cKIN2rqjR8LVlCjdG5OfqnwlXYmxHnbjlZ4hA6Du9a4z5pRZEvRYcJSh7eRvL3kj8WRSgUEg8tBRdU7IXdhUY1SjSxYOmqYXmWhs8g+l7XHFtNS1rDehTmYtOOTfCOcn+SsYm13mNHdiZ1XWGGvF6i0n4HduAryTv//bsQ64YA6LzisY4g4HEuBRDSJ6MLm/Na4BjJFFpGpxM4/HX2vyju8lwCRMH0pF21fOZkBTZ6Ipzy3fgVJWv+msRq8M8LL1pZmeq+QtnP0qMtkbwp5ePB857gvH584tFDz+4uD0OFrp1nOoG9nEOucurl5ecxLvzhOOt7pABHHNe3DwvOCfsyHjOeQEkKd1rshtfHibiej9rUEcZfDEh2wdy8kBzkTu1kHYxtFYh1W4T5GC8+72KAircZdA06oIRZq/ILSuUKYQlgPdsfGlse9nlYzFS9DXOGw5w9Jqzy/KXN2vsy2T+9fzYI1AxFxQqu0dt/17Nxqb6lCNNYjz5wL+hgnWrai7uZWzN+42d3XCUuosogULggxtWNOG65c5Y1lnJY5tZ9l/v9lOAH5LHWzL17L3NpLOL/CiR9Y7VdbJMlpuxOKUllRXpSRRcaRE8izqSs9qxjKyRGfdKz6TEnFgkNYyfqf2BnA1KCLyenqRDwzHz3jTgiGWcIbFlhHL1/xXYzqQ8gma1SbcBJQdth1cKF2n9fWOIz5REw+ymsy8lj9McNPo6GLOUwZkqhwp7trJPL0hjX+ow6lu3WXn8jL5Q2qHESdGmTljLPyUb5gmByjuX5vA8dyKB2SjoKvVHGkDzITrF+fA6vig4KcCR0ccIYvJckfVxfCv7OUojffE2Vlqb+MFJ2woqokmxvEBbTLotVK04MDqgOg/iTCJk1bcVIxnMlAkKx2x68XMmA9beOeB2KF7c16ksc1gQuj6gpC8kc6tNKCjF0FIfD/hni4s8KngWBL2LFqtEeL3nNzzO/ykQLrJu0CN1/6wrX8j+qQkuhcQQqtDFOKwwmdYeVDq0ZSSRg1l08trlMol/rFJhSHkykNgtMjWysBfjrK43PMFPwqbFzW/AiS30o5RFqdOYZ9VCJ4pm6EauVonq20H0ckq5gFqBZdeiI/L8mFLjGQtXC9Us78vaqu1Kmb/j4RWRhF3KTIPZgm9PWO8AI5MlGJjQDKnX/HxPrENmgBL/J02uoIxWdJ548bXJVldnn64VHKf/MTNpBrgkKEB0X2F34e8ZkBMj7j+UqO/KtZ+yV7ea3kyPuBcOYLVwIKUxkrS1roHJQCLHafqQem+rYOaoZOTWl/thtO+eWTLLYUGYL4zHWwjeNrWVLj4unr+4SoYRbAgEAkWc418LHWkkewwTzSDnbvEKygucL0I4yqmtaM5OcslmRa+JEIhPWABIXRvwcvmQeGKgW5chAYsDcGKWmcctzUIkawjIAvogmmk5WdWZ0gygGm5OkYBO+HADORcJTZSDlH3CDdArjg1Eeh5U1rBBanK63PRcy7KST3zjEsgEVyUzB0doqDgGx2dNErJUInbL0VMu/+e6AT7Ui3lKjks1PouAoroZavgBOjJWHFamtGsMNrGJBgjDdDOwk7HJVmdd3QSnOqFcTxkKsCtoRRdDrAQTTVNJGnzaLSJSl168H7IA+FfCNepK09+w1lW+rzhgCjkvfLxn/QSNZmEQjsmqFVcVr2rWvVG3zxRCQ1nNTxMmBI7wcdhPkYQfehdLUClOpYjotXPfffVEKdEFOscLnJkM1qM6Q9qCuJycB1DlI5ZMKm51lnqA/sx3DpF0jQH/P8ZhYVlsnDQFBYOyt/ipOdI3aooA2DUnGLYAZ1PC9cgaF22+azPcisS6SZbIrG2yswyPizFR2G/Ji6hcAqTygtiz9a+LFAzRlJkNGMUxCXqi6HEpns/sEx+51dkEPkvmzxJQHi6TdsguGDfybBKiGvBKDRnVsYDJ07FS00mfuB7lTGBXHHIHKT24jQ7VdJVrwJKbCrgNvbMBPrVkK7+6URK5l3xIhppaYHYCoT16j/y3oM+O2mxoQzF5Jr8jZAUrX2ZAJxdrxIt8TIjx5gAFxDzjpCqH4o6pso8gr9Oor1adIri/22XCJqxLblBiYLCRWFsoTJRElZXl0z+1nOHCL2pMnVZDbC5xoyzOI232KnaBcJnyk4aS3vr+Ar6szX30ri28iRyk+8f2qSaOY77t9nqBerZLTMIzeA3hdDODgxriKypewVBKpXWFBdLEg73w0Pu7dg9Oy58dMo13jF38UF3NAZiv4Hg+vxZuolUpEdO0mWvcwfQw86HdBNxth1tR5bR1sEfNq1fYlG/9aieVlrSZOu6gkMcDcMkNoauqeR8jsZ8plDMKcy4QMTBT/t2F3P/3eMNE5eDZR2FAn0/+xA+9pspUWXU3DkdO2HBodUx18xdvki/CdJodzm1uF1bzR/Bb0Ye7Eqwg6ZeRntEHzhd7r0xy9xUX1vE6y3qXbcYfy/mDuAzvrd0mOHS318KQxoBQN5DqHaghWe9SYX7Y48EK2ZjaqpCUmp2GhsL7Ic3BzCzHTxRv+AxXxuJl6Av7k4SYrN/8n2RFZV0hTfEkXp3y4wiIJnhzOuDlv0Plp6lGk1VTvpRGaXPnTxrs4Khd4BV1Y72BLeAlYuURMzLtE7KzRyTjVkvEFwsHES+xsiCTDW3pSCx4iloWXUOgl2xOEx2gYa9Gr9PGagGnhshI8iaNBvFYYgK9cjalq0t36ldkshIOENMxS4Ntx3t9yOJAlGxhYzcYvt+cA68h7iUv+8cb9h5KVD6BXUjDwHkEEIqSthATppThw5pTpvEGqeEdT+dnlCEejmRMg97xkc+83I7YgebDZSqEFKYqxIJze5IreUobovYLZsPGB7gKn/sZ7r+Aqq0bvD78Z9I/R7oUWq2+jLm9CtUlmeUivI6MijotYO6NZvIquK+tqgtwa9TJJ/FanwEdoQySdk1vpJhqwVPJ+AqhSFmRsYoqT5mcda+4K8xgQd3SwrURRlAFSv2151X2ZylyKTBTQAwbBYrxa8qkpGBlWqkE2wyscNKCbdMkUNCW133xIkWzRmIix6adQRCmYCUU4eEWoO00tVwLwXGnygDyFgQooJUbl2AuTNZBtYvWoHku8XoyVAlJjDwZuTT6jgvTa5lyW761JrqGdbdkdt5MsGpQeJCRz1P61hAA0L6FEFeMTKpXCb8cUhLzpt6yFLyKjeZ6J9/pq/X6P4JuxNqoI6vN/6lcVRN1EavQY9WskllVC7BuNhjP6DcD3aguTpI4ihjha8z+QsZSwRtFw80Xg4P82fC4lm/X7gyLIOdzwuf5plr+KAt+K65ZsgEPDnrft/AO8GFcWmlLg3BXV2qqfsAQ3GHxpe2v56fBAqojOiorW92K9C1568klubN/ghVVVibPoRCmTEnmggyPpUYKalWE0vy3GyQoLCV7VS8F/iKofwuqXr//yN5Jr8a1GpbyNf+AR3jmjl4OndO1SveR2S+QM12okzfzioPjgPdatzDYaGRxa+YuC/EDUvp2e8431VCsiz/lQhYx+ks3/xBY5iHv3sV5Az+F2hi8UZz44XrROMnIx/oObb4D8yvUW4duQfjUQNYheoTQYFSX/ShLrSMIfEZcmXNdyjf9FpC2u3OAFQwypF7mZvWnUNPeTfzNZIfoda9zFiDizXweHtzTyXBXxaMNE1LOybE1a+DOsQuqwwvjdttzLd98WXk3Qp10MofX8vbfHA2IJk3Uy/FLiDdUxMe/ZrzIaJd9wKUuSFihC7HyeO0sX8LnwaXeTFF9oiixNwwfXxLNbhI0uWBAwMpdSokBCzd/nzL0i9NAgWaDKklOh64QNJLhPOWsBBgUjl0nEXte0mkEVZ8Zk9qiO09Wsd6U15U3qroIZ2LzkBIKBIbyuufC8gbHUftWHIPRhviGtgJYXf4ByGH0k4mqwXHEq6HTcUcJcjNrDuQmRkBxRqODYyXTL6eqkPeLUpjs9898UQ57nSLzqn6TOV2jRjyRF5pWMTy8Qk41xuV8srMWlDOwq4SF3QS6g7gbxIGeOb7/G7uSnuOPsyoV1JkfLyKsX7fBZz+4MqCgKi0bSA82EBfiRcUiAYvsOrdDORn9yXC3xNcIuDikE/1ZoFQURnMQxwUftI+kml+rlPw9WrJQYMKUF39Zr+XfG5ivENC5/2Qnfz1ilZGdH6OAkV7GLhsXghF8w3Y0z9C/jrYwX0HOO+/7+2MRuFZdin0vUAidnJ3dFmUxpMkVpOP4HtHdp8d9SwPGi1X4QniNjZUTnxgbCVqKnHxoVcM6AL4AoBthmCyxPsbGRxNjI6qxCs+LB0mq3f41IzS6seP7mNpNkssrnQzTl0d6KvW9v+/NXpgqKxgsEa29HmsIuCl5zbWxpWbb2a+WXk1wKGWaSFTrYhXwwH2G5MIv1hRRxUS1Ruy/zs7zfFcGCmi4Cy++8djOgqH/+w1l/UoEUgpD1fxZ0ExxU7bW/JI33An2PhRXdn7uPAsNiQYmAOfuiFDbHvVxplymMa7N8GYoXe4GlK82ex1eBgbtTEHSdroRLBZuHVep5WB5Wxjhhs98f8FevrDr49x+wu8ziRo5OA506wj++KHskbqzOt7hlD7YGjc7CRfc2T0vF7wqgVe8JijFIOtDx1w7kXXxIwmFs4qPe3E7Gq3aSEaU6i+YHGr4vhDQrZstYN2buIVb/3tXtrMduBjm3/3oWUAY/pqO28swS5uwzUDzgm9Bz3/6tmM2CmsaWoHQE6a0gZKY1pWydaVj7DvCYMA9phDMZuLU63mW6sFDrlEV5GlZLSGfbLoWC6naja/eW7glY/diwRxUkTGQfdrYkgb9b9MtJUl1tt1eJXRlVG8vFZRsWKbpzhVRSlrVcZ16Av+RiN8LzhCumo12s/xE9wxRX3CnwU3pXSsVfq6l20SYtToEOOPssfTomPVP0UjmB1Ert9nu4JXA+7MJwk4jVy8f7lpyGRZn1b60nsHxxRWtdEZ4LLFU+2euioCF/Awb+Ec83oev1r594Dbh4wuTPivaSky22A4UbI1NWhMZR2WWBVuqn5sohVthVAu9efBR18BNOTEQ/agbmFNLu+0aD4vK9Cfloco3pg+AWcQZVi9t8N8BgsaM+oxf49oFxMpfv8wjp7EmV3dPUYzyxEy8AqCT+Vc0uCleh0J5aVjWaQBKFCp2AUlouQpzCtQWANC6iVW0hCQgjuGwZF3wrevgsuGaJvcmZt8Dt53S6Gl9e/mEyPEVYdo6Xcmic3eLx5ZWzUd+Sm1k+wlx7avy5cLOHE9bawHqgpelmlCcWUnUk/lJejXMyd3/80cK6wu6mc+isJQ3SUDhwAdHCN7NrnYcSuQP/TmkRHL5DmbMzDPnYfuh4nrvvnCLMndo7yWmkxYnfsiOx8brXZ9A4ATQ6CE2LXzIOuuED4DOetiT2nvnqPWXybXyETewo7tCaxLE9JUv05GbuL+d5g64fcNx+zxkoqWXp3tASu6ZKc8NCcUE5ATf2dH9bpq8uqS9S6ZEoRLVAlxwQA070A416HBb75Tj5UdUfOWRygMLl53tIQqgZtFL3eYx+Zyoz9xX0jWtYT9R/xqpdC+Ktt3WUNYNMcCBYwAITu3z3s1mWrHo7DYmEfWaRZsX8Uvg1HhVOqc5wePmS5F2USnMqDkUmy02KAk2SZBKrSrHBj+pRVePuAJEDFsgQcomYFSlc6tzfXIuYWhxUF24PsAnLo8TpebVABbtJQgosyhHK2mqFv3jNjeDD+xM7loxHr/hXZpjbZFztHsQu0DRcoSm8ckYkgrHAd5DjO7ZbzANAXNolciDlxHyM6gnR2rKvXj0HMWRfjpErg5eNQkFHM1VDmIjpZlNEZ2RTx5v0J10L9jKU3HssGDh0BofbpCdJMjK0YVvXiS5MYsbi/09MFSV7GpXj/xGEpdGvxoOh/yt5voD0TDqy7EJ+E5zKqbhe+G9iJ7ab3I/LQcVn83J8Sy8bVMtWabCXGNwrPb8E7dExuFFt44veXyELBscFNyIeq8LlCG04XOHGaFzvlOIHAl5YLvWicaC5BHCLjj6rEVz+hU+tGqgC4PlWUL4hYn5vtRdPIUPsfcRJXGehDWfa04i2dKcs3pQN86DfOpTd5Me+Eg3G8Gshl32Hy9KRDI3IcPMUTX0U01di/YSTgu0h0pUGGm77RjxwHK0ypYl4v7f2APlTP3J1lulmygNC/8+hGsVRJyqyprhOlgOueSOI5K3ykb8xBYp6UXNPX2lYJU3sHDlDOqDKg74VJV6y3PAtWyBiEaGeJenH6DTRjIyR50d7KNgDaz1MJa6PRyIxA8KwNr8sRcHQzW4OgZau4SAX3LVqIwyP2YKWrGN46mPsT/c7UQOP7vgh9Xwalfo4gHuoX/ZeyfkKWBxxbo3HdxiT8y6ZKAhtgSK5g2Ws2n9fKIDktMekEuCsWPfiRwbY27sdTA0lvHcvI7fx+P79rLje8JNGxHXl57jgbbAZ7V5bZvltOgtkskgUj7nHs5uTR0MS2W2r1060kBM5bZpf3yvXVbT/mlDfrnGf0M+n2ipeEwhbzwbxGe0Y79S+mj0acQufbjWLFeH+2LqUMlEWn97nN3Wyf8mAiOv+nUEMQzH7DAp3g8pCAqXYbGdeYnN+4jCQgpBKx0aLlkqDl/oWLvKVExjiPs7DmY7cn0RrPTHudC/nlHUbJOiIhckKM5peAvhZc6k5SmVoNAGpFhGGEfEeX60BAau+8AJyUQpotsHJj+naowK+YnDu8hpWM8bB1IgsnrhgpW9gy8RVwJTCYC4Fy7q6G9TyFFQDnoWe5YRdBOw00DPRoa8++YLmz1kIkXnk7hF/BqeQwkVhClGCM3v3keZLCL0wwTULVSsIPBxVvExJ4a6PiBJDMvLseJNk6uqWa7pVvz2sT+JiteNgGN2AiIg3gFK3s1ErZO/eUBpNL58p0eEByPItEruoPyHGOnPGrgMQOK9vUaZ9wic1cooddiHGC02KDc8vEqFOMRWW6gCCiZIyBOnSfawSIe6sbq4/Q+qdOEFzelJmPWzM+ury0EEWVdDGWEGOjHGt0h9GRtCSGR38JMvW/RZF5rwPU12sDR4Kt2UvD45dLOqSdzMXdJv0V+DOzWvqNKEHJ9p9E1KQQf7q0q9TI47Mw1+s6O5CIOXFZ105i+TdjPE0CEnxQrw/O3U8y+2TCPKeOuhyMex0DI/xrEGg8dLIIkC5+ixLApSNpwkBMYl+TTf5EtfUEeFyDuAujrpx/MHajlt+G1ipD+yRLzjbctUIydjITSUC9iskU7M5vz7TiF53J+XorjgWNXDWDJPnx5ST6NpmtaHOkNc7rWcEdbjmEzVI4E/HbjCZ8Uxh6eP5xdwX2hv/1yY9SZ3X5Gwk1z+0/6vlNZbz06HM2uka7A5XSP1bODWabqXn4hdDKL7Yk6Hpe47NjlrD9do5w4rPYo+DpbZSDQ5QVGWEi0epVaWLQ50LrZeQRLDo+YdRu/UcGSw+FF9JXsWy40v0bhXgcYWcJw4SWiQRGj+dT6ln7RmUQIjc9711Am9PAkpFkS9n90Mo7lFkBvJR0pEp3qGB7/ShVIJfb230R16TXh/7dit29dzWt5OmrOFjatpIWIrRvx2qldGjv1A8Y5Y3ZH5gXjOpEYpfOR2IJ0VXBpV8tvG1c4YQ9g7CuHbqveuoGzKfBV+SFG7HCnVkZoH8wOy/1NWjZVeET+dyfnsjO2DWUifVG6kRuorioULjs6+8Pz0WirVnzkiLTv2/oNXNS4w+vMTHlezT3IV74zgthv3grrzk9EUkmIhwZXUNGCz4Fxsdrg9SzoqlLhNUIM0aZ40F2x7PzOSLxbSAFsneYXHCA5YMBChxLBP1xaRda9lBYJDdiRlkv0R2wfgK/nAKOGXhmxrz+HwEuY8GUMyY2xoAtKoep2Q6eozECDtT8FHK7SYG6nyUtdZdS8qGHupiEfEulyvEoOUwakrp/khPX0TIqizueMdltQwSQAkCvBahnabcb8GN3Gb6fetzJfpQQXZwOpzr20ckM8M1p+ktU4AyONFw/QjzILtp42o2ZidnouCAkOu0+lbO+0Pv8tLwG2mR6XsoyFhjfT/eWfvG5xftj430+jDX364vxPfWfCgiBgHooKvPbP1L/BwdsZbx8PxjTflzPA7qESdXw8TxnwqEBQyl8XhIxLj55JbSZ4OJWx2XEE5dpxOYRrz1AP0kgIaGU0ReKK5245pO9Ha9WNLc/p4sf2XDVG+SbHL4s/clPIx5iy8QKh2/PjKvbnmekpeEwitoJgAl381yk3BMOQaNr9p0kvJ/GfuQx7gupvbjkCIaHu5x1seU5+BieIQYj6hO8lMviKPvOjkNHp+gbIWrJVGbmcfExVhbUqJkm33mCFU81VSYElQu9yYR/4lcRYG4f3jqEh2NIXIPhIGGLbOhITUR8aazLYX1qBKGbigVbcFBVQYcJ+bGnHblyO3KU/AuBmj5IYXOyH1ifUS8rv5vGWcW9dfWj2HX/PQexQy5urlkBvrmu5rYkduegTUhH5OWslEkfATKM9nWOHm+WYrnrbPWAKovdi3y11UYKaZ8/pTJD9+U83mhAawo2oC4FCupdWvc0de2Ip3Y7bRVjdIup50vhPY4L8x37R4taU1MljUgJt1PKsfV200XQKsJCgQKld/5Fu6S5hh64ftGQWp90bHep+AJngZnnYizAxcHKRUpaAZGbBAi5GQ66Z9uJa3FS2pBpAIGGOg8NS+VabmtW5aVhTGSa7pFDxP83JZWURPi5aqxcOJ7SzYqiG83/GcuGjmZdk0aeURwjVNZSR3NYSFhNbcIvhNoC7Bz3HYS1WTe2g1yiuaZ8iN4NmW70Q9rN/q/RBXRoUZij0Ut9x4tgHhvMDMnJTMMk8jS2wfr3CGmjX+YgmcEpgnQ3WxL3eTWk3qMwoi4Td8ODBUPosZZ0DII03LIHFbvWhVQCA2P9sA9H+0EOAe5CA/4vfIYpwC9A0BQ8SjDzV0hGbgkDV5jLB94QYHUZohKoUDvfgMtCKJtFjXlOrbrDIB8pPPCXSUvbECFALKz8WogLERD5I/yKRQQaebXCaf9uWTn9H6M60n6fDA3Gad9Rc/zupSlvxJYOL4MYqePnlVybL3kXS7xihYCaFTvrbXTosW7T3CfzoJgwEtmeDkkui4LOlx170kluPB64K05r5RUnwSLJ3APliP+swYyp0F1ld6yxVbImowblpWmY2L7iX0CfuJd3ohAzAdnSplk4uAKMWdUGmZPNyL+M4ksaAyUe3egDEFNuj1XdxgfCKtOMtbCkYA/shSh/kajkEvVh1qPoDr6iU4Xp9PCN9qLK8wZrj4P5SDrCi3HwWRjuOLFC1OCexPwr7bzFwncUSQ1OePQobf+gzCwRLEh3TsDPiM3Uv2kvLjuW8uTOsJd86DXgkTvd9KMlws+mOJqfqMnvKvjjnK5PeA/6hXlq7uozLX0CCTRCBSP8be96qgs+qiloGXwaheBn47srbRmoix+s3ekkuql0u8LcL4K457w6WIuKtkzyFW3a0wge74G/mO9M6mT+YH+gHrvL1ZFhP3vgYhK+NjHBbsqp20EaXMZ23DH+8SBh51z/obDxfGTJR3j8Xp/PPGmaQkWYj0H4PUxciV2m7hkUnxdX221fDUHRlgg+Y4JTI6owU4+9jZugfZUxtf0fL86cmayr9Hx8hoOcMBjnyFVJmTmhUlQFMQWgLUOQuZEAb3y1ANHsXavDmErQ330anI3y+Vh392Cu7f2qDsw56srzTVh105MrGGtYaqqAfsEKdciqovePTnJNmv1UslOD65TSGa2aiKeCQKunnsSnh34/LPKkUWORKU1fteNfBEtSkA6LLijyVQ5yfiMnjk1ELdzMAQZtcOJp2d/LVmzzsZtnjO0UeigSeBxDRXvgBzG/zS6hH1ZtvGOLavCL8h2LT7tiqmPCQpL6EeIdVAsQ0xTUAkMPeKO6/0D0arGceWgzMlnp3XjGMgkCAF+1x0QeV3Ub92dVeZO632nTj8JzwJR6AIwLPtiNyG8tY4hUqw15J17MeKdWqYMN1o4t+Yu+1RRfZ+NNlscSRcKk26+1bUa/SAvEOQXF3YPEy+6EQKanlGTQwefdazBhOxRxFkTU1/LgXRSIe50l0IvlObLkqJGYWVqHtOpvV4qB3NCT5B2Yuh0bTXYXqRJViZX5kHjxgh0JHy6BYX6FlT9RDHhGgTq8HQzx8wkPpjaBJPGwKTrQxs6MrTOXjTxCDVWWUH26CXffaMBWrgaFkiTZAyzUMUYdY9QDyABGv2b3gUCO5k93qOOaP7Gl9VVYvtHtipgFWQ65fu0B/fBsScvTtCfzpRvwcUTyoMKk6+RY3WXlGBTO04bsF6jKOsWmgam5RyOQEv3j+MisZA1PsTHnUPGZuSImMtunkC2ZUNQotST3khxyRgnJZ6ZhIZOTneckm4PLXvz0MypGtWljFj5rmF++uENhIetWIsnZdVymCgCyjfHiAKl+a7pYqooEDGXU7+mkOkmE8MKmnMbQ8CMSuBcGBBbSYNwYlJs0nXp4WWIJYH6MDhEaEk7rImDcO5BadVew66AuBpfXHEdEkD7fGlMNywneQnkVwpnsHgvh2/GDvWSTabf8gQFs6ToLmEHXUTyFx2N4V5FU4KdwUPiqYiNalpKqAQ68XFUvDkIko+xKeLgmbdXXTdaaZ2N3ZPs+aeego3tp7fn+napYt6bilZg2PVqTUYQ5hpuP0DRm0zq3j6GUTa2d3ZMtMMzkiJgxmzbqhKbA3ut5vn+43LUlCJ7DDvJljT8WLjfZg7mHzuzbKzkPokzia3+9dk86NlVdVYkmgHZonGaeobGULz71rfJ2kAYQ9kol3PyecMmDI2tbcV42v/zoNNoB0qA12wNfXSn4atWEYHJtQx7rnfNjB2dlfbkB3KRWihYG8mlMjufx3q87Ho0tFMBBSmT/lDDwYIKAR0KI8nLUaTb9BB2RMdSWJ+vJPOj0z3mU4UK3fto5vNmsYi0Y5FCs9gSHIbPfKeiR7NpcFKGGMb46d16T1ZfOkhkOjecHFHRSmIztURAK7pZl/QyQkgkS4G8vzNg651C8Z6aU4iXzc2l2Exr/pz1vfbjqp7T3XVvIdZFfdHvUtmhKE7ZRZK6ArMW3/KszPE8M4zuL8AdFAozNmzsfWL1wl/1bSHICfRRiA3CVzJlpkQEI7FXZ6WO6oM22o75PahcgUlmmL/Emk34+vNc9KteKjBB1CMMhpZ5LDA61KQMiJhyPo/W6luFadoVUmrdnpnaKe4IauOnh3srNhh2hLpkokTYFQgFc+GyxrW3nCEuAJ8strDYGakj0+XO6wbsBG1rYtRIbdsgTeUiLcmL0ZxCpZ2P1vYzZetewu8xwWWqSbTRtthEX7pzzkLLR00uB+jHHg28aLUvnH4X6aTF5AOXW/wFOtoibJdtTX3+KOE7mT/YRR1ZO8Q3jeRlYPB3jXyEGJppdj9VRSp4CWw+2++rJUWC+4oeSos86fuDHyAOoKw0bT/vI8KW4bGU5SrI+BVN17rVVeqkP1DYfqgvX48ZElOtIDLnuGcduuIoLeCAS6j2ug1AUVyto9Wyd0FW3n7WAHq9GYmrx8Sg/nsYCheAKopo0m3Cj1PvaikWi8ZPUdxPZXwzuvEoee4F1gitBI9a0QlZ/cxL7FriyL6HwDExR3uFEC1w/cUXhIEFw0Cq92mSNRsFQF6Lwyo303z95390LDcY+WiyyuQ52MBmEKZC2S0/xvI9HEQ4MIX+zZg4KUbSAsR08lwGaWvIdgTWCwVIp2YFmtihBPvW80sZpx3oELWISxNtNDNGW6KvZGXb9ZkEqXkonvePokgAbek6KVqOJm9+hNSzhekQF8LkYLPHeoSXhQAvB5HvCSqV6JIcM82Fhk3Ogj8ieTJrB01wvf110pxfRPWMniRF5OEQniqxquFZ3zpTJ2DV52txCGS9+bYoRRLE/iXzmCY+jqnQNATuq0u8hiIt+nb/3TUWE7IKRXPH42CUXL9F5Y3UPRB7334br9Lq1yoV6TR/UYTBGTeT5TKkSnEGfyYUeffKvjUwnFdGRLZh8zK0Ly+tmu18g8TqUoaPfuo5BJpsaspn5EouB54oeZ9LtZbFHTIBBy3aTuw8KlSYh9RqwfmLhqGM/a0+GfQ3AUuowUWla8bhcysRaW2u7b2QOJBoQ1iKX6QpucGJYcsJahbYcHcDrJUkTIEgxJZTx2uBlgtjbSaFx8AvApx6KcMklVkdWoPm4zZhDUIgsu89PSczmFqZDOU/PqxTO75HihLZ77fIrXCN8fjKSvzGVTQnCojnrChKS3EuK2RNFwB9VNzhVY3ucXSCoRiEx/naX7PVI4r8q+pArwgxRsR27VlD+tFjAYp0Ri3YcwIdLHDdtwLrjv0suQf/oU/XJUuZz1SMjUyRqFo4npvWFxjX1O6tK0E0JhvzhmAp8YqFKQnLwcTPaw5qmky15rOtdZFFQMwLNZedPZrHZgYN0+SzsTnvT2ZaJLrO+zXheNohMTq+Us97TWFE7xndAXuWB6dd8kH2by3N5gK4X7GuRXcRHQxRusyvAn+fuS3i6Upz0r/TNAmJl6AubDHnHPUdZsSVAzuI1CAxLG4jgyA9mEJCdWnTNUf5KdlOJC7nxOMCxrnIZqh1CNKEE2hsfAVk9LWsDFKp1DEK0bxgjY2ni3lMDicJg3qdSuTG+h8vf3G22KL/EdV6/QhYK0ekWDRDLdI0vXTY/ed1vV7M2EyGBkskZIRGiBcm6jMrU0Fp9Mhhul332qSIM4SEHn8wSjeUAcg1VYZhmSQMLM/wKUa2CZSniAm5mh8IjPRAaSBNS6uU+9v+a6BLHqkoeJPUTUqHmTtjcQd7niZU0vGOvaFdoDbdZXeIWNA0KzyCjcGebVdLPPZywrSsh831u0GpapzHrDqJaKFulAc1cE2rTB+Kr5OxLiAdc9cuD1hPTAxFaWF0hGf56yA5HzxUrnxj4u8WcxJKaJPLP1dY94bAtnwoiWPLQH1DSPFVIfR6mSJcFc07zrevkensDXaTgbaUZ/4LiXdp0xaaBlaToOyB34rDtu14K5l6FndadhUJQVR1y7E7Za3rIFfPOHdBpivM17udRdT2BPB427EtoEOlK7EoHtNoqCN0SaX3xf+z2SoEGWo6RUIaOM/GcZLy0+7kzGgE6OlYcplXd0IkpazVuLWu8z6ouF45tiVwrz85+Vwxi80hMCGAZgdEQ90HZLAzuIwHyjv+N7MkqLcpBEiUnEo03kD8dsP08AtUq4x9xUs7A8YwzBaTkcqeazUAmoACuVf03PfyyFIzDP9nNACn6zcG4aEtSYREqVBGbriFRS5dpa+nOQnKwdmeXr4XQnQy1dPUj5NIJTpl+MTZpM9SwJ8LTVTwCdLUk6+tVCcHYkRu9XBE9QAt4F/vTM2i9KTjjDda784yJ2Y3pEHvKdbb/hnazqqjq5/xTQIyBiV1MiryqFCnZsTTHRH5Umu65zBcYYbzPQcopu/cRpoOQQzcLU8Z2La876qs97IM9tXOOJkLU11ZTtcn6/RL6KwYUCFAWjhjlYhBTdrYbeJ6ALO1cURqL57occSJW8vQpNZEMCPBF0c3WgjqypkEsY3MjIuQL3gJy4AU06AtetAYcZ8cQLuIYazWPUNrrGgWsRiL2i4RV8aTOkFhA4R/CybTlR5YfeW6BWyIIderoYLOtZZFTIgsN0zj8RVvYDpP0f9LW40KdASW+XtJshiGyRA+D3+zOjA0io/JhsPTmM3jI/il8hk3ku/qcQZgdL44BZqmuXt6/PxA/B2tIkirdUBJkmnNMk5qr8SskRK0f60s/p/XzPun/2KFtVJsvfLvsZzeQLbiUIlD7b0sPLo/DpG0OmvfzIoFIXwwLaLqNve1FwU8+uslXfpjO1GzfIhjzEhW95ypWEmmcFJpsH6utdk7y3hOzibrTXWi5Fa+E62Ugl7h03MTapoPCCwLpvgnaIQfXuelu9IUGPtP3cMjLSDXx8j63tnTJsFJQboEQNffbVEJlnh1XCUMECqey2wx2uIA+UJME9laedQbGxOHZRAa6+XRn7MkIw4l3FL+Q5ioyAdWUxm7n8dEv8PIqg5A1SClrlL4v7cZt2z4y8i5/V2PsLtzo9WjrmmNcfAgAMG158JrO6OZadjNWXTZVGI5dy19HZadr3ta7xOfgnmAHIOC4RnwlnNPvajbS7SvkupoZf0MZ8gejtAx/ReZJGXbaixJIxtG+FQPkC/88ket99YVP5NRSzQy6RSN+z8E3rUk/asP+8PX3+xqtXerHRlkgmzUNUAdBjOgbq9m2vR58waSUgiyn8XokcP/zkncFTjnnD1dfy40ZSZEj3RY9lCQ2alKOdsKILZtHa08TALL8uLhuIMQjjDMV2HuNkuQfdKDf9ibkn5etO6n1OxtBqTQVSznwoksfrre+touhQec+0BESOcN35K0uYGKZKGd8tMfKJArw9M4TUGmXn8pccnqwInAC2eK9GL6VcGGCohe8ekSujHOKTxMPR9I+PaMyw0mYSa4ngcEKftn4g39dgNW4erzLJ/qmCFy1S5dmEwodTLXi6UursZ10x1oc50PWZ0PsMAuzuXcOrOY8BDgMJBK6wsrCq1knv1cg4dA+vCmO9jY6c6/9BJ7Sz2J3WcsjTEM3xVK0C/9gMic2dww709y13mAcb4dyxpGi03SrLltVEiFI95g22Y3MzsWqOrkEyqLiHbDDF58+lv/ziKIDQGkkMt3GjV/qOj6Ks8WNPxqG9jkvWRWxea78Mla19mCI4sAR/KwKx6drMX/WQFvjK/RYM6hKDv/71P6FzqBVx060E2/6YZiXdSdVRlM0bR7eIa0fZONuLoIWGLmPtQ7FoQnRxaC//1B3DnhLnJ28AiZprnmItT8RqD+9rdCT5aUwQKJab8h2eTe+15uconE8IwALp0VUVSveeEcmgr0Rpe4b9H8PfE3yQPUCjjlkTjD3vgRnqO4bwcPzFDkQ4kFddTo3vZBTlySWk2YgrZfAreZyBmZ1jgnQeFIV+ZYFY6co9MOVmJgm5PoKDv/9NSHGFmoOERQjYsH5vvVZziHXvWdOIslOJ2YoHFKSYJtH5lVAGIrVp0LrSTMBGt0ZZ4khKa5dxijXpkJ/RLKDN8I98TFmchfYkSm6ss1di2nCHPdiOkaSaIEwhQ09iH8Ypu2XqmVq27jw7VHYnKOfY0L95nCqJtDEYYF05HAh4vekszSmzeWRjOSj6oLQ8LyMDIsQwrOw5TGMuBFEzXG4OT+FuLd/eqTE6o4DZYQWekjPAvXV05NNOk3fPOEODKWkN3jhj+7Yb46uSgsD3p6h9E/GuwyISxdeujFeNL9ywp/UF5OVbRInagSgVM+Uux6FI7FcvBcX1kZWOKMsGadQhbYZjgK1gYWGB+04X3ewa1t3+5A0aQW14nZEzcow95YiWlNJlAG1Gw26hngXKVX4jqOaRrtHyhn1mG4u95VDEoLux06C2pnVu+uIjGcRLI1FaBUjHxx99VkJm3X7uf0sDfQ+C7zWu7+bRuhl8ID3lrbud+G6sjSoXuS2xpTTAmHFG7zLEEjswON+grB3Y18dWijXE4UUwKtKv0kMzguF9aehNTV+sQqeHCLSw0c8NWXXZttURaAlunZbWRfYshQmLPZTCeiQ4TkjrfkZrxQx089Z342UXvKnLhwF+3KgOhw7r1fvOo2zvbhkvn8vGBV80V5vXcwVH0jh1JD+l6SuvTyAwK0HDQRYgO/IBDJQjY2ADEmpQ7InNdaFFXcx4gojVnxT7wM0crt3K+UMleBPH0yMoMzq1AJ9gQfxe4zj5jUWbh+aHxZmnrgo+HIOyJ/Afb+UTgla5hQDpr23GhV2wE3KS+sGk/6HnIb0xPr7oHRP25tZUWLz/zeuN1KO0KEbyIALguZ6j4467C+KFUdRLx+jQaDDfFPhxpKuuPNa70OoQea4u1ZbQKOhjP1LsSPZyIMEtdoqmGiipJLOIdGdQ5UohhiLmiHl5IkQCavZF/VOtyhAo+4TpsD2+ZWMqFBmiEl9oyrx9kCg8GTJxnJqlvMzJp5tdQkLDt+hfVygEhreFBmmR8nK83RBQvMtSwj2MDGg51TmUJ96NfTuewGDhdMzFgUP9IAH0IR5co+zur7D55+TXOtto+JVrNL/Wb51fliDja+47cLrI6Zm3zsigTWVw/4hcEqxsGmfWivx9qfnjIDiDsAZ03Batv6snW4vH8n3saGsWigkxiVaGAficoh3xqfn7bwiXrP5goYuZmdCCqP4MdX2slpVl/AbFmUu1GcbowRJ+wfDWWI2lOqKULTot0VmSKVeYGtmWDQG7S67dzUD8vQyQ+5AwXFAAs1WCJ+Yw8O1vIccUnt7sPRxvLA2bws7pQtP3Gr0jSkLFgzwDKu0DyPLsA5V7Sfr56+GcFN6mY394oyekNCsGky8uB5vk+ie7AUCGXL/3qwgCCvNPg45rI5zdqlA41MONtlaghPtM7yjvBm+4FPPAJ1CVki5cZCT15p8LGXJRuFtrPoHKlb6reLzNLw0mliwUdXnrlMf7Uht95ff19FBE2lQjZ6fz7qZslelqE31FXI6BtCXv+nxYWe5uTR4mKR5h1thxzhGKm5/dFtY6qusd1jQM9gEkZZ/geGLSrzSdSr5pdInX8ohdItpOeIBZTlaF7ULgXs/vemaj9X8ssCSOUEHNa8JWf3GDVwDMRdxvlWJFN/3GOfqnlC1jegayg0DDefm2jrVv1EW0pZXkep/jIU3d09blo8p9GNivUVHeez1Wj72dUn68sBMhnAoSA4f2xzK8qUbQeM+YK9Mdcx9Nin6XMmel13F8aGk3w3ML4rEH8HOQYYFI6hPSeHgUOOJmg7EbZaRwEJf5H3JPL0WgAtoemP7TRSSARpt8tqYC4QdDxOmwrXNyU4b4zq8V6p2UZrnxheGjuVkxcSivpaRbY3aAeO5oBX7BtmUq57663IuguiXymsILmKpS5TXzJdYtLXTmUtZT21AGjOXEqGkxy/ozeU0tDUvEZZwOTAsM2OK+PnXqTvCNOELPbfsHTGJy6ckrkxriY1c8VwSk1hMsTu2meGHaEJbDEnBZPm0Di3TpY7p7B0lTnSOE8oKi23pZQljNxVT1PvjT67iMMZMVB2jc7JMihF2O69617jDV2BNxCf9pmGQPJM7fFxjxQQSpu7SUqvuplLeQmSPcMJRUCJa3oiIrMLdQaDOTlcFx4Vicx0lsCNJ822SbOvOmM0QFZy3neTmqpqT+xhvT9aKgVaDIS6L1ir7boJkLPpvu+qTV0cviUaJ+u2wwelECQt36+kP48kLPtdpeXHoLzytFhaqUmbJDwXc/F55ANuRIxKvMK6/gifefH/5YH776nhIC7hNiy2/SZJbaZtL+OuoB3JE1UfeVe4OWwpC9U1as62lneG636uTLb7Lw47FfnEOL38tH121inVEBEsjMnmuDoAjDPvaVENH1/Dh7+Xzyos5yeh9YgV8u4og96yq62LDW2nZcbKOGUoW4f9bQ+eAU2bKIUfa5vBeCs80m6gU18yq9zWR1Kr3tbRz876JikbM+7JgG5PbMRmt8cGr2MBZCoI8PK6ZV4K+ADbgYeb1Og+1pUG2fZzmNv19s5dLPVI1R9dAD90DEjZ3VuKNFT/XrrdpZLrCoQg+AgkyAYBStuaMWrzQSTk+HP+2Ua84LlkPBeiFb5bUssHilG5hk+0HZ+fiH9iK8CSGUFSupU4tZ7eE81I5pP9D/7a9tNtPzm1XsMmyE5w/CQ5B2rnDEktZ8bPxjnGCFYPggrpGH6Tsv6pY3YpPLujCmLwZPd5U1Jy5HGfGU/6+K/HbHk8CZ/br/EE/GkWlEZLYmnMso3H/SV/km7OkE4zL0jGoM1l8bj9aGWddBlMGFJszYCqBwz7ocqELg4mdUYNWH0k1vEQH0stiYay9pXiEIlScV59BgxB1F1zB4/EmZXj5PIwt4PHRVwhyLxBcWZv1eVortLkIaTabAlQGN7pFr/P52W+gJ1EzBCPY3P6OPMdcsyWyIs+00FQuUd6XlAX1/Tzg8xROVuqm7wjVcYbBUvvIffCQcoA8gHIxE5zn0DHXHkuEPSI6WxD6chtJN4tM6d+QpzPP8a6935GVo/IGpC+wOPh20ZkqSoSWhz6vC19s/LDKw2OXOdhZfK0Db17Rv5E/oTvoka4rjA6d9ecBq3jemFXOMNery3Bdcah0TIDz+F37BLP6fW9Ptfvccatv+J8GuEdIzN/KbE+SERdcPeBg3KeDkBzqIbY4LV3kuUqDxLyAFEmyKQzwUXzVNuHG2DHQZrU1i9SDlP+4dFtM/yD0czDD4y1u7l7TuEzgXz1BG+JpyHyEGbYStVHySw5DSDtbcvfGSoL9mKJMN9yhv7W3eh3N32IBmfWL6bjRD08V92w/J3OgyCpv1hzcJliUC5XVUoFpkbhmyECflkQ/28MRUqUFsjLe4elO/aXszhIxLqDcQD73yt3G0b6C7JuItLp0Sdg5b9YG+YoA4H/yYimr7WbTi1wrkFWFCkYz09qxMKTQVZHoDgTxNOl3nMpb0frem4c5GflwtpKos6JTEHqVm3lzrzVLXw/ULbdo836c88G7R4iznGJsN9rcla/GzNBoHucvUx43efsXgued7ilKYPtai03Dlt6B8ZsLh/z2BYo4PDBSF19B30MkfNXhibqD54yeNlYykwLTJTF6ozOtibVOm3p8PwFBfZ+X5YRY+eUqaz3KqYgp1wCxJYpc99hHqZ9FjfowdRCWE4JLDHVXFvIKLVjSglLzYrdFydH/DAPE1kWYu6CBKoJKuR+sGT+wTJasFzTOOe+8LaInSlkmJ/SWXNK/VEyTXHZiiTUZmn2uwWOFywVyJaGh8jcdwAXbXKcpzyr7yoQW/LOnvHv/QSWEOxH8F8YVsPfHysLEWv2WQMxq5zvbh8fngPBpYD6gGoA2tZBBZ402HszQmi6f0UNS0eD4JyLtQjxc8IXBpCbo9B3asrLLOKV0d0poRqCzt55ZAOOj2R3dJaypzwMq/IhRvmTsM18wp9S8HCa7xIw7PU+q7F/0GmvZijBmJKce/Usu10HWytG1pgvSR+t1bBMVxpVvJXV2VySwD3P5I6VHbJtkrtIXTRJFz7qcLzNGM3tqIl7x33X+MD5besgkeckJcaOelYAEdTn1BpxPH/QaUaeomAsJJbXyd4HZXckFgSzy1/fobIM7Yvr+LDOUkrLRIaev+/kQ/0xRz66v14HIIT5wXqwUu2k2NYcfJd6wESotjHeUWYqHyldmE9JxjT+ulAEjzIUUlfozjUIAjz2I3Aeq6qmqoeorL5yG4MmvAEQIX5bW8kP9TbFig/J2ZiYa3F9cuoYprpeZYyhYC5ZqtJOFZec4i+imboyx0n3XNzPn5d8a+ZpeoeJcFGlntnlm066y8HY5W9Z1byXs5JmacpCumCM+ktbOMvAwvl0PEVgLGwi/OlbHd3HbsHnUwhM6uM5eh7VrslwWG0perqzKy0b5C3n1p1cRnDGe1RSzWjiLFoTgn5EaPmjGQr1FgOqZKfV9PYaauJpJC15s0LD83aHMMZwhbYgVbMADJHE6PoOddjgtU+272BNVxgOvadJnAXArPmHwvUxwpTpxv1jJr32sKFDXdpK5iCYwb9uvs7nfWH+ImTh+6Y74jaP/w7gV5j0sxDIJ68OPS2vMT67JCXYc1tFyexwx2EZBYtNlPFblQeUokBpnsv4p1Jz+MPT8ClUC6sq6H1rktLLc0KZX/0rxXcnJs6p196U6Vf65s6JfXutgFUZTcmqKgoH2y9PE9wWGERfCujRkprY8XHH3K1C4BF9sr1DpW+SA3A9+HHURBI5fxO/utk6MbclpeiJBDt207CmedqlV0bdweA72epntnek+pK9s35F/0pZ7mvuY8v9pDv5uQ64X/xmyG7oT0woVBwZ46tKlC5+uSCdzpoLpZ8+XuL/t4RcdcA4i00zVF60WxDhwkx66IlQYmJ+iEvpXlV3Ncy35AUt9jAxDXXdltHclSxYBpfdkKuvlxr2PQvXmcQO+Ww2/Wc6T90ATxqE07rpEVtN8SmNkC0UEQS0QPe9LCRE2Qz0rgFydx5nAWnNmTdnUJZYdvbcx7s9+zK+po1j54zgqx97a4yy9mMvpx9hZ18hrW6fbbNLyTXd7+k5cXb71ROyatoQ4C22rYxbs/zz7MtNcJitG7gyIZNcHfzi728B6y6TT3aZ4wGW/u1GygWJuVyr72EdLLqAldttLGhgZ+/5H5n0hLjnW6bVYHR2U42UWXu1LHVcaMg5pOqhSLoYnzTEZQC4OcUcqUbYqhKPsv7fhRTe5F+VldJUU47ZpWUVosxka9mmWYTYpLZtKJn54u9Vl0ScKaWvdTwCM+htgVYXsZJy0QGaKHys40AyxZfUEJbZKsoLNKIdQs1+qJlzO7WW7q6OVPp/fXf25y09BMN0jyzNO30Mc5/Ppnp7ltU56m2gXn09B+eTrj2XQCwIbdhNsl+M0Dw5ZZWWmgTCSfGB9Z+tKG6kLzi/9ziIYHxOc6Jy9j+DFDbvhC+lqaYCs7NjIVFWkui7G5t6yPz/sVT9wsYdzNDpTuYVkUVkOZN1UMMoUWeUPnP5YIjLL/l0pQ7MI4m2jancP4WdULM+Flzac3o2myQl3R2W2awRsYfD9N0n1GzkRMjcTqulpXecpVOiG7SDemnHXw64AWfTnZTNod15iWR9r9Fis4Iop03ywfbx0tVb8+nTw7eI91FjnCmwQv3eoPDHYkS/lC74CBWsls/I8FPN904bipkjTZ8a2sed3+SRY7qD7RYiwjFIP7VhMIC5qNeWQwlz8KqeJtflkAmtU+qCr/mcGIVgwuPqFckVDZaRQLZ9YcNwZZnXM2wUMWrr3mrqeZlPvnNsvmHp25eOjUhEq+UtvO6FK1pKq8cLFK/BiMbtc9lluVtm3BUmFigHeFJ7it+RkBof13vmH5KyKU16b4oi7HZKNpLqpn3oZ427f948+d5jWlIwU1hy/It6s2bGk+9JiqrcdmTW+b2F3T7+LZ2cNyF0DNXnpfrqOkQtMmfJij5PhWpzETs5Is83xomRGSVzPCK7jFzbvs+ZY/WV1xH/fynVlNVc/40t1auidpipKyJW6pXF/GU8V3QPx+eMH4pXUJ0monndvD8HUMUd97tUKeOy2zBZRhnJLdAAtwOCsPQJIRpeTBbyGFmIxWEsNXM+LDApHOshyRKuDOfo0H2hCmsV0KwHSlReTYmzZEX7welJr8TjL+6Nc4w/qzX+Eh3H/VCq9hQ2SkxXojo48JRAe7Sc1Ht4ihae0RBPvGJl/iwznz1C6PncQS7tp2WTpUdLF7D9BPvuhkC/XKVna7v3csQhscrdfk6QwJMC1bdGrUyo0lk01/00ecF39+ybcq1aqGtTpHZyrAGm+DF7Fsdzh1vAhZ8oOD9Mlk3I1X676X3/V13HxLTIv/QvajjxqvIxj0U0r4f75yDiWVOrZPxfW7kSrr3ZTKWn3xaKyq7CjNGOWotX8n9jhEPOfkJlSUjhAXzTPEfjF4wN4yBxFCSfxeRdzBhBndjua+hX2Qd2NrVXG527tKEQvLkynXsmVFGZc/JOJN1HaIiS79BdOHcZa5y1sEUImxBSCTOJ68bz3cd53Vf5lu8rTfiRdmuZG9EP9u5vDxpdV9UYVle3qQXjwd6i+BNZiqIi4+0H/mesS2R/bX+Fl1kmHc39ZnX2ubxduvzGH+is/TVxUWLjej/4Bqi0xqYfX5j5s/wM7aAsL3IZ+J+8VS9rvb+ItffXmgmqfJBpH95itcfG89ev8vmQy3WavKSMLZzIZmrzzn9jt2lr29sslen8Jwx6ZKFLVabQHDS4q3PKBsbmV3b2zWlWvaHfuH+RbNH+jfo3VM19czorx+YPlFXnuA0HNYoylWvWC5j2Lz/dUtnWrq212b85N4OhV3smvOy55Md4YQdN7FU+v+KrAlz+U5qbBLIbvBIdy786nhQ2oPzWGHKnC7uWEB1jaSoPEyq2H5Dmaar81bzQtvW1txaP5y4pCNfm6zgEdtVIX2W1HbClesuzDIq8VH+LKXPuhh0LqtJ21o0unTa/MGpcP/Oy9enHyRQi6/01DJDJFe3Izdu+X9rs8MHBoc9jAslu5ItX15WwNFESn5YTr3wsmk+I2777n8zVfvs6S8/OXlCTifu6rl4ydchg7JJdIIEnhOI7pe6aeGhs8Hnb3v8IAz9xxFiaS8QECGhvnRcHKhaWzr20/OLxJdPQV7MbsJE8tyh2q49+z65Rw8SRtMinRXvr34V99e3Gs8QGNoKkmc8eaZF5YggtrglBt0F//yW6pbh2j6W368bVr7LL+k+UVQIuzcE7uJd3sEKPliWdxDfAKyLG/okvIOG97Mvxe7K98nJVNC0vL+uWmlR26MPCvR8G7MtPYW/XT/Ns3Z2+exva8PHudXGXtLpDJsVbgxsHGzbWP87/cCMgLs9JoKV03rcgKU5tXimoje9PdfemWpQNMzKS/9K2i+JEHfDn4Z4W6Tv2d6S8RYWthVlOV2apa1UrqGlZuKKbO10uWi0ZuXTs7aXehmNPyV5PN/W8VcKo9RqHvZ7KsoZh+8LsgGWkomFFcrXxw2M8GMdsd7oV4Sn3pIY1NP7nmd64+NQER4rfLuUNFCgThXdnbJHl2l2y3WT/gQJF9fiPM0mBR/fK5+2Y1zlR15Hqq88bcgh5E3LdLR+/sNvrz6xz/EFTFf7FvzM05vovjpcIV/yV2zC7MLhZVeHpK1nhlIiR06etzpx9C6LkvDiRZ86t8jqA3egmMn57hHbuxfWnN5UI0reK3FCYRqx7O5uPnG7xldfdysuCG+5IOzcvqPsLFN/nmvpCvob4lHEA74xdsGHH3MwWU6pfkWfI8pan2g7YrEuMrKvUdOhq88MFuUvGF9fe2rZ+RuguVdX41lrj/MKl0+pG03Zu/tQLOiT8Tf8I98vMiWvyX6YhQcX594tioDAtN7ksUDz50Vi+vjp10XdI+4MvvJdRnTjPm8bs5yNw3qOhvIha3K2sRXFuqiCUFzqObERpVUOr2UKk+wcK1tUpDiz4meNKKSx/YGjsmQ/LaAnxMXol+0zJ7ssLW6xl00g19y5TyL5XXwgzihlaun8DXcT/1ZfGTkjLvU7r9+Du8QnD3tMEjrwJ+Jp95ahUmOI1VnjPjbHaCjN/1JC8uWBbN5bO/8BjL5Jmz3TSxMjPkpT3zEU1YQkw0bq6odjzO8l2gvkDgfyLrFB1fXpGQU2Q+w0sfQmT4RSE04rhrxV0ww4W1Gam1dQHs27L+bfMCi79+l1snxOG/h+l4erC+P9l/jTWTFbyZw6TLsbhv5N0fYXGOti8SVNtGPPk4Ab0zfKL7dd+/WxR+6tiKYJh692O5K0Q0k1bITcq9Ao04U4usy6N3floFA/ZAw8m5f03tN0eznYj209e4i7hchZzOZs53C3pK3Rb2COkGblsEP3n+Kw1N2atPc4hb6j7quLHM5VH42e6/00I8NKnfDZANRGsS8BH8Tz3dnqlwlsxMc0PBQTL/xAB64wMKqxMpsJp+wF3yHAcb/BkFQ4X5KaEku+8Xh730R98+xnHvBO+3bh+axJAMFNW9d737GycxzZ0pbJiuveRfemi2vLEtdUDjTXGSjnJTJKwB7gyyUtnJgF3ublEd9VOSmpTMZtlqu4jSUipeghOlWZH2uPEdGNaJOh0k9QGZVEC9QeZsNEDGlX26FBlAVnEpiqxzVMUcd0hL7wR56lHRqgyrxOv28kYOqaRe3+9bHkcqefcvCLKIQTSp/LYWCqAcOCi47O5QmSTswkxdIigu2FkZF3mojL66mEDoyL2RzmsNKOmlG+nuZ183odcPlcYp80C9mw6zZ2gV32hTqg4TWIHRxL0jqs8Y44vWa8F/g6dH3UaSL5mCsoo+Y4YmegupXWWV2j3vIFqcaSMcIJNtaG8sTEYbq6vrC1PB/lBQ86a5sTVzjJ9F8KT/lbR/86+IyJ6OF1kI3kA/KSXQqzprtS6itTsHa9WZBa5/74KNVyH85J9dWqfXeOrA/Z9dFkUFT/FF5jgQsM/H0xnpoTN5yhK1leQY9s0SC3zoJklS9J2R/QEUiZ07ZzQhjpkhsL3u3Ii43QvxfVnBMrGXA6j5KvZL0KjBBmIgT2/oDa+wl6W1ZVTRfzo47+iDZ6KmvLqR8sqinu8yX5brstjF6uYs+0nRNyz82WvLo/oH8kZy4MZGSMvnShMVP3zGBzoTHGfCJtz9EDcO/hOncbH2aEZVJzyFtZ3zdraRv08fOGv3BqNRsozSGwyjgD/JEcknLNIVJL2+0zWA53Vfgu5KowZbnXWzpX5akcz2PZTkF2xOC8/sujxC/Bu2syOMrGb1dDheZGOSEiPZjyu292EtAFlTmyttukUU/l9IXtMx6YSA2kY7bNyNQ/3UvpbHPwI78sugn2NseVLOAhTa9g4ggDT9XP0XnZW1zsZ+lkH4/zJ9JxuPsY0OlVTc/iK0QCMi2FcGiq4lycbKvFhkrtEAQUIYAP7aGWf/HIcW+VshDd4MDQ8DQBkOhj2vRxGUHPcy+Uc5jLCPCC21BMGwGtD9NCdmC20aKKNRP1L5wEg7HDJXBfOo4vnuKiVd/aKRbM5jJd7CbXF+gNnyVwNsxJ0/9tduefZrkqEyMz6W9ZWvDYtqkE0Am6jePzAZ/d0V8JEw/7S6NpiWdtjITj11fGDGelHXnBBWLjklK65iTd37Ui6mVTgIscwAj4n4+gjL6RnHD8IUBYjQsUFSTd37Eq8mZJrBakzO1oiiZ7BcFPuvGzXWrKsySKdy5rMt92TtNdG+TyYF7bllDuMW/q/MtZQ/1gGX9VAlflvFD4ESpqkksD//s1jmao8wcql77VAqqYJtI2qWhL/WA7zT0S8c/T2XlQyr8RAhUk6+mUvVgBQwexwU+78iGvik85YpLvWYcRyYzivfpZszyl3+l+PB8NnxtcaBcGNxTp3Qo+BzZrQYJpZTB7ygtSwsUVJGnJGbNxcD8z+wVg0vs6QOacjZO4BfaTo7ObhliBJU0lwfsesDgdC25iI7KEUpDW6RvwMMYsG1YAWp48YwdTpSvaYGluprqQYrnE90QNAhbPkcoua/XFnS7izxvjJe+Ho5sJ8+8Cwxch+tbElKy9jui8vMWIj/K0ZkSqZmByoI/XoFd9roC/duyhbNvVCvbc32+zAbgwmdR3q0xdYOAX3+MrP6ki97cr06cxkkJq4omCK+RmTLcMVbDOv+nlPUfH9977eVkaLy/tsWPRVSYnrIH8at6hQJHA4PmbXbEvh5OS+T+0MUlQo8s8aYqNBpeNC2HrzFsPYh/l+O5fe+CSbH7Bx6TOBx4nmHxhmo9Q6ONyE99Cgw4tg2iFxo41HXnlZKmm28pgRoNPg1sxBNhkG4dasYVYQQChO3jvdj7L8fdM18MLdP6QlfmAfYDN2FNmvItspyDvNAauEIxDy4/kPTItR6/c080nS/sFbPRRhV5fUza2fVw+gbqHSQOJJaSQr/d5Iz7pwux/4smTZJxI7Av0pngZG05aeRfd0ggaYJ9+9lPhacPjP2OHfYsB944uGaWcaEj40bPQG0P7RRuFv3Pphw0hsAE0gdU5BZ3RlUfKS/03rLRiZHc/I7y9KLClwF9d5suexokLRFRHlM3Z1CZQNGSZlQObeV49JqznDnBt8fh9eYdfFBV3Fq4qSlv5voLdgsCWxstlTWuAurvfUgKmrHd9YxLOe1pfKcP/7hyKNueuP/8pU8+t5/+fHh6wbHnC4n3GtR7pBz3LbzfjcedNqXW/+rUgPrxkCCVbj75GlmELucNWSgdO7tPTGcdWtauJfDp6xKKdxaFnTwvOONqzgWnfQs6TzSP91F+6IK3vjvPTLdN/MPjVHemm1DiaIwle7CVo7+uny0zLFizJLlP64aGmy63lcLZRndQS2/t+btTSHGiCKCzTjWv7BcEcBmc1Gezxx/DSrFqcf07ozB358p7t6hohrT8l1MnIrKzPzIUgofKRvtHcnFQWf50AYLUzP+0WXV+j+bXVozTCvNCs4GIuuG/v00suH14h1/LNp825PHgpeIWMmuWwCShTdJ01Pty8FCaOEDZ4u2ZT8oZWNKXkOo6DIBtew5ttu8suyAVK3rerexUT+ZVr/vdBg82uj6u8FBytf4j5BY0cKPIf/WNyEPIdu/5The+UWer5dowoT5bcfhAZjEaeDhnmU90IctFq1Nr4ZHGwde9+XcnDG7212HZ+c+gPsYfIcomXN5NoXl3kPct73lIn5Hj7Hsh9DGquFfHqAoAigCOHZmSiiA+f200jwWS6GFqXlfakrKEr8bTNht+jGN+na9Au/LLlh02KSG1bGk0onjqZsehQcbJ28EfSP9qADspEsxaNfKR8uLKBTf+fknds5//+3ru/4/UDCj8HpWtLb+RPAW2g9u/N5dWTbw1/D5Wxqp4T1dTFQ69N5a6DlKi+kxhE+7cKM/F/04HrO/nN/27sOXtvx/CFEdYxJfyLstsmv/VlLvxkzzoAzVffXoaHY+cIhrq/Dw+HAlIhFpnqAy3i7wvpWZSrO89Xzn9x8Zzl/honu/Cs4HSjFPkiyGbMx//ec0JIHvCsrUqkeJyzWfXlvXiQ88HHNS4FrMfo85lAr3y5SwuBZ2jmO/7xk90ooy64Fh1qJymQds7P36IzPL/bu4wzTiQ94LYfOsaqPXfpeC4Fk9GjdXT69FVqmXWJtlhtUOszXXw8B2xLiM2YhBlu7Hyfi/47PFDszldtdPjuLvAt57hG4D9vwmOX+2vHqbe+6CdUh+L3x9vMLQ5BQvNvuGUmOzxd+4ZiP+XjhJ+AN/ju8RyTogM9fpDe/AxpK137uuI9iXYu8SdKZSvkOtHdOihJnoTSIfx3m+0cO+6qlMu+cDYFdyKodXL2tiIxs/vgBy7Y8vPHKxbUKzhPFp/jvzABPCNZ3gxWNbaq8L8WdcbmFLY8Zao/YqaH1468cSSl7pMkv1gOyhrbLVrA+HSS9Ec885TmBz8aTx+UA2ZKX0cVvZdX+mORolWxM8pEHzJXI1zk7IIooTNl4ScqzdTnzvSn/OPM6ZegNWRdZA7PO7ncyWBN0KySQa7jxQ8kCANCDNI+uoPxeFumJVmLbWm/J3n8qsOM3w9ybic91rX575FkUYBzTtb6f8kfyj/wQzxrwsA3G0PkugbrtmJaGw1aiFoGpKFic1sn32XmimTNXXIykC2fjx607sDkmMvZYoLsM/Trjncdb92OPahrtNnVGJiURmjafSgNy+vFzT0HjN7rOjrEUknENSr2ZnDDTwR5GpvShVLXo2whDGNA2y/g2t+Szbjerk3LhHkoxzftuSedCb+cR7RWDJTbkPF9fxGCxJ1WDZba7rcwwXPS5hVBr4dp6lWG26WlRXfsCAhp7NwwqsT9L9+xlYMh3Do+JKkDyo0e3C9GlhGVbf5q2MT05cZ6hDeul99c0ovmH8DEkaeY5lz3egUzV+U1wOHZ+ynP9EYiyj1QH+YLcNeNH3eyYs/ap9KP2HywoGTD7XV5f5NPgZFru0lN6PkRsl78QgjCud2Y8RcnO+rxvWKppCX5v+MHE/f+qSN+L/Zff+fvA8eKZhYsfS3TPw1Dv03bxfMeL4+gcLrpOG09n5vKeWFwavqCe+iXgPr53JtKn/bFEWDDheLCrv7hyHOmrLKSYQWihe9YbZ3IA2UMANdUEmM7TToEonAMSPs+MS+XlbH4Y6vrKd59HGcchPEJrrGebY1wunx6D0vkpAPcDrOIX/uL8o/ACyYlJtJ6oJjv7WamjVVyBBPrGqE6y/PWXxfp5lHDUAwl+b/BiSbhwkPS94HPQso/GCxfTY1LeZvsiB8NpsNiLMFjSnpXTUf3Fc2MvYsMWOn8iUZb2XWqDIgUnaLv3aqPHEoNI4czOO39JFCT7fRdHakcfBj3HqbsXvEAEDcj5Z9UA3UMBfVUz9OQeCAI98jejC2ilfQ8pW41g2UPv7etHSTk5ZkDGtMGhmxOxvodWcUk313lzKWytnPbffKQAvx9n6YDx1gA/CnjA2pM8fLaB8FuPELIJCCfOXVmpviDHNjfB7w0Z5FQn/w2lHBwvWnxPYj5Tzo6Z6y4Y3DQT6dd+T4oBokavGEYfhRL3URt6vG8s03lXNOT3diXz/v4avfyIYd+ltdzZSJ/lF4Ukeu+R2lEQ9BwHf7T2+NNXHRzWUt+5ReHfwOEo4Xww6B9WKyqG1BleitovEGjTKL5SytvOSeCp6FOL9I2c3NqhlEFdzgc06V6K5rPgxNlLks/5Ae1IAEsGG+B6JVexTQojir7PRGgseNrGYxfB9jGQhtoMyDN2EJ6V1ZvKx8pKG0RqhBT4kWV/uNRrRIiCn3JHmCyRJlfEKHTxOL8e1cPjd4G4rvKnePm3S1gO1A+BAF7beUnoayNahCp66/n4TPSLCdwL1UDHLYFdlrgVCgqhpVIjlGRBq9Bl/gNnxYXC6WTiVvgyCzlR3oSAXxKSRRf1rshZ6GKfmcQXCh8gLrDdlOywHHzVWcwRNAuEoEFXt/TVx05KX/yjufJxCWGbhd+28n+KGqD+pUb0/YWyBbDLY8bIW/X7kspdBaEFIR2EdWxt03WUlGk79rFhA4EvmRhWpIQWFOxaUonsGmM9pO0oK9F1bGCD/yFUg7CJUXafeBgb2MYhcM4ofE1ixvveg8yZ1a9VveajwzY0kOubTYSMWqleJfcF4spHSwN9Ygt/TjwGTcrJRzxojCGRuoHOcJzAyeM8BIaPItSZaifFaU3iu7R9JkrjZnYACF5h4VHmqhllAVvZPiK/9i36CdSiK6Tuw5efpn99gOW4WSQE2bXhmmvV13JfxmT11dsoQIQoLaXMbf+eVICidjT9q/RXAtL8XtT44T1P4cYr7D8lTfwE+pmism2bazcyWFj7dt3bBT1FOwKeoGQPByW5dXvy7Cm8rbUGSHMKp59uXyVWVljalppXUHOd+hvXuw0tG8yZOlJaGVfqkK9mUGweoLNdi9lcWicVl8x1Y7VSn9KdBneupwRVjOwMkbDwEZsXAI9TMaD2IPn0fWTQnKl4Hx+puAqvdPaLyNv27mNlFXFlPvwKgODYmepFLSg29zG5z+TSc1VSe8dhb0PKM0ZnpJJnZhmdmM5d+4huHL1+yx0eDXFdc0qBUCMmj4qEgH+IQw2nC9dyY/g8AgH83IQPLEYq1m1mz2Oz/VDCsAxdU5Vy9sH5V0X1bKYNoQnPMH1mWosYpqvz0b2Q3Z+3HlDe5KIfnf7KOsZMmSFzMIWJvnT6dwP0TXKjs5y6XOIQmEfm09dsp14kySP/BgPEgsyIEwRU2uycTVPzammzRD6/T7C6YnMGKFvZG64QDAW7rWWs1ywT4mYLuXrTyAUSJ2beVV/WYZeWjaqb3HGRFB0bUU65TI2KjL2nSp7LUOjREZA0NdUIQD5p44PqYlJ2JB8xk64olOfkBIQQE0G+dLnd0UKyBWXBDgfc+eeJ+jU3MZlHOwAgSHPASZ8vYCobKTOH14WNSH8hBWUhQlzeGIxSmYNy82mIi3FfIh7UivdZUoUrxyj/egtBe/exz/pdDsHBfHzJxQE62KomXHoSPSrDijicrQkBzKQ8vnm1RxUl43is7TPJcSJlKTUcNVWMlC6sJydjUtCENBIGrsN8zEZ4lCGTugZ3bpxQzec+LBLSlexyCjOxU6/tYM8jDGInMX3jfEXcV95ruGRsviIOLKqlzdE0iK45a+kVwsF6H/1ZNSqNrue08Eh9Yole4vlaF6dWOO0q3EAM9JESiGJuNar8qIExMjtcELSgUHtKpz+7kMqOaVwekKBr3StlJU1UA5O1IYoCxChJjvBTvaSECo23BTAvDMkSACnnh0iUMNyQyP+0V3rKpxqUKlSWCK5Dpa9Qr+HQNrdkgKZSDCBMrhb3m+yYCSzEiMSJERXjhkRkEMH+ejmBQFlge84LgAze7oNx3LZTKDv793ABRmPoZ2+91PEqOItRc8oa9Nkkume0+e7J1e+r0k4hdAfa0P1TEPvK6PjzwOxYsx7GCdRsxj52egK/p+ScvgPXMmwvUYfAbdjTlwqnZbe+yjQuBdsAIoa5EOndd7N3D6Bs+PX8W0BUq07TLH7tBkpKz+YeYpwuPIdoAb0honECTEm7AZjSBadTqFP/j2Al3wSAUMwUy+zqpBM6eRcAUCAIm1NXX6af3DiH0AyXXcegGqxj2MtqAVC8O/r0AmqI95qFXrz6KP3ixn498FGkhC/S9AGwytRtkIzhuC/1JFY44QeASnvL8YriYcSPkaN4hoFMwHBAK7HqsHoAeKO7PH8f60UBNxHJFy5tRgOfkmTOFFHLTz/Sbu6BZW0qs328g+fOl94uDftph7Nr+i2sZHtlr6KBWY3D6d2nKM6159ilqOMaBw/9dH6FAAEQ7PtCs+wsH6POc8s1Wtz6hE0C+gNTphYRKsJfX+fz19X5P/oiAF7qn4nw4Y5c4DcUAGGwLSc9rTmHF8SG3G0baLEtAwA9i1H3wS6QkdaUA9ION049TfetVA6062WAqo+mY1ibj1mZJPxUTuyXl76rbaI76svVwZTILKIcYbqubAUwgtGnjd60Rk36u8lXhKBd0W6rEp4msHJ+FgpAMcI1TWkHwuQr76Y3atKM3pcDEEJgq6LrDJuNkVnBlHJ1KYc+Ysh3L/2SEHPzz2kfCoRPcXAnQYysnUdub7yMsPsvHf+ylY0EUovVT7YfX7kbQWv2bD+mINRqqCEJtcDsFbEhnLa2DS9nWcif5yXKGqsKAsze4fqKSYJNQKt7CP5LuJ/wIOE/gBXEwVYyoXCNX7YLxuv3bT/+ZJ5ehVujj3+54xUcb7945PZm9nQ27A9CN2zSj5Q8H7NvtLGqrMmuQi1j9tq2EYy1zBpecdAg/C/hQcL9hP8AknV4Z/U/E6tp6uLNqw7eH6rJ9xKLRoY6W5hjRw5Nf3786p4uE7ByDLfnofUhQPwhzNjzaegIY1YkjAwtanIq7MdrTuespSuLNq4+9JT9R1ozQtjhvQO8Ikaqctn1F/89Wqrm8zKecDz5P+ci428geCoUPLD+Z30KHkn6e33c/z9BzhFt9X/injuCL/i4cQ/AOP1Td5PgDn2iHutjxtlOB1fGLNDnaD79syz4LP7Zdmg7pIXS4QUUqUO/L8kOeX1su9oOCjb1wQvE3+HU+mKZE63Ss/+bD9wBC3wWRhegoW4cuAbb/YMtFgFwIbZFpX1LSyre8eF9Ic4BhHRGvf/sf5W/cxBPivM+OA70PBwd/riERwPKaQfxfTDVRl2HilUBJ2YRZP/wOIQvVDVIJnryQu5RtrP1kDSpFjAD9ochuT4VQ0FwfATeGgEnYnuNVfG9lt1IuZqs6g69sstRT4+jzl1Dk47txpHrUuM6O6Y4xxYXb8nJpTvBnS+AqGPz3uax6wjeAy7BEdkywPjDNTNJHexj1sHBY31sGPkmaT8qQyfyy+Pa/qOCTTmufOfLhpm906v8RnIy3oxPEckJMBqiPKjs0o1rT3lVF8iwWLic1LYDl5OposhRMlMRm8VAAsXUN6yIsFFKkgCEF5MV5CSjxPcT5V8qLNV0cOghuawtuU1GVW7FLlwsxc7KaJ6vqUF9TihnzM+ZNPRhmEsUm5lomlMO+t9QCFKHk0dkOFavD+11bqVxCmtHGxUCuWwkeVhOVW+1XrxQim1lEMKYFo5fXDZ95rBCyYEAHBYDAUjJTLCimZs4ftO2GPcwOALbkmm30J1n5LJ2eZpFhiN1YKlhJwShqWDw/dRbPGTjWN4uo6i3hipAKSYro4e+FuqzKDstETdiTRR6yjkIYwgQIgNL0DcCqZe7G7RrMIyQzhAIU6ZXcmm3Xv3ppKLUwBEZVbtedlQ2B8ISQ+Y7OsapVthTuvu7+go4uewcIaxIOHhbpjS03g9ISL011A5KsSVazOi6VfxmeOxFYRUCB8SqmARVuSEMFYHM/2vUvpMgelWpVxBRqwl6Ju3WtROQYBjM2xPmoqoRZlC3w6nKuVIhIjMUrI5OJKzWcd0N24S4RhEY0MwXyeZQbKlfzxIUsvoieqdXau93AIJpLrVPPESb+McfrgABADFs4Jk+FMoJuCoZPEwpZtZvDmSmgdqXGHIS2GZ+mZNXAQOoaB0KblFQzdye2i7D8eaDfOWCICeVAyStobqzhU/42bkQIlDjMHhbRrvV9qsQW21mU0C/HWEyJ+ZjBiV/kNe3jvl5RICdx68GGhoOer+DgGnga9WUo3MKJrBN0c6IHnrBMMNdLWTFGBR27UHzYUZqg3cHDGCRQIdESZ9oPkHnCJT8FuYrBNpRD6peHNVugonEsLnN7zuZ2qZPzgxUe0HUK4YlVstCvPTYHqX4XRiBaKZ6GZtkyDac77BPnmi2BTRLfjPTVcHmGALWecaaWl4OEUxkmoSCGickX4dHX7gofH8EDMQf/Ws2Jo01xtq4nThPtYgPEUYxqJ8fXs0Qd8ok3XrNHcQeyClWo+NJ/V+fCLEAeeMeffknk3H99aN+6UwKHo8DM6KvqFI5aCp7+JJKvFgiPaA/vGiSU/gcQsaRyF/JHCyFvP6cUnxWxr6bhOw+od3OYdz1tobzziWBdGNqlZbVNFK/i2nctUzGWUcadEEo35CarSG6WvRrE3DKtqK72mW8Qdydd10kX58a0bGOdzfz9z5DUPW7fDwsQJ+0+9ULqfk6aZb/6nrqmKWCDvqz02lRSEre4+P12JE8V70EkSTvyyIsQE9sAJvks7T+iQux32jy0vKeyvfNkfMMaKaJUIljmjY5YXKcEe9+M9YongEaPqy9tBN716FCZlPXHgWrp+ws6/zciLtUNAPL24PXMeoiHvfO1l2rZAx1HGZwC/Wv/+0lfDyjuPJV8yXKKLF9itvtDroDEvGFRIkDfafVpLwfBS92yu5E5fwtpDgqH3zxq0v/Lsvn/AmiPpQ16FVj5osD6LvnHksiq9OqMz3/E0UXsLqg5glQMLGkSivE6dwFCVkNkcJwIMUayDWQGXnypsffmZdfUOgriFPVmfX/6gXvGV8goxnTa1/jr0l9jhn9OVX2nVqzvvczw0s7RclxqdlNNvHxLhj3zKZ5FQ49+c8ayJriWJdMUmTcB8VXGhTdVgSVOziNyfNoMiuJzY/n9grE+lKOVCa2hzQ8WSGbmaI7IuLscMwumyqTmGsIquTqFGdcOxAkMH/sA+4Zt1yisDSZDAKhu1enpHUurPa1BiPpw1z/p3AeNF67k+07n7JCJW53nBbCTLMNRf05H1Vl7PKWCAv5MMao5aWSD5lxX8qC+S0NxUxtiEqUbPapfn3P0fxb1f6Q8G8SHR12pVBln2rQDC4n+JODQGFpApwypUjC3y8w/vIXSVprJ3k7E7NS9MHrNNsm94tei/Eos71xnq6mq02IqipGr/JawTo7JpWk82EhoWk7VPNaK6Z2V2R6E58mcNksuH9PWimeQQVnu585ROf/4/LupeuWUMVgwT5kqkozZo/BaYgO3wIZlg02SadyPa4NuRfvLQrnBp1txnv7C+3SIdhDsIU+Ha+QT/66Za2WCWr50rhuCc2UunJZYmnN8XYj3xBTMR2xR9StcZQEiRKU05FGscyDM69O68tIqux9IJmkbOvsSUgL5p25j0zNGJZ5ZuvC1kDTX+YRI+kp5LMR28ZyjT7NOfJxR4/HJ3czpAseMeaLXSlMqinJDhUmOxadNRVxsrdhhGI2NpbnF3BUacupDp2VpnMt1no3W7B1ch6nhnGA4IJRsnwYFM9b9YI2Si8qOU3WlgSnsVbJ4pcR/vbZZyohQhTqx5njyktNXX/YMQ1j+EATEH+FElhgfHbPMxUQRlCjebdBlZbX7PxIOrhX6tvP5+6M+vF4Imr3GqvYWpd3mOWRlc+K/GNudENhLjSVCFecdyWTbf49rne1X9SRjF++HmXFhpZwGHOpGo6PB0KlowO0MKPSh8OU6KYOXw4RJeMojatLq2myR9kfZYfAqrkMLVHpOs9eT1MVT3bebLKisIl7oibdycQELhpaXfamOZ6RXdM+FpaImrcEYdI7ZyofwlKGqmraM+rXz6vE6sBCs17Idh2w24C99WKNDp1sP1eAIQtde8Oofo4jNvy5Zcl2ChQp81mSsFwzSZLE1ucVJ1viBBnL2UzDH/Y+m0cLKoykZfdQnNtlB5Lqk6Iduty5BTNJCljbIPxKwB/iitL967wORHLEG/i5mZ+JARElhmGaqbRoSfLeiYqA5AKft5Any1FF1SoICTMQF2BVIQwxpHK3szzwIUeIf56x1FxZlJekXgvIGxS/Fdro08r3c4UIMSGyxmpe1Ep2CCU9XMLOMhthJptrtsj06cWTU1AHKihpNMbZxWGFDef7jmY7G2ZzHMLzHGn1q3PExm6L4WUZq8hi5RnmDnWJGlYS3X6Z7jUkK9vrAIChigu7trWh8KCsDX8GokO29pEVmWV4QulM/KtOyCuHNUV67cEdR5ubya59spokGGPYzpQ7TJeAblS41TvjpC8K3vwfoIkvRD9zILcGuCRBKyFUU06CH8LLz+jGpMJlJEXm8+ckZ7m38spi36ZkQsE/YuMbXn+3ZFpnlzvlL7Rvlw2BYV/L38xj+YtsZ5b1FTvmWWvSNa/5Il2zXBwJTO4NNMqs2Llsyc/XcA5naA4CN/IvGWKcT5qEEebFyEbO882wFen+puKUbosu61/N3y0UGIJbi6t3OGk5HafIehZaQVeLEtunskLHPt8qaFeZ4VS/a4jLaZp6galujYXHStxtp5wQGk6cVdRDoKmrFPJlt+yAeq2qx9C638261cnNKqIXPKCL0uspjDC8WaJpPwRxEVc0lA2UFEXLQsZigSvBbCJ4EBGx8V4it4FcmCDFOVH1y0XbhJwYR0T9BlNZuRymSqsCRZtIkJUSNjcVMvucJqRn8wdCIgaTFg2uFh/Plh/1lr9dWsTWy809XRQnr2nd0+cgXjTRoQP56PRH+DlB2+exW2jrbC7rsEuINn6QIjeXtzAnp6mpVAJVB8oNcRqwybHkyJ2NN9xZ//jfP5YBNNBz4xcocp+ccBy5RG/51e6e438Q+DC9y15nJbyVFJIdMjdHKTHWudrFoVtDhIdNBZi2Y+GDK/FiQs+AvhleZgXRM94FlszKFaBx4hYYblfmOfxroUGb4/gkzSqqkpmDDmhnWKSsCUNkV6rMevBK6OFq9QzjmL3m2WfM/moU010xll1yBBQzuz7tKK265Bhjfj/OP1fhbz+blwhV87djxij0QNkdfZC7teGAfI4f+NaolGLlRzp+tnq+H8a9A5V5J1hLCWMkLTieD2NMSk7EqU5xjSi25Hm0qmA4N5fUklH6ZZ3nXZP0cwHfesCAY1QrxsSeJRQmRsOthpQURJ6U+QldcTvU9GkMp8X4X4BnAE9swm4JW3b4c00yr5KbxpC3G2aIEHwv1gWPd5AOXr8BVNYS1Im/FxAu1UaKVmorvTKTP3dH2GKxT2SXelAQm7AbK1reLEWo0WKqiLmMmt3ZqLvRdJ5iLQA+Y2T6y26xhD/N9ZtkaAE44TseaLBdtpPHGQG42dcg+DCzxkvaaVuJxmXm+j9VKov9L6kF4zkeJ++bIInHO3Sxc/TQWap1sao3HCZm8QKbmNK1htxq1xaSOBIqbirbSJhJ5K3jz50KxOh60zNbDmK8bULTRltYVMo2x6DQ81+AbB7v+spnz1LfBKC+t6PjygvKRTP0zrNU6d+y3BrNFjI/0lA0pWySwjO5uOB4d9+JQLmkQnO4gzODTFtOqzZHI0Q5XW9qqltG/2foOdcIgPCZOYs1S0aci+k/32nohQgcP3uzZVialVvlcpVOgRsoXvo3mCfj8hrLhmnClzuj6a1Ntctwenhc44vOv72DIzI4Vq819cl/Rj7lkG8hBLB2B8sJiqlb7n6kCldk+SpDJJpfT2b+cGXAqRNvFlsEjCRldvEfaqSwr8tX4SGTSAOJBsIV5Zka+QUJKcVwA2+PSCQC6Vwq3Td3MS+wiELMi/UZIU9pg+ZOYDNT/BvMq3EtJokj7386e/2KrXO7IKAmiOSNUPu8dUdWHumfy19HVxpxRCTCgZiuHPEvVr8N5PQmVFzGWUbCj0MIgf/e8CbwF48mX/DvHLnaCa0r8ObE+vPOzewYTUfS5TL6P63/B4vrJ9eCKHjU14GKrpIe5S4tL8bfIouz/69+/nQ5LC4Kiso4W3H+XZIaBls1x0PDp7o2e4F2Sr923f5xL40mTipKHqISfxB98r6ChfVF+2fSQh6hCuffN4Py5JRYNU/b0Gd7fC3gP254mn0jfJBL5a9RtXJLcHpbYWU0QiJL61wtuGrxGMYyjcNbJDNkEKiW2BW3JgdZutLr02IDHMz3f9dWMVU0Y7TBzpovNG81V8EvI8NvC7AFwyP+2eBQ0919AwxZt8Z+d3at9tR25VTK88cG+OgokSZoE1x3z9JScW3+SV+GWmXvP+/cVb29V6/iewaaqSPzF6XUNWbzbtOEKFYiQ/I97twGJ1eTXvrSc0dVTMs9/nfTe5q8uvz80pmFV0uar2f7jvWrqFZZ+5oy//Q5gWhSyLPtsMZ1NFmC9erj788B/+JsUb3nlgA4qqRx15KRxNyMvFfdWhdTZQvKWzKPfX2w3NnQXhqUtGkrwbaEIr77cVFTrpi7adWixZHACkxs5IRLaJNS2Ue5Jo2/AwMt/AeoSap00e2auEsKwbP/uyztfwGV1LPJzILPiq1NgZq88+phjXTO4cVqvrb2M1OUPj5tnkkC/wx1b56m+TWbAdcmXLWNP38rZufPytzitlHsuGuPxqYB1/z+coKm7Z5m9pLVSZKaZieblBe31EeKGEYwyaya2Lahuq7z+/2mUfMNIrg/buvzLvZIq6MQjtZKHFB/sbdzlA6VNUbjnXUZI4V0qbYfFM/Ym2vrbE7PXoOxSUTF1RWClsFj9rtj5k3vmDe07Y5Iy7Pn4vLrxGiIOJXwktURu8+8OtNBBhebOA2GxhewgZN9yLXCWSt+sbwpaC1bGGoq8mYS6m3ggnpk+Pp+nGv2ouxGul8BrgAodMs0XXSAN1NfChCzbCig9oiMx8PzBHOC+oKmzkKYjWxaq3stzMtkWXXbH/sUJMuwYc+wY8WHFNue3PcPlx8gOLvL9XIIoD2Kp635JCnh2s6FChJC3L4Tw5DQ0vJ+d4SkAU24Nvgb5KqrbMotxZXMiOZhUJh2fWpfEW1sd+DsEIvLOVtx4yJQc8DyhI4251R2qdPOOQFkF2Hv2sY7PIBfJ5idsEZvFf2sS9k2RVAI2TDU7w7SEiA2XkWz606AZ9C5YIwBFJkfwPl31hP3+UBnXf+wgrfLrpG4IyNzudJf7I0vncNouO8auNFcKHWdtr7sewms0LXjs1TzTW5V2y0lAr4PDSaMEKB4zvaTByG/9lF720JlxzAnE3J1yT7f+hrJRM6MjGcU+eatZtQfP40TdcEs/fYp/EKedH5uBsEFGwEhhOnQSnMhCzjTNr7/cGfC32UHykZuhLGHeFRSXPKiYde6PV2P4/98ttgKnFnpEIThp582RLjgGI7TALwGIUyawAvK05MlqdcjZRfOlKki2PFwmLNJ6hbz75Id4FVnG9e0f0kwWgb0YVOirwCAqw3w4XnlMpASC+5W/L4+jV8prLqluJwVBoiFheLStdUvnGEaMpoCBvde4rvG1klFZ6teD09i/6jCJaX6iCnRW6DIJ7WCLcrlUGSA88Dymjbt5gDnt/R3sf2whGje7oZoU6BFvNiA8R9taxF6VRZc2L1gV26RVs+7RkweXx14szRS1+zd2nhhc2Rt1y3FlayIcT+6vH89+MhQSVPnqNW82LZQ0dHKYA8Yz+StRkfyf2IXwtGSub4I+XOtYkU7vsg3TEE3V8L5/a+vgZp96q0rUu+l8mxIVp+PaveO2prCzcXn/wtoJPoBf8mwoGJLGPseudsLxNSC3Kn7fRCxkS+XFY6Tx1d7n/PdYEOB5Oo3qD6chEAXaNTAiLaQFc2LxR1/4UzQOmAIkr3tGHKqqGAuHQdH5IKc/n1SAGc2IWT5d45Ab3suZIjK1gzZEzJrUBJb/59KuYl42s0JbvfGmPrnqoomZxgzSsPUt52RzPzxvgLV0wt8c5StSKwpcVMG5sV2khRAP7zpHyhSBfE754yqPpkDUDW7uiozq+oBkZtZmQWL2hcsibRNtrdHtrw4t3LJYHubctEOLgHVRxEwuJ/VUhGiAF0neOU7VOkI3z8OGqgHrJ/Ze98yEFNor6twGm014gBfMt4iWp+Bc6rmJDDJWzoZ94qFaeZ2vAWBT28yMHxpLAQzN0Gheg2l52jIv8P5DLCu7oDJ/ELJC2bToTjFAYVyq0K5Ral4AZTGPH/pS3dX/5enVZX8uoQ0s0oVWfCXpI5cERi421RtOlKL9JCpootSTA41IK1k61HIidJWVWvpRDJFi2KrUjKRbRXZE5I6G0I6JlwrjhtxTMHgsv9/Ovr/OE+RS58jzBaLw9PN7/KjSUXNviR6ZXN5ZnJlNfgXM7s8eWbRe3EeV1G2IUewZm3MNGiNs49I/WFaY3NmUk21j0iMFjeCtHXpsnQ/ZJfK0rhQZGHzAW6GLIMLJqWnl4IS3exWGDhK0z2slqbUtLTYU9Cubo2ttNLsSMD+zMfunByX2QGlnDfroGCWa8Py3BZzIDvbbeaByR6ze2QMmH1AfSA9PfSoL2l2p+VRiSOtlFXZnB5m1QPpzgULLljYdJ2/YL715RAoQzMvGBLzSv4KqBoglt8kyQo5NSs18ckBEUMmdXMBR8K0TNPHJpG8VSxZHeJ2vJddJyWl5Uc6EZS0yb5whC1iTy1DSYE3mDbFq7DJpeD9PzcuRJGn/iZb6rrfSHCmRxPukhWFtXY0uBVFIsdExNJbkd1hC7JaFFUTm441ylvdoUBCLDOaU++IqLzd+L2BBPcLOTBp26mUcu4kr2bwe9+s28dn9FfjBgVn35rckSem77zf6Jn17eeUhMKSHzGuV04rgMIkL1X9HowjXEnqZAskXsHS2sKz/l8OMj2c4FlfxJuFJobPwjikFZyNIawjSt2tC7C8KJhpnimQuDQElhRDvhbaIhGLRfzbfNEasWSL6zY5Jgkz/S6J4HXz6y1814QVNxU8iukVueGrJPCp4StTrvIHGXfrR4kNevRASjCegWDrXdTAfHtnlEIQZ50tDduKpyi0zXYxLKkika4ZsS9NkSbHAvhAxoXb0rBFmD+JKxlJrk740Y82CYcu0TTBT8jKi/nxVRm5SuE2KW8sjFa8Wa1P5UHYV5Z+xtsok/+Mqrro9FBqjZU4AO5K3ZK9esOIkNGW47Jm5XsrWhtD8iqTTCDQxtFmYFmxgyqNKkFktFYlSVT/6bV3eYKxOKvTn13Y3jaYaC33Tq6lJfiKC9lPNcv4En0FnxgX6sKXL+rHb3+ZizEXV0tt8v1cdTaOgpcGZgYtT9aneBNsgYDN7p9qSEj27v2NZbAOFjwfZrsqMIDUYAkN4vW/5aAD5a29ydXx/sCCDbXJ77B0Yv9x9EpKBrdX4g0PSpi9SwzfUuuIy8vR6iBwswE/lXIla8BFdUNUPqlsYavkdS2CrYPQaSHQAghR6UYgKB+AaSSgg8kG9HUI5B25ktqxv9Hvd9gCflsdjkTIODhT8H6aQMvWuDYBoBKYKVvqCBwxEDODiKh6Qx5uZf5Sw9YK0srWbgItYAMFxdsD/mC/PT7ZpwTTQQtljx/abBD74ssrAdM+mvX3kVeSmyNkRYysAjC5gPShkl8h+ayPRoMyqxIAg6zeL7i1PsWb3vNlVZWrevoA0bJiYFp0eGmwspK6b2wHf+f5wkI1PGjQsXzBw8Ffsltmny+9/TWYPj0ju9XxHDF9oNpVVaVbMTI8HM2prJTzOzp26/dNO1ZdDSf3K6TN/FmRvTr/dun5SXDnWg9i/3lEib1Uq178TskB8I9z2ZB5T3SDeTuMF9u+4xiTyrzP+G/pV2/Rr6x9m1vQctywum1jkm6LoNS0XxtPS+kAWuV3L65SqZv7zxFYdRFVf7J85AmJNkTNe+DFGCmHyygo674NviCaOEe/Rb/6lmfX8RZuwdtVbUNgSwo9c51+sY6WDRoDlNA5pUrpOkuJFlLGSt3eV530g6J0VOElijiTgYyHgHecckEST0jJb3Dz+1SfHAk9mZyaFO7Tr0rFS8KBs2WfwXZPWviYbEaiAQMTocmgnGTo9znOxSXB45ckob0505TVEt/fy6iC3xyZqAKgDhoGshnYvdFbBgmsCH7eDvIT83tN3E8zYwaqlKNCzdRrztvU9lg9VpvlocAl2CJdkFLfZ4RTue/j8hv3nT+EaR2TQ0wruG0Gm/JExrneCMy+MshzY3bCRgWTSEX31NwUl7Y1eBNXWlDCkPvSsn2ap+xU1z4BOF9tJQMfS1ev+PuTA7fEMKO8uWophe2ibN64kREy83I6lkky9A13JDRyLjN0A2w2WST7rYzsMy3Gqni36AqVH73A4R/mCbI5lhx/WvIvcb3r1V7mlU9pt3GP2cSMoeOFodIuH2/V6fPMTMesJtBPyu+jEh/ls/1s1dLXcFFjZAX9BvFFKdWZwKNlw0x7qVTNxOdXM527uexKmEU6O2xPDPLDjLOYHBhlTq2OAMoMvmvBTHlbKoX9QZW+5opgFT0GB14MIFQM5ule02hW8CJNl3DPjLkkFq/HGX7h+YUB0WF+l8YZ63CQMjGPXfJ3VXIKyuNdVsXtLRTTYN2KdhUTVNKWkvDJJMXx2pk8HL1V8rMwvLtN1FkSrq4AE6TSp+Hhv0uiTaX+BKNS/KKAvYCtOPQhSivfzEGy56VPtp6MN9/RZ9cXxXqJ0kSFNdft0Ev2AvlD2JXcnSehkG6YYHT0JZnkLMvdka+gUZ87z5+/Q+N7MCmh8XPmjyRJUDuxvSLQeoz5+gPuitJvlTwvhxF/J52pAqkpGpF9KBrV6135KVnJs/WJ77XE8IKK7WZPecbM33sGP0+SfKdRNCvY4yvf4sxi5119c1+XzeOWysFYErWdMviSS2NEN2CYKAWN5zSJBHeETYRZ9uRpJ2xOvGzwI84IY6EU/OpEfZ0yNWb/SNwH7ZAYDb+NQ6HFzW7Uezd3Ig9XoYqn/ZPKtgy9TeUKgMIkd08efs417XgJbuTb6S2vwevWARR8gSWnd81aMHo5pac4UjQa8MeyXGcFOfMaUCWYVZdjXcvG2OGDDgRAWLoRi/miHMp3D1QKM8Kla/HE97YyUgoL2uwLWpxacf7OxV+VF1sDlYhI+YpVT2CqU9cVLMdGmgOFv7/+0p5Qs13W8yH3Q+qkM+5p7Qn1WLQz1zqlTsJlbqLe2DzEwRZT2TSN6vlRMHfCp/CUKA6XOeYgxN+mlqhkUpWEdWF0TAM7FnHnQ7RqCYXkPi/mBLRcyFTNtaT282L6CUTBauWgsQQ5EnzmYKyzALN0ZafCKYrbmi/Xn3q7Ts358idakyLvKicpVYooX3ndpYE+8mNKq8JDrW9pAhS1SGUtbRdBmPpgXiWOB+H+a/a/ZqLKmiwXDPwuqYVOU4sb9CguDnivdjFia2cdO4vX7CtKVSCjM+SmsK5FTW6E9HqwhqpKqiznOYckiQxFqHYtvZEJkldMcczmjmEDdks4taqky2ZH5jaUOMnzG3zpncZAjIg9y2gHdb9xqO/IDotaYcxXV6jzjYoUFM3OciQrFvMmebx0HsuoqY3hrnvG/i8b7kNg4ifTwvgv6zhzEITy29RiR6JDzLowutUJOxZz1j3CqgBLaeQCozBy0w/z+GPs3exvo0FIJK+YXKE0Mql8e6SSF+gzYzxiWGXXPzkVOrdkjP68ltbKW+/4nDy+XcITT7iVoApgTaHy40LJuehfMM4gP+Fzpdur9DOdZIH7yZ/oKjizeG0GxvPq3BjkilvpnhDzJHb+amMCA7AEQmfQjfxhsE5fbEuhwDF3fbM4WKzbyg8MmFMcxlkjP1vtBvddWRX2IYVAhay3+Ww10uR7HSNJmnTvtr6pXW+fcZgy+J6JkxOimF1TPSp+3eS+Ea73GslQq5QAtL27dGMrpffXN6z3i1qzfTvZpjbKEGAI47arR6OkKQv2980IzPjGZi6Q01DAz+RkYVfv6NS1c+YzG8MSygPDSyqARE3uavDAgJJiQNkIKHKjLzCYFQmTtIeiZG3etSmupBpgJ6n2muTmxPdqmPInMksdPOSLe4uV/gPaU6G+Jj2Ap6iPN4TQB19uTkxG+cWSqrFfWCGiD029CwYOZgVEUnlfaMSfkgTgmLWb20j8xC+kl48bo/Kishbq/cNLGp9poxYNcnvhceGTdTKiWklXqred398/q4T04ICS4mdKSOBXcklfdyDr8yOIkrrVkaYucGVgMekr3VXi+f1IE7VLA9FxFZ0z6fgMWknrdrZ3DBO0k4aYDz3Q5f816Z+6Y2d3bdMWPD9tHMzVHmPfrT0LxG53wB10uwEDrby2rXZ/5UH1W3srgvbbFTS5dSzRcSE3v7utO10FuSOTxEDjz5U1ExM1le9u91c/613tS0xICACq8xq1XmrdvuTdTUsorfXUa9mQO5a+u3EZZTRb95OUunl9+2dOfW7uUVLtAg85S/AfZyQY9ISrpzeuOuRWDxUL7hNMcS1n7SGn8+MyFU9wTebsHXkhv3+SmMzEK+ZYre3hGmUCSHSWIuGRBGv4qr426NG0UcWKsWS5FbzzqpscgaqXY2N59JiohPZca/vHpcnNiV92M+iMQwyWOn7QF25Hume3UZUuauXsWGsyM6ZAQj/TNtIeqZk9AQCvDTy6pSfJ12R98PjfnM6PS5MaXbclyq0aEvS15N3mtQWC7lA1EcMafuW1/2sqaPIDq63WeGlKzeyJmjCozJzZg//OlfAlX3A74baA/7LiLQn0scddfxX9EHLqoOo2fEnfsZj4Mis5+7yPhr1BYLKh4VPwNG6D3x8FtIx8PPriCtwUk3mPnfbHGqvai5kC95L1A62TZM072faJk8q8ivq5DMSf86nar/sTfmsszWwwmZ+3Ev+fN/JNZeUWHIOrBGMt0OrAb1VDX8uyqmq9JQU541PrKcKtLISYnRhEihlANRJMDg88QIUUsV6cPMNLyMlJKB4UkIUpsZi+vgy7lzYn7/P20XIEZRF87XKK5S+L9NG/j30wLoPAbFVUByc7oqrPeKCRLar8UhehuFWFC6mJsqDgTSPJqymLRihwhSw9vjE/IzAViQB4P4ZtoxYcCQGIly+YGSSxOchF5iF/kjAOgtoAmUcdQa8LwLkRMzR4cWvbnbUVAMi147DGiwW2HObitlNoqP7tacddNlpUM3NVbjUAqOPSOQb/4bsYX8V+NAl8Jwfmi7NnjuLlI5pcJNPgT/T1gH4YEA8UzMbZsVI00lDp1z/fxIvE7wR6jy+QiEr9mwGEdT3hJ9JOZprP6zcUWt+oD3oEueiBgkKKlNYDLjTucyjfkr+Vk3qrFLhQi1jEx5JvnIV+9OWibnCHreDjVzjdCYVBXV5qG04KC8ksf2ReGdU3/a3cEnfiQFz/l6gRYmJ5B/sJzxj9ZGrcQKK7JPetATG1YWSeHxOFhShr7Z2qKwwmuJ2TYPyMiugx4h28cRZ7gE9AoaJnmdL7eakctAr8WECc+nCqj0WoOXU5qYOlT1wuyOETLKvXf/RTMEmqOm/RYmKmhctF9z05JVbpf/eRYqd2ZolUZs0k00CFZYQCoXkKEPd7xEDh+AdR3X9DZwwpUz8mVd33L9o/0sbQrS5CoX4AlbGhprhEKgni4pNhEvxSrmmd+LxKnSBegyIYCtmxqTe5KAJEJEfaEIJCPRrdXAShSEsbkhZpATlYvoXSQC9+P4VhMGgMYlZxOYyRv6BYy0VhdhhaUzEWW+j1UROXsgGAzYUc2F+jIS0IucYBCAwGVbt2KvQB6YpT58CYYs1uhRoKVIvPnVwRkAI8tBBdm1tMrex8mcwpF8W9tdjiywDfyMXFzmHqgNzlKeXvzcECvZPIHHT5iseBq9bUdIvw8DsUXVTvMrGU1bUCrvoZDu2qCfL3QAwmxDJP/EBN6TQevSTzs3immHlqQbxRzcE+uUwPAEL33yTkoP5DmgM4nV8h/lBDwsgqJ8lv/AKCTrBcYiO6KoR/0phxH3vUUyclgXsCJZZwxLKQh5POeLK58tcBQv8ygCAC4yXvRTnn+VLhPmYoFlzmX+AU3xAL5S7mQQ/YrufoIId5SQeXmM06aGb+qIMfme06OP059cjWM76sLdGQbpr99nE2GFs7Q39rAYeW+sRRjnT6nrbTg9WtgD19RsfGC2dWn7h66bnZU5IPHxGwWN0kLeh3+r+k6FBsQW7dzHlb3ztDjTl7jM+gS5+oHO1YWHRExzlqgMfxo0Ven0xrzTNumiMxU/RLTc/WC9/AdfP5UtX55wBtrap7VMkhqfo6Sb1D11f1Gpg3ACizRhjbp8F409YZBOYehXO8H47ITdsKTflmKoBXBjDRuKJ/sr+cTAMVOImsHd5fFkpxC0nUkPx75RpMpIZw3tgQXrhyGAhaG5JYKJVM/ZluC8oikBpScIcVUYgSIv99y53wN/ZvWL8eNH9LfCTfiGUlrDMK8oIblVLw7cbuVirMZ371YSZxzYU1cxHQLNvNgBD2L6Fj8/YUqGhj7MvoALbNy3Cow5Q/CEQt6Q9BwCBW9/UTS3aAgqSwgVUBzESvZdIuRd59+bhzYKLoqquKFFGKC8s91TgfV5Da6K4g89ZGZwxqtiE8LlS5+T/XAearnAVSvux1h2jyhZIqEMEbm13ZQYpc1LO+lnp8t9BIA6UbKSSw6v2vcYmybZTS8HNgNkjIzOnIzEmwHF7AqT1YJvdqFN6yBxpgz8UTxndi01T8KJHVN/lRSkIICdGgmZI4L23gGKXYUAgJ0PO7xeovD/8hZf6q5KWR8FItECfYG8W13fYyleXjmFLmffX41O55VO9H3xGo8xE+xkTWZz2/0WLGk6wTOFgGLi9gdA+hKBpuNZOcq6pOkh6I27vb9My2QiSf4mZs/8J4YrfwzF+smH0k6yAOSolFI7vRhHi4C2MeQ4k+LKFO+1TKYJL+eR7xWMOxUuq28mBCfHHgJt3erif/TMnNOuD+8XeEIBCOQkmw9qEh7bAwh5n4ghHP2wS4RWiorNpHVeEz8qOIcwggjinIVfhMpPIP3HRpQirZKVLjuQRNVuQmRZE006GsoYL4IFXuBXonuSifBHNqQqs+cNHFhMpEqkpmkaLe0PLX8l65qFLBpSpOubdwyoqVBGw/SYmYjIAm/0JnVD0zjcyUFZclhhV8mX0pLEt0hlBJuySXIy5j94IfP+KRwxFE8FxK1+fo2Dq9zWrZCzaDmr1yspJvMv2afF0RjM7Nm4clLVHpV/Dp35VvgWRBZ0jqcz5yMJ3qlNOmkWVU/N9JE9UpBz7jq38jsU7JKAFPZeo4Wj3YhehaWhd+X0MeLfwbaP4lsJBxzQhut4YcCsLU/2UxA5/q8UXxTG4ff4HQ1yz6f3CMrTqSi5B+l0odxSCApwiXXbuU1zKU1pJ45SMBSdanEmHEGudGvfx2ufs+jftD/Qlp2+x5CAP2OJqYuvuRgN8Bk5h2KAlKO2bSeOoQmFuuQy4bn2aiaTAJvmfyXn+Ny2RpY4wOdyTm4c8Qigf5vEGxcAZgbcDQybA5oWCHleTsCZGYM/QVCD6/AWwjCJkVN6Hi05Bpwj6n8bNjCHVMa/HjGKYwDLZJXZMN1jeED/z1DiHmm8mXvpybUWhCmGceTwzs/Rk4JfYQvGwl256DkDm90kEKiXHd3XWRhUOGWLC3CUuVEtyRzSwnsY6qvqPIby0HSQgKIGB/g8uoVOkhO+Xcw9YchN4/EnJEYMiE7uq6uK9WMuJ8YokRDO5is/ZdaZuUF6jX8THoEPDuOAiI4JLyuohFKQ6xHcvZMZ6fXRzQ+XBFhsGby5QIjzPxnqURg4+EQQqOzWrD80LUCZH1Msv8nCL3y0oC3Wyv7TelTKG5JwjMrwzq8mPGwqPCZIkITXj10tgf0cAObr+M8ur5FXpBKeAeNoTiHJ5slzstW8XXZ2tQ5fpeKAnyx2dRp2koTPchjZNeIcXIzhfXr9jHGlahFi94CU5xDNC+wYAKDOpHiGi85KO3VQENIXuuk1veZ7bXwj0cqE/tXLW1PUsH7BgEpN3zuTgfvV57DObOBU7/BDlub9QsxkjPGNYvjoxLmicCp2mSYSoyhSO4ROAbJu1FODbHwrkiEpDOVDowBKt/a3qyDaewDm61akTkUZ9Q1VHRVVzRmdVcrzRw6vp1lGAWPxQ5sl3i4H2FTuXtjjXAfkoyc8Z74gFPN+G/40wwVsBke0AxHrmX2p/1xVxNEH0wcBwjj/itidmdy4IX0PSywunMEuZgRbgx+btmUjYbdXuAnkkLgn9CLXIWH8lGyREmC9aFtiYXRmllXb5UKqIVwT8IDZItRtB6OUhIs8mBb4sZsTYvuvqdKo6Ew+fg6h3wiESzQQBPhfsfJGk5AKIByLNDoxBry6TCWKXITOVk1eA+KD4/5gyGJcLQJh48HPbRSRFajNyNYbHwklA04pR+yJRuQtDb2G4f0YDAbOrrQWHuLRHl7AAGBsmIElI6pd0SUWe1cXHQjSFIo78ueFwqMfhqBHPsJG3KCgqjCPYNL9LFR+I5vG0lQFWLMWnFFAbPWyyo8Rkk0uPBOk05KjRedy+PE4mP1y3yFUBgibNv9Bk2T2cej+dy0P7fc/o5Y7/k/mhXT5E8ggZA75lJwPVyxjj9P6P9HdzxeLNuXkM+hBAbnBFBZeiXlq1Z7fgkFywnl3XJMvqv1HY/TiKP/LOAqrTPyaI4YGKXuKlFv+6UIBFj5TF+JAo8wo/RJP1qOwGc3zkcpHbrvc8PBo/8s4AmtysLPznSTv1o60+eFNARKxCHY0fcO2C/F2ENWfLzI1wvBXj3gJ8+c7H5x0/Ju0SDKGigAGzAPoyFyBQo878owHJkgQ98gKDDPuuQg4AZIVooL/qst0OAt4dIcv2U+hICVdiPE2YazUu1oekbKSA2eROKgSNdreDlT+2MUVh5EYXV7130up2mzdB2dluMzI9yEVrsnA0cPL2zjMJDTWQy4wfv3LYyfUEjmUdh157q5+04+6Y9IfGO/QvYW2ZrG7O8gnBdjprG8b+7HT4hTEsJ7kAzJHXPFRpsm9gsQljM9skoIgRL82WnOt0YRHB5GmNIhT4aL3UdyDyPYJyVdIwCGEX+IQUObHQdpg6LBRBiNvrXD2g5KZQ+TY4bUmVOmHzZE42ikdVa1HjRlqCxSOBOCbj57RIMgJaQN9fTCA9ko9nPUCNmH47vCpMrYCt3VmmlsL9AyEG3Wni3Q4OnZOR9I4TgDd6zAMbH+7Lo0rrm3f0zReyHpojRxvfwkAXfBRDEn/ZsNcjmPuBpXKTgSAGPHm+CMeYdKzO4p1W3fYzZutE6Lk024zkRXiKua9yWG3KcyjDPe6/wCNYVpqbDFsLWtyWcy1RUuxfQAIS8jSPrhx9JtG5iIXOPEKgcSkwsvUjLJd2Gio+HiDQmd1arohTTR+t+ko09K2PgaptD6x52mwxgq2PRQBR36JLXf3ddrcH0jb1by2h5Wi0o7FjBilBUkoG7rDiJ8wqZV1ZdgmsWd+V3O5bCr7qo/X/BtOmqYylZWrIboGx62ePnULUlTaGPL7UlpttsU0z0L5JQRik0v6MbH1kyEU/DzV2OYjTb1qJjewo/XQPwPXlG2JOaq5lLjsFbUHoKvJXWUuLKLy39J20A90lsIfuLTJbGDkPWDOCAvWEolWkxWyEmcwT8KHsBUOEmeWAvHyu5EpN6fvLiEcs3GALbCcYEpusqKwiGuE3OZ6IAFynbgrK9sMnTlsjmzjtNN6bn8ckqyDQ1bGbP4erdrCk76A94OHEs3btVSxIU+e8S/Tb9ShpbU8kE31L2ld1IxeVcFRGJ3J/CPZx+xKXzBPvtUbfoIztJKs3XIBCaeQyHS6SdFJBnDGAsgQjKuq1Zi+NZS9Z1iBzTUHakQMek41jidzcBhu5iVIFOXJZPvdlsEFWkhA3sbEzOCG4V32ApkFllrT3P4naluPGWztSJbR9cGZ2fEW1no5yc0xUbL/6X7a0UmvK+s5JCI7MoLPCBK0IlP79GScVXbKCzS39N7Q6ofnQvuYSmZibRs+79xfK1Y1ya7gvsMewAntzKmblVnsQJcj8eUNm71aktiNMVOF26vDhtHhBuIQUWbsOW6Pg6VfTfSK+zXZI8ypBk50fUIxJBqSmpQRhGksCMJ/IN824uMyvZNXpRL5m8a5o00rNtmP6UK9eT/rxO9QZy49cVH1ASdFcS6nlXgHOps3eXlpHwE9l1A8XWFiAM854f/QDzzYiH+8pc2BI6ZnjV9nIVQVKsXkhXZ7EJ83moo4IqZzZONGB69SxPfAL1l4BWJ3rHHa9DKTbhgJFQEhrxwiLXFmE9K7XKYiPt8QBPA1bepzufrqPrjFpIEgBCklGRURUtdFDr/cEQU16cQ/XKOX4hl6FqyhhNU0ybr++mA3cBWV3hYI026Sk6Z+5kkvo+Scj5VcTMHb0cURW2QtJJhMrKu1ioxHM/Nd3DuA1JopCxK/nmLeb457okwLMeS5n0JB+qasYSrsJJUDm/jcGLYboNS87kz/QSpmmugfJgV9t+4weMdwrbbLIku75o1CJ+T6Z4Wyx6WyG9DETX88iMOtfjeS+XK+ReF8r3tAE9RQgrNvJckoCCyiVBqEhT0h5YoQpx9DC2rhkO01T3MZCC4hQEPNnSn7vS7cEBZtt1iSD5p3/ia0etfwhkO1SyUZ0avGrYXHxR+Bub4ZoxtvlGgvQjILxh8VpsMqPXUFzR/aQAimlakoQlfAknQaVWkQtgOnZIdsxX5RlCGjTNxtfn07Lz3oSn1dP63wdE4pdligtg4wNp9rjgWkZVCDFoVbFqBJF+8yAadsSc5jaw8nfNPufLsKPIU+q5cdilt9Hf7qedgVNSFaJi8ApevyqOpOzbzX+xuwJNC3YkKAJdTw+uatEkKL8XDOswlCp35WEx4vNJLTH/OwKy4xskMn2plPxQYCMoo/lKq2XtoW4vyQrHSSlEwDxghuKffVlFUviWctoGWREIRd7LNqkQsL3m1iokRcpNNv2cwPzYcDU2tk8p5v3ZfPT79mBjRllKSSSF/ncVS+/c1aeIoa5O4Ol6e5sL44Vdq2eVbqT6SaJZzA+bv44dVZjnx6GkBkqQvNQB0R9a5n+A9wSSkfbry4ZWuZGeKpPVVJEd37sgCCpIymglq5DxvRKtS4VE1PMebUQCmaKEpWDhGk0Lj0ckEbDoHcmVqNAEp7iARed3N+b/8n5F9jSVE66OQmPyPSmGQlpBGJ2mbbH9/YPMJSO3/kdF/KA5DAHnQXKYE1G7Ko4kjGxhHi2TPtg1r9yXqA3rJbuO2qHo8+XrsXL0y7ar1lozs/jFlR2Lf3byyPnw/nJ8sRarivnCrEhhiVlWa4UGkyMKGAQNjJcRHJ6fB5ReEnmgXIeXWVjNBXLWvOlGVZocug5/48QBmaDPa0ZIYSGdbo8jyRs3K9HaSPHisif9fEM8KXGesHnf638QUFJXGIcw9pt/8RqvFSdUY84DjeT7o1lRvpr7G9d7E+Q6MTkql4gyFo589SxJ5/OFVZ5MkGeh94D+5zrajy/BhOCa3oHWDNphF7kuVI7o57Rva6rnEh8QCGSHJ0UI9AxY/471Z6zSR0CHP3KC9gIG/L5aAWJ7/o3l/tiI7ipImvRjh4Yk6uUV+kaujYqy6XDnR85y9L9H2Yo/we70/FUFdiu2PNyqLTgR8TCnejsHczGmp3UtqEkJlHvKSpJkMpBQHvzPtQUjMDQ3MNJPxbjonmCZ10RVb13cg8k1C+s8e3SdWxbmPBPsj1YGQUqjsYCotPqTwWw89Pjt3988yXW/x0jZiovG0Ae29RDkD0pOdIwgCNe3Zy/FplwXxNU8eucBLAc6proBgCDqaMzcYGR/QCUf7sIoxkNCWH1xO/WYCXH1F/JT4k2lTjot9BcjliEKfwgYyRnz8SegFfPyuSrSSKmfPidtC04RcEAY/pJX55FhZEKPbhqsQXWCXjlohwYQgkCEFDBjRcn8lqzrl4CJNFeh4TVBaOuQDFp2UW2CVuXVWt8Ow066uAupe6FXNK/N8jviia9RmN4zam9xLH/ypoMuZ7PlHax8XTkNffcyTDF+kNUVj1AvQJIEFSTOlKsat0SjEHegZLB4PTup9LCAAQJU+vU8ErOWL2iJg0JwoFtrE+l/0EFvrU4kSAVjCp/uPEF40anCzQiBCWVK3q/gKcGQOqycVfGqKuhxdBCV1wShOesUaFZpfdIWjvxkJc2+GSn9RPHTyXT9Qi3tb5Kk4vopeUEV9DIB7faa/5fVH3PuQRJG1EzFUqe319OmTG7G55FozKMaFTJutO7/ohifnM25Mmp0xA+vbUTH9EqKKO3bhnrXWOLL4rkxDl5P2TFJULAZh92FKl/I0vQMS4d8QLgpmJPFdkMzJg2MQBgYZdmce85SV+H8gWLv+7GK6Xr8wHr5nnCi/T+bpdNiC/mqxDb43otlW7RR3TbeQisD19ZOb10F7qRKyu4Hzds4g4yUn6B05M5+cSWX8KHWLznOIAT1oV5+6lvE9FV45x7BcrmleKSTXYTKRVna6N11UlXCCAC7in9yQPR08nTRdlN1NEfzkVzn8tYXJQvH2Cca9UU8ftXQepyWV/nY8f9D03u7N0AYeqqzwukAYXhjvdb1dbffyy5lxyTuQL47c46xSYFqbs0divAGDigmVIL4PAPh6ketqWxBcp7wDDYy7R4Q+SEMln4ajZtQvAEjNzrSMJhM9l1uS6aitsqXJEwrv8WDW12S4OVo+nL24YOHBfMazKrSZgNWG/UmLKqP5/rFTGmt0ljk2YMMc+lf0EbTj0GI6FMcl+KKzF4tKYRzykuIeHmhCtqIykBcD5CtKjQkx5VFymbBhWzi2IuQKySI+jNccbc+C5aUeFa8yJNckPEk5awNMT5yxHPte2DKyykobMs19n2yd1EOAiD41JtZ/fbHTZkxMn09YIflBupUE4eWLcE0WY+YMJxQi+XBrOK6hLoVm+o8XfK6xJTair6htjrtlUvY+/3jfgp6Fop+9vimraJr1bpzzSLjnHQVdSyfuPNvYJ9OzoiyyJRI08w5dsZZE6ITYNIG44aKtYN+6RtLVfNwAmdP/IU8/LtyKA/YDanlpVO7SlMqkz2SSTHXwyYCTKefnxqtT1G9ds5UtMkFIHAW8Wgw4OyeUY6RpSNazLkJpsBqM3fhksVHbG7M7OhdpLYVvGDPWCOoq8iPf309KcWfo7QbpqU9r/9jQW2nM6x1LD/lppcNOiHRL3EbHt63OV06NS6cCAG4EPtvfLCln4BucUXMxvov0bWUuXHe3d4RTkVWPqeuApMqT2AUhmBVRgA6AaDHYsofwjO6YkqPPlqsg+uRGL68X6og1b1dLtbtsNB2JLs3T/d9HEhDs24DkAcJKIX6O9GNnDQsubYvtvttLrDUEL3T+UJVH96bX84K6xaMxDsq1q4FGuNgppZ1Qqc163KsGuNENjw4Ip38iRGnn9Evg1PE/v9X96kzupZW7l23sJzew5qzdGo5VY6pCYigPFIuQ4cDoGuhw6T3TTGlSFJHHaujKCT7+m3esAqD4Zw8lrZoZcNbn6egZ2d+XQdp843dFn3ujPvLNejIcDkEoPqvHv0atZH5Zlu3CpJmNfpoVf3qM1YnzlEAcAhCVFS6BfFmFMuDI5Up6d7PWupgPbFnswELhS3NIVKcf9/nIszxPwRDUd8no5WcljPD5tvFl6MQgxHdLXm1k0XivD3oiWUQg5Yjv7uymazi9ic9eFnysDUpEGoG0l4aUO5TDWVwSsnyEh2o9ZXoamiGep8C8lAJnO532GezX4RzuEo6flbD0BH6PoGJzUss8lbnM/Bdw7rFPKqfgoQQrONH9aO8nvPebHRNeA1jpwfkwghKIq4sgM585F8spJEUN+JmcUQ/IsbzWRHLwj9IRo3tqNGF5ezPaYL5l/gX3S+QMbTiUko0eJ/SP1gyN6IPAmKFsWOmIGC4rNfKjBxb9sawA31kOGuz+OHFdtUwGJsCN9XYEFNF1JPw4ojgH2NUhVN8cdRGKhmdnY1R3nz4uwuVON1DXrgGkaMYb6kn3mTd2IbgkhApuEKlmaoJV6c0YaJi5sAmOr6ReONnKOb62cZAJkliRpmjPq+jYQG2nPdw+SZoKPgOG00P9qs68ytyn3Wser9XsvsxLRNCWne0b+s3rEaL6t6whAthhNGUTuzicB0V1u6VjuG4+MLyVRG8/hJatxFd2KIZXUbGVDgHyuIKEqMZJBnfyAdcRQ5OXqFjL3+3FU1pErLjuWZTQiG9fwoKSc2ElRS+kUOERmX6n1f5Q7Z1HeaWRFk2rrCvYuPB5WMxB5xDHO9ndBYetI9B95wntgU/2AUziToSoXhePLJjGNn1XHdf+nOxgNOGGiQvsKeJZGXx9zbwqZ7FUS9AKcjXoAYtr91BRsHTbAijuX7/WZU/aF/XOmaVMz0A9V+UuH9re+RB+IIaMoqODMnksKawr2LDwfbKO+GSYEZy0EMFEDgnCBMuFI/iRfGodo99zJ/hsLMKIcfCxGlB5yfpvOuuG62Et9hU1KAWGR5dR3FRsN122RIDcP9F4Q0H0esXQGxmZ9VxAnRd29vsRFWQ3lF0QBxa5BIIgxzV4UVHHz+5BcXzPdRHzgdvAOYkZxBGYYcAG7ap4/MDD5c9jIq49qd7GzF3v6Nsj5bqLMzMz56Ta3g5AEEIRYRVrz4TVpKpdYqDoskJof2qYNixrm/u7Ee1q6jqwIGe1m1LrVrUyvM09rhZkekH3x7F1+MM2fVCZkf1czXLKhdtw9fQDMV8KaPrekZ3zUXjB3CWZKn/x2ChqS/NPrhjR77ERLUJZdWfRw4gmjeQNOYZX/qSB4e1hZKa/tevWtPfe2qTawNmGMd9/7Jey8vmthhO3n9sH5U/MSl0Goj4dY+akjl9ize1v0QNED7ElJIKR/a4MCU6/eILSzw31sZRnS9kdVYXB24I1piojhe83V5uVldNzbKq4YOuVxzNcfBmO4MxURzFeTV9Nqp29Z9deVAnaDdT7KuZEubjI/FznuUFDfiYt3wZo+++xKq1yBl//Wc0/b0XPzUsXIPZovBuh0QssKeJ+J3AZQEU0N4TZlNIcC8HQ5MSQ6dVgYhzftcHp9GJyipP2cGXqX4iVnUjIH0CXL6huDzdCkQ/TJBb9B7ja4omDwQ3DftNZ8mFGsbaX82sdOmyNWaK42pWdxY304HaJA3+ndnwxKd1F1/c3r6s1b6a3r4SpRjvM/9KTPuvmue0W0EcwDwVIF5XpQkb/8J5ijR/AkRWWLifvlzwj87VBaZ+8dxqTX8V89nCTe1pkNzXA0vvvJe9rhEn0z/N+5CCXcPIGATbXgH9IgQNsE16/lzap5Zpx5PEyw87Xupkr0n6EFcB0C/e4SrM28yokpNeO7XPWJtAo5lZ2xSitPduBDGC/BA1OTpesr+4XJSsHQemFdz8veMPObXpKqlyNHHm9USEUgjh/tYEhWKzsA+RXJ2Roa7+Mpy4E7bINXzfTDYmEC58Y1yvXAB+db8fWRWvpcXGLkqc6AQcNsCIuuz3jK9qlNLTbKL0rwyp30wQ0tNK9+cucgg2Tpyll67TyndsBsJtVD3XW6Cf4HLXCaZIObSE+mbjgekQIjA4Wtejv8bkeqzWJR++x4QX4vwujFid6y+k9uhaHbAjSd0yym8pBUfU1/7b9PpzHIjYFdqwvkePcMQrJrVE1D1cs4KMHf3dinIFtxcCEEI3JNKMupOs+EKlMLF+w6EwAUgE+R8OUNTXumKsOK2FvWe+C7ApY19XtZpzO1vzs4r9mfzimse6gmTunuyVDNtXcpWWMElhSGtbfZ/ATew53EKWonWJ7oVnjymFHCtbKIK8xpo0AdvKTjUG3Pcs1sY8rI2+OpK8+sybY5RhL9J0j65c0maV9uowI6HN8VWNb2MrqYfdHD2rGh5dfakRX322TTrh1RbkpdbUixPY+MzL5NUj36CvT6D/IJuCYnLv7Ht8zuqKNbarx6XV1CF9+1cab77ySdF6tFpdN3RlgDwotubZW0By/0i6+qKSe824SMDs0hy5G/OdwlU/7Btdt7nhs7qV0/d0FPUMs1saaNOOr52+et+be7vbvzyyoSEpSigC79l/dbt9dp/bYdeqT0cfsPvk//89svLYskWrtuxZVT42emh0rCLTb0lyj+l4W6ieo0pEBBM/+toc5xN33nylk6Fmx4S2Zdk2X74xeeDY4slLLy0avbKTP5xEGpyivElhvY+8ODj82hvTBo//80+q6rz1oRV0Ah5Bwu+ABmgEWOS0h5FS/sn8RNqjJJl/BEPzU0rmdootL5Rt6ctYLp/g8v97bDDlO4SK9q30f305uHkqxUR6iuCKzMEhhGr5RaWbd4bhjCoG5CohwXHKRpR3AONPY0Gp3jcmIuuM7D7pgeQZ/1U2V4YbK+JOeyIphbFYmSThRPlX09o/n1aTdeER6bmJ/4u8E18c/y72QtvHryZus9m3JcYxdr+4dXZLbNaeNtkbQProgZKekclv5Jvi6Ik/5JemplR38ecLmtIbg68gjyRSfGNlm+ZIn99+4YmInviaUmpi470DVS/WNNDTCpncVuKkx9ZQHotL7WXVNIiUona+KY6t/FbJNpn47duvg0prGCOUluGpO6XviGXh8ylJXyxUpGVVOez0rjESGU/5dfaF9UDwxXrnOlOywCfOsr5g3ydZ65zbkz7R49b1roisVZhGgrbgKDLqudnTVJxkjkqwvWawJmcwe0navkFw8fZOPjnmBcZ32gsxZNcDzmFDepgx0CjL8FrmVgzycfPnS7GnnH7OwGaC7Orgc2Q0Ujx/+t0kqi9A6cUcdA5nc+yK6SYTj3jAfCGG5tzBo8e8wPzJ5FmI8qE/jV2cxWyObdygX1HNjTOneRlpFucsw1Vmn9kCoI5pvQVpP/JKotd/3I0zznyV8MCg8Idc+x9+sNUD6hdfzPryngqQLAQxI62krzwzLVcrq6bP8jEdiceXR3/6BlDlhU2EDWOkF3MixNy0aY9/1vXo0JhU0oK+4glESjVJw8dTRbfFw4IccupR75ZZ1EEb88va11Y9FRqoVXPAG7PwyCm9Bs8JI5Y/+LXy0j9hliCH3HkITj6m4z8LpGzNiXSo2U6wbMxIF/f8kwDhK1M/fHSobbvoGYGZ6nrP39/wytAVzE2LI7zoDSfOizKwzm5P/Si8iusQGPewFqev0fE3/SkHkiNjEoErzmOPJ08bx/oyZ13z93dq0HaDRm7dh70M2495AOby2pq2PLqv4/5EoKoFOVS+PyyP1GmEmsf5DwdSasxRPdqlJkxUSkB8v2YcNyGwuvW6GCASJ6wR20kPWZWvksR4YaA/dlWyO0y0B28F+DOqDTqDvT5m+XbeylfXMVQqJANAuLvaPQ8L92ApzcFB8V1OvA285l+4puWNUTClfUIUb7+Xdr7ImqTSOdx6ndP+dGJifIvTtjN4KM111d/vL5jiDjZSdikUv0QyVdfjQQBnp14cEy4XAJqcruHfyWq0XyUwpdJhrhHVBoCqFEX1hg8Oi6C4aHZ5SSevt5201Sib5S/UwIGTmErmayUzJwZUlsTQhOvBP/3JkU6X0PPjzXg7X2hz8gU2t17nMn/X2CGNUPtUUz/qs2DqltvkJJd2Fc7zpxz8Kkl4WFg03tnVDuCnhW+Yd8Cne2RaEHa+5I7ELecdg6Z8m1r29fn6mqAlyobX/+iEhnmkFBdgLLWCUW9gmG8ontQQrzzXNvOTh8XaKZu0/VKsAZrrFf8u9A+4Jt4cp9T2/4ddP94yac7p6DWujxd98VygvyMqUWWlLEO5fmgXzfVez638wsKZQ1Rw5TGCusXl62uGlqobn5jxO9LPE0d3PSqd+zhTboXFfq9QArfLvL0G9n9dyJ8ufn1VDe9JkmDeUXyp/H9cvKNbcD9cD1Zq0zX5I+kcKTOc0mbrlR222PKmM8ckbCT5NhKHxjgZaDp8O2tKnPTL7UyrGIi3JUU3VVs3d5G8mttNAHTDRBT5P5Zq68Zy3XbnKU47VJMk1R1WE60k2VmhpYOpZKWWzZsZ5/iYP4upaJo+2aSn5w5VDNNVVVen6AYZTIRcy7osH/jefkUVwh5BfRQh6vjcdqPjqmCSoauqNo0vYJYJbRlzizhDzMPnMlUxR1B38KReGFfqpMNj/B/yC9jsxYU2hTZEslqnJCksbiPTUSG3gMSfJq7u3DZc0mXsx472+dvXLHjmAEy6nBqzQogNk1PfHegRda8jytBtUabnCA+tL5St+oCV1NqmdbNJu0rmKa3/N4yP5dnFeyectRu873/z9553dVkzb/SdXP7WGkxd4KzhQNMggOlGMGxZEBj2WFQ+O9Nj/9eo88VXLZhxxE24VcjUHSgGtXRtzQIymlbDayr8yW+XSGPk5wYfdH2z3qj3O6VFz65xrwrmjMXH5X739781c6CZZCbr+rru7Xe0uvfeBPGdcQmLH1azfhUA/ieI8qovxAYz49ysZDTbDT/cRaws8U6pXPieoSED4KFueFxA31Klpxf42KZypRqMF0B7isSyiC93ye2KiLJUlgTIknBRKhGSJklHBaOcxM+SQI8ndspaN94QbxwYMVJvzOXRpaE9c/Gln3Alipq8YnUzsLPKnJJgggDt5TNe/EgqkpeHcpS1knThHns6wSrp1KlhJwL850hi8ivMv0TcR+5XGgEyesDdGemp8lyhSJaTljl6NxpQl7rVpfm7NK4Kl8aZEB/4JEyNkwIp1DhQ57fLK8J5yjpJ2iNuFk8SsgjQ3l4D9FR1p9OoSrY6MghWKaiEE/3L+wc0G1Qu8gEbn5QiEPZPqRd3CjR4h409DZBxL0lX7UzjJb1F3mVjw19Zfx5fYGNn/kzmpe4Qn7EcftYGPBbNBH2R36KZTb8HsGbzVcw0lx751/0Q1fcz7949BIGv4oeCJAWzLNUMokKQDF92inGFgXVn0aMLvSgUv2uW4tlxrgNwNuqd4zZW1z4idOI978mS/acrxwli91Avuo+vWvfSnU7L93BX5eQKdr4IlZPHjpFWHdnSSmGBR6GupimVhiVcjt8VG+fc98d63Iklg9MdCe3WNgeZ37vjycNWSyvFkJeIdy3Lv3/rht45VQkSuVHPfR2DxbTEd8fdMu61rWmyS1kxe5YXqOE6S9LyH8crrhHoF626R59c6krk19KUsA2DnWWnm4Hd0+ZvufpO7WaN3HieIy9J6l7Wl6W7Fjdr7RUtocF618dRoI9fKiS8Lwc4Itebd/fyJclSbirYjOZsaOQWRPJNi1LiHWu0ZuH11Sq6lApld02tslfxSugB9vTCMTQy4chSPOqznnEsxS7zIeU18wXcMmH+HYAsfyHfq+GIiPZWQ45kLUk9PgMC9htpTAz/+6fw7Epi2ZY/y6f6BIO/pVOiONpB8NKfudCwMrLPCAPDScidkEFpyA/pSxtB/rt/EOvDM4f4qealxuUYoRV10yCw1E09+VkGwDhIOS4IFm1D/oEo9aqEzihkTe0aMjxrDVDs0/aaNzNYnYzuxX73gbRwx09NgTaYlPqtRh68QAqNpad2bo+L7xIyas/uu8KYIAo23P5zZVGy+dKaqhckVFqhmltYVxhgOixrhbY37J/W3bJfBY1cQm++JaUhsFXAjZZdorhWu2G/9hfsDGPg4TnbJY6JKdCizSZa3zIDKSW9SbuN3BdOdLk+lL06N9zoYZ37hAirThQm6vxK4IzCAq/OWshZG2+HOyL/56XDGZ/gqS2YP1H3gf1Vq+0NO+i3cKC3IP1fuEOTdcwVbXB6wp7COg/52eVOsuL6F7LepfBFEfi6klwyr2Cerwt0+joBpy2jhROXtLhKpiDbgqJ6N3naoJ3S9V0C//Dx/XJV91A+xT4oQK/PPnPqsx6GWWlH1Zd9XIcjr+8J0AzKGLRqXUcezAqjNpj02dQWhZNB4yDewXNdKaOqNJpDITPfyzqxp5qCwTaJpj6a2VEUl7aLhFs+CFw0Vk2JtPVH0IzhF1DWP1CXhwiRpOkO41u+JnBNz2d/8Vu+Vr5XhQUiw2A5hlfedR/eyxVdhWFNipDT+CyCon5uiife2DSTzBgyNwuPEmR5+a4tRoGbR737ZAY7DuqHbWIY+oYtaeu4c6i9JU7TyjYxHYa+90Nn//9W03qaOC3uucaBEFVRb48e6OgiYXAQLGtW732e5f5ZgA1/noBHEB4k3WempN+2xOXrYcnyMybZzBUOG3tjY/q67XhZsoKyy+K+yGUf0M7QQ0RxbIzev7SIwk/ZGlqrzsxe9MyYnD79TzlJ85OGj4V6KlO8PwN88wgqejnD/0jMUWqGTIA4CAjTzSkqAUdAH7+/fQ3mOweNmY/zOT/JNg1+NS2VVq9y0rvYv4mgy9LoviDXiyMAcokRjpeNFlKIYbGPtUUjUvZhZ5Vq9J5I40vVKp+VYSDxuecZcjSbB/XCqMNkBg3y0rYFZ76Ls/5r07YDAceFU77yyAFqWip9qizFDBO3h1AKrU0UrUzSxBtMeqnp86Gpt3FuxUycgalA1JUW0YQp6uaTmNWdo/JFEogZJAFSJqmDKsZ9RHAPGh0pEkuEMIMxncaYEleWgZXzD6CS7htvWEDxvi/kfQshkAbvjmE5jgr0BeoctEqUCeraOSz3T08ihDQ6+nztxsoghfrBNPa+62f2JBn63GX1sUDGxEuN7kXV18NK+0d9pKwRdZMl15pqRJMOijJs8TxsuN4wZKJEIVpXrkzSlq967gr6M9K5UhRb1c7VosMqMizWHTZSxrJuIjYfpctPJ3EpwWdYvrBVWSZYij1bCsUXVVSaytlW04hT1s0toypRz12qUKXTpWnhXWVCf+00P5c9ok8uyK2Tl9v+3iKWQIsafb78MSU+oHggfR4rlGwpCGUdEh1vq/jFBT67IxBQF0gnrguutW2pI+j3vfhdQ5V3dFGnaTGBtyxj//PWf/Op8d1Vymsqjnl5gcLVsWhlbf3Q4nbZz4j2+EP0LcgxmkB+iusYXNpQs2hFh6tAscLMUV1TqjLknFoe+BZCoVZT5+SIr7rh+9e3082hW9pOJ904Ql7gvEDRQ5co16RJ8uvSRel1edJyjAHuZlZpZCjrpNdR3C3K0XQNGNgQRgTm17rgqgxOhaEKJi4vaYDJjQwuV1rzi29gnaqzSWVESo2PJhc0O9Oxky1N2oyaXLiDj5nzTUlJOHy8pwj79tju5HaZtC25TSqb/61sxto87O7AtwMcYv3UlUgTgka+FdlDx7BbCR2bdj12zqLGnsMcdxGIVgLAF03UTU7XazfQOHZOyKBekwNeZL7QAm+O1aIMiQkI9Qn3BFzLb4AtHyiuYOWpXhiQ1AIlgnHensuArG5aPAfGdxSrURNLMoB4lZIZD8SyRLyEY0+rXrx4DV2Ai7vsCAYmaTdPmKa5Q9rJE2iBIxelmBMmoxWrEgNquVkdGobyJwqzBIYDAGF5OKCINOUpzPzZPSG8di+FgAk/hDAcARBgH2/T2d2o0+NOrVs7rWYAWGiWAUHoGbfmZTUBeZ9cs5UKXz43MSiq/EdTSfw1CuCJDiCxy5/9pTK83zo8s6aoch36cq79SnSGzszsS+F+czChmGgANHmU8YQCwXjJgo7D6BHhTKZUTiawZKeA3ggB+jfFJeyzdEZzAGyOLs+2dQj7Eh1ETjnd7dQcpNQE+WQL9dC1T1uCcbeChHGyE/BxqAxO0H/BIy2xeF6s4eyGttLI+hlWZq7fUCzwwVHu6yPK5mNJBVxKrfM/T7DWvbpg/8Y3b73CHAoBPOCW/5PWBJ7t7y0LVJS6xo1YjIc+W3ks8w8FYhDQxdoV4NoGLEFv+GG5z+IdEigw3Gpz0+73kWsWPZHEuXRphYWd4IIOefL26WDsy9o8OLs+nkjAx1jwggxmMknqN0fogyuIcD2Hhcrg5f9ndbdisW5YrRTuaSv2Wja9efeKOvkcX0GY2c32lFiNZ7odXAPuhVLbmSCuVbeG5YdXJXntxTnFj8qyrP5BATM03Flf1DGlkWIOP4+lGCB4h/6vmANVFv1xegTpt/yJRO6YCkgTURn8yoA00tKayo8Vnt3QNh/Ork9gi/D5NrvXyhYl+PXTdKzO8zXZogiLZFPaPzSy4Ce3u+zwiayeVsSi1eXHHgjAClN+KtGK5E554ZcBtuzxzHmdPJLxDAvVMXeQVgdXWegrC1SUeTr4MKXNVh7zUOKkbFAC2Ym9KaZa0mC0oItdpHRzbfMS/L6IyAFfCoa4pSrqmNrE1QhptkIY7k+QQRWF350cxvq00+IS0v95aX3jCNZ7cRUbE2+4DJUXfAtyGLwtiqXxnkTDrd+GW7LYImx9da+FIxIHgR5MsrIdnUnTdMwkLoWq6ZFxHUlji38q3MnR+Hv99ml9LTBOIRWpWIfKelsRHGg0uLVjtcx446HSpmoG9zRg6/xSdZO22ueOu0DTEeColi5PT/j3aayOO0nTQPnrObjxv4xB/VZWpisQHIZ6df2ROmhONlXBDKi6+C/4AZmwaF9+kpJf2DVVA0Xzv3kV0t7tGpAoPQl66mZnTEvrF8dg18FZDQlskdCr77UcEuEZJ1N1qp/AHNClUyBxGdeVwonmhogqs7qziry9McR0MWorW9cujZfN9EZ6W5D4GaLZPSPAthw/9+MQKAc6aB76Tj6ppJ19fWXByhItT6bQ9RkJMigxjFxbkmks1Ravc9rNkyvzv4+sXNVAgs+frffjDwkYaipZ7wjnB8ShVkJVhR9dc7XisQwziMhIj193LYzAKNJnOT02IX3XCcos2qf9cWLClO1QNHSTNnQt6b2s4WkgYPqbcDh4BzynQeMLq3SJCX7d1IRrBZVdjLTMJbRf8KuSGFcGAl2r51rAzBcVPKIyx65U8aO/z08KT4hhiW7JQAwqgJ68ia7CfNZHen3CgLdrMxJYMIIrjPiHrIECxY+6awAbLEluVXT2Rmx2HJWF1aJxax89tWudid8feVTqXUgIBiiDHDgsyi1IyS/q0M62iZdk25n+NSA8IQ3trmx+0m7x1nfeIigc/PrKEKn30sdSFAroAYPxwwNu3kbsysj3oWZSBaRcuaoyVVyBquwWnrDRA0RSqx2URx5tL62kWM5QN5HrV0fiAKjZmpBM493sysjH4VlIWpKzUfhlgJA99vf4X/b1tKCXaFgPZi1O7ZHyh+e+dfqmZiB9HOgNGNux5hLoTPNGHbVxaVsyd6cRY1CtH063mXVOi2WyOubhqm50iCYDpCIOtci72HmohLqNIn1W0GIJoDyjcidUWfj9JBpHXasKoIYsZe78Rm6KginSuBNK4Abyd42PzJem2x/XSi4LUaQmz+wOR/K/eWVoCWVKmAIEhIpIIIhLX1Nn4KsYx2698S8s6BdkYvEO3u0i4WjG8DpDq1G1ZzW15iW/LDx4WOOAOwHNpzdA46r3TNaUqsgt9fEKaCrMkzG0UqCnZhkquXxT47tOcvkdF5nI1dGryXHtz/PLIgGyDAqIRSB2uo7WMRJBySlE69Cno3SlNomMq+PyUxT8vCXroniffy2UEVEnDeRXuoR/Uk9JVMxeqZQmalybrp2XsyWZ33PXgcwY9M7XfSO7tvrVJZh1g20ryLXN8/zRFKujFXCTgO3PGf87cPE3vZT1EgvM+82XMbGC4B0NLQouRbaANSb/ys4w1/c7ec/c/2R5+yHTjdfYO5Tp5gWv772vkS0x6xReX0L4VOQwHadaXP8TSxiw0Ybv9oi8YuhhmB/cmkZAj907/pZQEXbmT2KPapqWxcktfJW+imVKpvowG5m6O/8m/mbktKlzgiYrEVAWBgD80P2htHuZ7Uq8uOuX6rg0x6tmxo6rt0uUs2to419WmgKWmE9DGfKeNOGFDmDLmTutxv6KObR6KN96E5z/gqlYqp7yUQHaih/FrcRS5C1HmPrtsT+Y+pEtUUTpPtb3Iy1TtRT9YU3TX9O1VrqSvcZWUrCphsJdQrqw9WMkCpcy7uasxUmRHqk9QpHtFlx0dVsKo/jq1nMU4eJSh5CkdZLULM93+spgL6+uLoBn20FVy2hbseeOQYnFuj4RGBhc3gng6uaGGJVFMzqWWqsGmn4WrUfpp+v+2dw2vy6lNivdoioaqPXuq1qlUXG+PMeYuNKOy9Wc5/2hx0S1Ji2GeWrR+DxP0zPN3LG8T+ZvZ/fG1Kaweq7fQ1C2jnKhOrtpkp9XQBPzvDUUTRZNL69ZlZ1ShjJ5qqhxhSUXC8tusCyVleU/S+GpFpvDNVN93Y2k4Vsearjvc9gEdHqq08gLkPlRN+ixzqhxZsV8AUHVWOS792d4XnNAkS2TwD564GtBCJr+MrCtCN7BEptURoG1XnSxlPJy4SWgtP2y3GyyFklvinihXXLz/GusLFwn2LfumulZ35roFA3Sorv0ExpZg4kUX8Au1rZ4swzTJPZcRRu7QiB6mdmp3w8+wTlw/qRbfG5ZOgRgyWdpXET3ZC1Oe59f6YdpTBmg2l/7U30ZVp9UmCRlX6EraxS/n5ni0mR6TPv1afzggYmc2fRda8XqGKBUdjYwP5X01VR2u8QHPWK2WIS+QDXLS9qeNCSRn1MSeRQ2l/ni6U5VLC99Vq7+xxyukoxZpexTB5YZ4t4DlJbzn91T+/+yzjT/gDlz3JsG2a1XatfGed557ujwPP1c0ERP7aziKVgJCcRGkvyUTTuS1ZugPCLku+rFKSdn0D0uaENy7HclpSU8WxtqrgCUsS19hwrF92RMZCkJOu+BNS29rJ4ehmmug/Z0f22S2549tkIVn2+SRMVyR6FbeQRwH+uGrvGzB7/EotkKepySUNCdWZtWAo3wUmoBpYYjeeIIouKRxBzIdMTX6DW8qv0ew08/H+KYj7sDVp0lVMkPis2mHMsKm1NKnuXgnueq9X6wRg85qEGFLyX4MMsoq6eXv2mm5oxXpUVqE53OsJXKvVf2b90H0p08bULELBoQyJ0F4FsiRNAbDK1cNSpVbkRMRsRQyzWjASDZc5CBc0BWE/nABdaBQtf6DW8rsANn+GfOu/aaHwAiu20he6F9NDnCtypBNlaqaCUKmBLXb++fOrvSg6nCfhsGNfXH1PUqAlR1knr11wbAmG1c07dlYSbvPPqieiugVGsZWzsCY+xH/otHgf4X/Sd8rv4zRsJHHLbiC3Cfkfo5VU29zoj/lKGD3N+sP1p1kl0SgC/7dol1C6gbMse6zmqS+MQo6ebIZFtnA5N/w+q/kvhgDx5DdY6qC19HwRQB2HGqhB8tlxSn1WcfG7dn2ov4Rby/tWOpubt42mqbGOQ+3gWvc4OGxslM0CSA1iwp5Od/MZ675ItKn6R4uIDPm3e9PP+tD7bNyAiDU3925a1t7wh4ZdbRfri3SjxuWEurqvHjDAAkJH9WgkHzLTY6ipx0b2c8z0kRwaa8WDMNJzWRTnBmYmUcnmS2ALRPNcfEsuVMedvKm89lOTN2C5OHN9DjizXXiBJypNJgEkggH+N6mhU6YGB63uVTfEvvs97yiHZBKLaisnKwMVHYzmCvf/8Oj+13sVknQNs/X8fwrEtrjQTm7idhviQTWhl02FWwFJdhp1Rdwr2Galy/Od9N6tsFj5cJ+R/Etohr4M+dqozLChqhAehKRVqa4a2Z/9e8RqjICBLa5YlgfpqU1LM63w7uRjwQwPQ+J2ZxmShZ/QWWdMpCbeF1DdEyIdZlejZJWFfjKWlSdqmBw3C66ItUl830BrL66WABncZPEPAOqRPa2ki8MZ/K9v/0/ITSeC3HRHHlcfO0iGqM+ITN9CXSzmHhUoq+qJRzq0twSckhHCPQKEtPb/OG0+rdXQAOqH1iozGv/i03PWaqiL0TQGi9j5OKfCAMZx70hA9HaFqifHZ9ICupNtX0OURmNzPtsNXG9TINZ5nNyvMB5z46I0WeVeiAjdAD5ee/pbrnHsD40Q6ol+BJHjSnklenp6JmSRwmopsjw1T0WXxknO4l5c8SnJRcmvjfyoJGKERxOgbtfbuzyrOycj2Ul0bv1gY9suz67HK33etVB2xXVfJPVcT43Ni57wP5u8siwqiCs/S5cz86/59k/Rv4vP6sxOo0WXokGT7jp/eLK271SjPg/qo348MY2TgJAdwQQ6RBH5fEfdLLZo5SSph7OvO8TMMus1mXn/ZZujMMUGEF6P1jK5cYcRusLdYL5331qR2znjVKithUt9zq5P4raSj4LZdy14M8vwxyu9jz+WGvTpUuC7GiwCDNNy3qYe8rjWCD6GpCd9NecvX0NUviSmzCuTc+8dqJD/T8J39c4/JRbfSbuDpBX+vgILUVilkETbbsRm6Yrcb1fnw3JWtYH9fJWufiWtrghZXUfm8giGjVOkOA6VKr5DjLlLIl/ayDPEbonQzRKpmPGcQ5B/ImrAJpAQ0kbsojBRQWSJTUTYAThoWlvpivR6H1HAyhCJUkjGL03AtP+Z2GnDL54ScUMhD7gsIxgHtcZFffuWxCnz/B1D11KLYbY8Cwo988LR7orMep3XeuDmMpfedwMZbaucegz2zYFin1xwoXISlvxapJ/nsFB8qsdGMh9QsHbC8UDgEHR2RgQVmqo3D3nscOO0xzQqnfak6pm1H1qJN8VOhF48eIvIWp1Px13bvI5Y6lwbZmxPU9yldKEEN9aOrIDy6uCO84Av4xV5iExIvt2Qs618wQoX7FJXykU+kGCgt+4BqgOHjlXx69NWwlmC9FSJfgMv8EweJf+fm0OqiMwuIZCvPsBOEIGTMooWzkz6uCUgN+tpgiw4b2hjOTC9oIMnWSs13te2237FNIJaZKsOARZPMWQkw5gVF7zBwHoF7DP/g1yreSoZFeG6nTAfJYJJ2DunKgq9KHr3iF17cQMi56cafS1+B3rv+IjZCABY+ZfNi//pr23I9eB31HX4b6agJHLi5RsB8TjAlZ8Hr70/8LDsm/0qYUX+igy9u2tghh4H5f7RrWqCpG1BlRCj2+YD6UGh3WKNs61PL2c1J4KvqZQ/4Gb8RxbR0qORSfuMWvYzm0bAtOmr1Eodzi0mucm5TIYvSpg3HaVJxzgecv8QQsjZMXJHk6MnFdI7H/Oy9tz06Ft87nn/8xhMlAcg30qE7CDaogyY8ulrPvEbT2rTEBRsF6MlGSJCG1YS5+P3ECwFsgMHE2SghZiHFCdd+CLd54kXGD1xBhnJaqCfjobcqy4vnsDoomS0NxVQF0tvsGIkbWZDEMb0dpAFNWLC17N7qXE2EqXSA0IeVF0rLm+czJQlXECF2G5vBp0xkTJFWR7lYUuGULzoulZVUDzL6aygCm7IXYeA0fkYwZWaY4gx6RjLoG1tuj6OlUvfJb5Jjy0v/INOTHpQgn+z+6thvFRztNWoB6Q01oQNNqjvMnuGdDBFZ7nOgQ03fH2wocAgSLcdJAhgW6gOlDQLbDkBCyFppMc8AWqDjf7HFNKTfJMR3yTnD7x46mZukpopXexlHPNGuDzAaoxvKdzQlr0dptwPq6i9AZoRvI3PFdk/IRXgrxEu1LrliH4zinJ/WbnxfQs4CMKrkKOYA+AK2pY1z5BnN3oQM4tnNxX6gWO1BpAyqGgpTF1N8jnwf7Kd+S9/dPOLNHEF8gXXi98wKvXVeojXA1NO2u3DBRQ6vX/NJtHWVDR6qGHtneoeloQ8HFmJAFleCDIjAZd3gYAX3nsnDVarSbEzYu9hJF7pEvBTKbI9ZK0/b76b557KdbDXeQwz+1Ggz1bcV0k3HiHKoLIWXBtyvyVwnTuyk6nZs+dUc5fxQNWUOXnDrJYpFhIFM9yw642OEbEVOOl70d0tJBjEbioa12bD6Ipl4e6I09s5ZCMOsrwPQ2fctvzsFOSHMLtnduIjSV5pZA8+oUMOIMoNJcAzAaJyhMOC/6pHtKTyDsRRmF/BiD2VMwhxYFMRU0p9HMu0UrTNoGHA/V1lPzfCwB2zV8ZIB8n2vVV1+g3h7Xqcamp5PDr//A9wAMwXE36ji/u+qhOJe8Iew9wsc959TpMHLeDD+9QVNnTlaHbmvwgYIPmeut7k7OI5s3i3YszwX+86DKLJ7kPPG/fP2Yb0lvf+T9XmBrrKAAQG+GXzaHBfyi87O4pWP8EySU0+9iBPTJkTb970/fpLWvdhkgRwCyPyDGANp3QM4DyypAewhIX0BbJC9TAXEVaP6A+AfIToy1njXhEJlLOYGvCs/pWRS9W36oIkizANWwSAlz3wAilfHz6+xTVJ4FvacPtv+MAIpjeuRly+JwFi6FrtKVhYgGCZ2jU23X0OVm0pt7za75e6ToSdtnFkKK6m+A1GyUhTjYkDFkRymzFEkZomIq8wgA09Fb1/eUs4BSFMm8SrBs94ZtvV4Ia7gAJZwb25z8AioJZTnFS7gwIkyeLKB9A9nQUYqaFKgwVoRau6JgNvyHA0D9YWUdBTYlVa8vgFkF2mTWXWD27y45FMABbMMh9DK7zAGrsRNzsPpiqIVtjnqEwwIwfTyIGx8JzniLWzQs0Dmw+XEVYFbTqvN4OAjpP3oKnnZAv3Jg8bX7OY33we9Q+nmbJorD8z+alr21RtgEjima5MTLFrpvO/7HU/Uj5y/06cc3AggPReQWwN7BlFgXS8vf7u/mtoIiewgtyJOi9agIiA5HqnmHYotGU3LtNxDmA9qdaAhPei1ck45vQAC+luIBvsC9Y58Q+vmx4tf8/ND5H0UELR7wUiiQfu2dIVDP1Aotj3Q64ek+0loMLal2gSRfDzvaK4XQV0/3gla8G1dUANFftClwwcRPcppm/Wnu/S/8kfq/AtMkU/Nkdnn9SvNnAnjDBn/1Y8ib5lMJs/Tb3yetFAxHY9SlmiKsuEWdWboF0T+p5AYsn2bmLupRk8hu96+zoyYOi/poWI1na4Z4lGWOUCtUl6asuqoVZgDhJadRPv8CAkdrELuwtOjzXN4/D4YIFAsMYZ4aCTCHd+WtiTlYhmW+uNY2x76eyDPy3XYh9yhKt41bUFbsqFo9l5RUOJkZH+ycIffi2xfXQVrSASPbn6LgON3tlNR8JAg0ay1v199ra+DcXHGnUSnoudQwu0ad61hippoGu4mtU9vddV6hA4gYNv1EXUAhv6m3m7o3uzQPzwqGR2zsAdoduIse3NiKWUt47kf3Q7SMtSKimU46SG0fS+YNmnUW/suGdxESgwIS2A4bWv0/vDAYk9fzTEDuAGLexPi0lWpw9CmV2aSpKgNdlRRFivjo9F4ijrh4d2nYFb8RR8kRiTrLlutRqSrbk95OnWBdt/594PY9+y8AOv7NBpD7I0rVMrFFXjlfdMlRIfY+xrcoRGmDjejYaFjrGAmERgWZExcsoDWKyy3nULAGGq6DJvzHrwIhSQGZAgbCHFEwgjA7joqzPRiDwEkQd/UJvwFNhg0pY9DovWl4aKBO6KRV+wDQAw+KwH8NjiUAzUzVZ5DDW7z9C2MVJyu7ykuliiUwjZ0cLNyXQCd7SBfQ7KVvsJKB9laVWO2qVFVK+JXodFjclvfHvO9AXkA3uKOerjMPtYojtQgsL341stUeJmXsYnU104JcdHnX0bN4XdZNalHWwiVZE0ViLJaS1Vhn8lqsSzwXlmGd2WuwJv4S74N8gAwZMmTIkCFDhgz7I2Iq0o10bi3Uqqttbs1zydWiYvHd4LJfpKJqumFatuN6fhBGcZJmeVFWddN2/TBO8/Jbt/04r/t5PwCEYATFcIKkaIbleEGUZEXVdMO0bMf1/CCM4iTN8qKs6qbt+mGc5mXd9uO87uf9fQAiTCjjQiptrPMhpllelFXdtF0/jNO8rNt+nNf9fL0/398/5VLbME7zsm77gRgkGUUVmm6Ylu24HgBCMIJiOEFSNMPy+AKhSCyRyuQKpUqt0er0BqPJbLHa7A6ny+3hvD4+fgFBIWER2XLkypOvQKEixaIx9AtbS9xP/Y3xPv7vo+MFms2UARdS6WYgTCgDbru5UAZcaddbKAMutGmXhAllwIVU2ljn9VeECWVhTZhQBqq/AS92pY11vX1wfczJxCTdESYUhDKuv8cUjjhFCBPKtNe/NZQ+RsCEMuBCKm2s8/pzwoQy4EIqbWy3IEwoC1cvEUJuqx1jjDHGHnPO775+NqEMuJBKG+u8/jOkMKEMuJBKG9ttCRPKgAupevdx59zzCJhQBlxIpY3t9uXwE0SYUBbGhAllwIVU2ljXmxAmlAEXUmljndefEiaUARdSaWO7gXAydyGVNtZ5/aUWd/moZwiYUAZcSKWNdV5/S5hQBlyUV+8A/DsTMKHZlDChXMjqyzAQBnX0GUGYUAZcSKWNdV5/x8k1ioT4zyRgQhlwIVW9rwy4OPIsYkIZcCGVbsaECWXAhVTaWOf1J4QJZcCFrKaprPP6M8KEMuBCKm3aFWFCGXBhvP6GMKEMuJBKG+u8/pbiPcIk3SdMKAMujjybJB8IE8qAC6m0sc7rzwkTyoAXa1Paece/p6eMMca85o0VYUIZcCGVNtZ5/btjaa211s8SMKEMuJBKG+u8/tfR58OEMuBCHnUuESaUARdSaWOd179Gv7XWWmuttdZaa621r31jSZhQBvmaMKEglHH9DVMortb8O8MHf//Pm4AJZcCFVNpY5/XHhAllwIVU2ljn9SeECWXAhVTatFOhDFx/RphQBlxIpY31+gNhQhlwIZV2Xn9OmFAGXEiljXVef0GYUAZcSKWNdV5/SZhQBlxIpY11Xn9FmFAGXEiljXVef0042bSxzutviVAGXEhlndffESaUQb5nwL3+PmFCGfDj3xjlAAAAAAAAAAAAAAAAAC+8MTHgQiptfOt+HgA=) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Bravura.xml b/data/Bravura.xml index bc11153a139..2b184736543 100644 --- a/data/Bravura.xml +++ b/data/Bravura.xml @@ -679,6 +679,10 @@ + + + + @@ -1005,6 +1009,7 @@ + diff --git a/data/Bravura/E926.xml b/data/Bravura/E926.xml new file mode 100644 index 00000000000..4532bcd1f28 --- /dev/null +++ b/data/Bravura/E926.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Bravura/E927.xml b/data/Bravura/E927.xml new file mode 100644 index 00000000000..b521b202e77 --- /dev/null +++ b/data/Bravura/E927.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Bravura/E928.xml b/data/Bravura/E928.xml new file mode 100644 index 00000000000..5c744354c35 --- /dev/null +++ b/data/Bravura/E928.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Bravura/E929.xml b/data/Bravura/E929.xml new file mode 100644 index 00000000000..89acc08fe34 --- /dev/null +++ b/data/Bravura/E929.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Bravura/F43D.xml b/data/Bravura/F43D.xml new file mode 100644 index 00000000000..cf48fabb3da --- /dev/null +++ b/data/Bravura/F43D.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Gootville.css b/data/Gootville.css index c40a0ed2167..71fd3c2a269 100644 --- a/data/Gootville.css +++ b/data/Gootville.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Gootville'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAACWEAA8AAAAAm4AAACUkAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGiQbIByCTgZWAIgyEQgKgosQgdZPC4MUAAE2AiQDhiQEIAWDHgePWRuhfiXjmJV4HIBkuw4RlatniqKC00H8/9cEOcZokB7DLP9Lqcpd1Vq6l/YjtE0Hb3dFY7VKrsfNy2IR9sWary1X9Ly+fJPCOMiWqjIO+kUqRRYDCAjEkP+bfz4ncb5mtDr5hjHsCI19kvsDbfPfHcdxdxwHEiUloAgqrT1FRTFrTqezavbCrV05F1Vu7bp/5dY/Kv2/nOYfJ6WZlg2hZZuzwCxpAiU4XIFGrze5vUmWEy2QzSEwZ5FHmsKJ4N0RdLp2wJVVT0ZQu/agSTCLMEq4KQo5oFd/kdPhqtw6DSF8kpaO8meuys0DAPpfGwl2FYzFaujFJFhnH/MNG5Wz3sC2sabr0iUVoYbYeUmHeXoQfbAA7f/3a+/uXVHcQ9UEkdPXpFTinzpTUYsckjYPUYwwN5snIBwJWkzMyWsEIMDcGJW+zqfLz5Qj2/JpD4CD8PpjrJpU0mg8K82M1rJkLcnmwPrvZ8MHFForr0l/A0zdEUF59/oAtYBQtPYGoUrRc9Ok6FJW6VKVeWlT1ClT9uH530+W5d/zEfTFhZAsZcEZhJdkSRaS2RZNNWpziBXBtt3rqgJ3LkneZMx8QP2xtDHjEpDaMR9j4j+mfJEId7kAAAEAeD7fnwHg5Z42kABpA1o4lYpOJ88CBRSU7kMAYyMAlSKtqIF4B6MAhHA3GBsD345ZI5RzKNJ2sgAgVGVgipG3DwEIQgMbBEo0OeshHBhQCeYUw4MFOBxYdGHAXpVOHtsCB9ayFKBwCAdQUVHApoFkNhKXjwMCGhZucZKk4QMOhxbdDLDLVjzAbBpYebAfFI4QQFEuLEVDpQoV+wg5G05lOb1QN5oMqHyEFEbaB4iP3AFNYmyLsJiaEFhFwkt2GACC7HMlbCDQoVwGYJh8e5CX4AgUgI3DfF9EgMfFg7yFxoCH7j8+6hpVXkKns7FR0lhnpmKmWKEUYd3zMZYAgAIebBAC0PXJYC8zKKKSdpayjr0c4jlfY330RfbUpTV9ma/PSR71nA887T7lb5yO2l4n6ot77Vjc9/bj/zFgz7wd1stk8qighjmsZuex2yN+DbmwW6rTks7M0WO1Oz3ldQ/b8cACOg217YE/u5f2eQ2HXt7Y398eG+YttO+4aO6Q8/9WmutUaWuov7tkyVGhToMZVtsIYLuDRgAccdVd973tQ5/7MlCERjRaACTGvBhyM4Bsdrt73Q8gxwDQ0NIJAACASG8wBgaZgs2WkNAwq83uAABwugAA3OERkVHRMbFx4+ITPCAEIyiGEyTNsBwviJKsqJpumJZNynE9PwijOEmzvCirumm7fhineVm3/Tiv+3l/f4KkaAYAWI4XAECUZAUAVE03TMsGAAcAXM8PwihO0iwv8vrl+tVlqZu264dxNp+8E3PvI0iKS/MYvsBPKBJLpDK5oshjjjvhJACAU0addsZZ5wFccNEll11x1TXX3TCXb7rtTnlrd91zHyB4n7AQdEfoKH/YIABKcycy1so3PBCQ1QJyXrDPQsRGkSv2qjp1GiggAAcMWIA+0caDqwCVJ5sNmNAGQBcAgQTM8Cgr7sYBDuCULaA4LIdquEkUMRR1r0gUA3aSXkfKChEeW9mGjKXgXFBMhBlp10jdc+F0jhNgYaF3irAM3eC8nTjdxbLKk8FGL9SSqrjqmK4ZinSrVSUXXuERcmARS97istiyYlKJZWECPjG2iyes7FQ8LmQJesb588inoOaFqy7ihviK2DpdNYl9UdJIEEa1+5VCfH2EpHCCoGka21RIECco93eFOyNVmFPCpY/egFCOcCzCBNOMW84sUbROTvmGXF4kSGew6YwIjU5LaUkDRRlI0kSZYCJTIVQqhERxq0l5cUs+kDSDCjZxleuLyYGYiRgW5ZKgVZF5pnk1GCZgRWAsmYwgGEY2HlChYAiGEChE3V1OKAipPCoSTkEspvUsbaqQmPQypZTLVcVGRgqF7XzmFqs1KYLndKbq3aFyuU1HbCooFKLygDMoqmQqoREz/16wxpp6dy5Xps83wULArLYkMdV2wVXJ49lgVoguAo65SNIBEcPZCssRCYQ8Zue31nWxkX9Q0lI/B/Ptoyhk3XpcQS5znFi8Bk60uN+9Kw2cxu0XqSOcbm/fBhKlrNtdxNqYiLFrtUXX9Z8pv8T0vai31EI8B/yZwtiYOG/ARDB9JPctuLoLsloewafxuURAd+PsD2Z9k0xwt8MJlqx+Ct5xVGdj+0FBdb7f2pr1r9Em6Yf9U/ibaJEYxQzr9GLFJELdLbmDFcmRnX6ECD3qwfSXuNUQU3cbbdcCwjon+hgd7qTnglwgF+aT2ZEjCtPH7GnguhXeT8e3NavTvSz8iH5m2W07o+vfo6i+gj6flMdzKfZ5nTrv6HhfDCmH/Z5RWPgzuqp56QlY2YDrenqndEUuZTEpFIaCMGDJez0A1BFZy0SYCS+yoohSpMjC6fJFIldM900ReSKa5nbfsT0uyu3JvBhwTYYUTVSDmkHrM3zsSpnJVm/saRtyHvJl+I9nWe07kld5dsKS6ut1HASCyIZUGXN2f7mv9dtyr9USHV223ecAEnP2rL5SB+1xlK7k2/obIGridIXEbWqxT40Wzn3wGihfOyXtocRu0yKrJxytkTYsAmBKUJCXmmOaoSkgfWXKi54Ee6kOXfePA+qr0kU7BbrMe0sStPbtvM4Xbf27AK63W6isDoXqaKe/UFFTNh5JfrLm3AEH5SX1VEV/7S/U11QSj5vmALi/9CElf2k5k+W7CZB5UynKPcwL6FmHGCTXuuriy4hcNGRZkk2iNjLrKd66dxPB4/hb4MGKUHAVnNmzT2M6lNlEq3vGORiJGEDHQ8QuFFU4r2J2a+sHHyAYxOSvdVcC4MxIhu3TfCuiGNbnEAQzE/23YSFb1zE/kUb7yGa637joUC8K0OQ8jh5IiRnKuNRAFjM4N/qrfZe9qj38RIW8GkoLub/09XCugDvvlHvrWY0oo+bTweyjzWQzFs9sufb7wN66m3t/sj+68T3ZLm+h3zZ/VHSlwIkcJowetk0DNeIgXjlTsM6fBl1tRSoHqlo9PAHev06SK4coIpFG+LWz/gkdS6fO9efTtZcD7ReMTS4Os4Vb9fKsZuhElpvhE08edSmjCx6yN7W3+UpjWJGOmfg7rjLeDI5WtTx/KFkIccYm/H85QjDDRP9F/QD45AD4xWmgWGQ9N97e7zW3W4/79AND3ifPTLRx2OiN5+J1mnOJ0CA1XMbFj1EhZF1Z37TDsjN+/zojlRsA6rINIPGGWsdF9za7rT7EdVLGkSri77F3tSds29mTA7l4Yej0+dPqqXOnBvNatnjijI6bDhuA7IiqzvvvRV1LQGmlcRkB019aBKL17OFb7vv62lK3Y2b8WV9XXoRwlsFy0NekDFymDkHlsR9Xp7ISKE6P++231tBQqO/xBwhSq/K7xLA5aTFDotDTDuAgnWHZhzSnRkHDpt+ACjDRuLotnFRjn7Ra0QPij0Shq9epBDdCWcxZqqLsAk2cvjbXzMUmwKKdHOg6bhcSQRwW2+9grstjJlt0DXo5VAYgYXtDaYQaiqSkkQgp1WjbhzejC72PMKeZrmGDrtDhbTxifs+WqkzAYiNUfoTEgEBiSpx0/uOEbY2Bpe9WVpi9s+mdz5mf/81x83nFxvhy3m7VJ+aXJEgVPfY8R/YA22cXTlypi4VGO14XnBfx9+ah0ZWQnXHt/fkACjVArjB95i0izZBnzBivFynI8vlI5aEmZBjOM+l2E1SAnupLlTKWiYyQ6k93XUjqTh4tYRgFc5skMiO0KB5AfkeolsDUZboSI6uHFgA6UfxfYWBb6LaVPHZ6ygAK/htF5YSdjPTuuxFLhO5Zkpy0RWa1ePDiiRS7sIK/G5VMUnvrZepsmyHiC3WUmQafTLu91mLr7ErijqbC68zzMGlgELmTRO7ayaraFPXZ8kAt799WSmL2xawx4ewd2Gw5FkeVeVjaGikHXQ6uVnnLBKYJSHfSkoH3As5WwEItYvWnhzxHinQuLXsDL7qFyzzfVjyScKHpTTfTeMjhwOFQYPvjgtuJrhQ/b55j+UNrT7e3au78O1EDnluVB9zpKUn40lRtcvmSLdMRqRkyPZ3NzfXi6vccAkhu1yyoea+XIjrDlDJAVKC1HJ8Kw9ktPctyHs+jxKQ3O0Po3qHPOgJNur00smtpWsWM3A68FJWe3HCDZcaN1rNQlUHqZuKqWoFviYQbG7Bvnx8yPl6inYYuqWuJtnNKohcpvomFh+VNL4vR85OBoulxJ02RDD7nS6YVj9iOzgSyKkuXsYn3eomnnWBxBDS0jYqrzXQj6py7jQGzF/gfb5efGPJ2c2yboMK56bdPKExFg5p10RJFRUX1OXC6YBu5lAYOG5HuHN5IHCPt3v4XeUShG5BcEEuPvjFHP5jIHbE+H5wGl1Dm+uc+eqehIWT1PaoKSE4opV1NkUmZUlQE4aVdhmxMBLrCMICmPiqqaLQlGGp7m2tDW7ZxK/vLSVZgP1qFEILvnbfzpn4y+JYhTVu1brHQh0BdC4qMFFtZobZaOdqBrpngg3OxxkFQKQXTmv9CDxPBHu41TUu6B0g29tnlXXoCpBIIeCQKJEa7xBdxI0HF24AMZ/HAtVqlPMA7g9C4kFmkCmwvWbKRLnxAJVPSFjw7xNfv1QPkuoD2EyqFWgNO3rNfvT/Uj4Y/MInk6g15uSQTIK5yC+9dCNfRkLBh5GtH0I1jvdkV2mkKLesh8kLG/YK7iP7R272rXaQIU6WF6L7nj+Z6wbUGK/45ACDPf3eoamPDg62JRuA+aWuyD96gWlHaga7v2kUptTLnRcUikEc3EpYOP2bHDWHw5DV0cvfJooPAJxBsOtGtaDP7MLADuZPYiglM/zTYaHTgSzcrLAIc69KA9FYdWmGY0B5X6P36agDkvX64T65ZQys4aZTlWMV5MClbPNTtMR/o6RLr89fB6hGY8etl8LDhPniwfq2k1I2oV/tP3d4rFWFa/30x6D7FHCsWXoAAl9YPXyC+EsDeD9fLavBYppVKY6yqoaRMLGmRTozfuW/keYIoUvoAU8bpjZYxiRP/DmbwTPBnCv8XqJ9Aafct9A3ZiYtuvCGLhG4LrL2kpQq1KlN3AT7RqwOdjGmOcEKivf00psyIKgrVtIuc7SNRuyII6sAP0XENSlcc6S+CmrdADcC0jERL5wvdmxZarXdZVNzS8l6T7kkJbZQYLYnfZuRsoTbDwxDnB+va8bI2oEtLLPJfbXu61q/jIeUWbEkD4dpMPEC33CYjQ/NLPQ3OSxgo0JCT/DCLWoWGcMTXh1emL2db+LfmcV5sK+y02DC4cjT4ffZW/B317bMfX1jrPJeE/FTW4KqqKXfwl22k91ywBCPuHy1mtYB8RqYR+dQqMbO6l0xgEO2OxMczQdyuq2pTrZDgRzFinhU14OAPgMB6dt/G2GYECAb6/JLRV86EuUjffkdJI3XKKXvrtrAse2c1NhyUdvf+Ol7gZ3M5JBBE6whU44C+vIk8BHPwrdbcenpfUWYjwehZ3zbORaIbZmCC3Mzg9rTp6euVPRsUtByUGUxkEkX3BYiIeNTOYnaZ7WaWYXr7Iwq6l4nIXUbAWNKi0YwiaFQmInf1tOia6Hq8EJY1RY01YjEsG8gnFKVqxbLXWySA0gcAsIly1Yb+40QbkwmajzsX5wjmpnxU9RXUaAKBDJbsHIzhxTx6OhfyN1a9nuF/hIBvVKmlHonE9fkenMvlswcPcAjBQ0NGghIPiXU/+0Ae8Lk5t7AzPoJx7ENRSqZVapeP/sjXM3ZHVaRQwRhWV3cQ+QUBzMf3h9ZEtvgBgCbnAME7uQ/P2TXidzPSH2gbX/JU4SrOubxcebqIT/mV1CwREMbUbJPM4E+8XlAg9fA5W3GSExsbfY6tM3Fizvp3/SNMLK8zlwWwiJYspYxXX/+Z5FXLqxIZo//vcIS44sLcWLGI2ORhp6iyGusSTO9oIUaIXjeE518dfU1nzLig3u+HTxE5h/1TYkUW8m+9WMO1/ov7Aw89vy/1gjpr2doqfAHz8MeGjBL7u2d4DvHekVV9Ar2XwycM+WnYdwTBcV3bOYktrU5UnfyZx3hVXsaYEmWSVbQr5Is3ETwI7r4Y3LLFcmE9I+1LDIwO/NFk6Fa8foxB+RpEQhEtEMa8We1v8tqDdR5DfrmAoXObnfNQP+X9ba/u0Lj++2noAv6u/OnSBPy2iDUz9w4JHpFj+VC/5RE+vl2hxe3TxquqLl7IU9+XxB6ySESSq0dxYp9ws4BT+Da4ZZiNpQyf08BiFdyymK4YY3Mr0yLmcfjq5yMLfjZkmg2Bajj0rQhKr/v4PC27D0EZZ6X6ZLjHfxg8iXiTw958RjofxY4lC1Z6olm0+W/BVIeuecfVjyZCgaknT7Ajeb7gAMS35K1zQ06UzaEKM5Oqxy0wpwBkp3gXo3pcZlIhxWEXnBu6RX4LlZZXL5ZKSm8ETf3QGDSSvnqA3CMGDki9QVUD6XEJBdGmtHCLeLE44E4tKRQtFkfWN/qMB2W7uGKhptW5FfrTNZDCTR62ASvLG0wlwsRichPGDksl8gaxMgNafUh+QoNB3ps8XRvSgaQ6/bybU7wk/ZrNUqFPSY8u79AeMpzzsULPSmhQOmc4pO2AiN5BIC3o5gTuzvVNOL9ygm/u3RNS5Hveya2ErYXyjeYHDvmm22cnwZuashPc9XGlZSRj4PtF8LNa9QJd8MGNrbfkat0TTPXPcmdH/Kvg8mh18AhZ7u/8ucwW1bYF0Q9kPC2l0wqttNH/IwgNCSEz8ibnl+RYTnrjTf13o7PF/PF/OtZkNE7cVVxgPLpLWr94+aScinlZGu2qsIquGeXq611xJstI0YglOL3G7s6ori+QMuO7uqJDh195o7KkZ92mOjOZPvGY2xsc8GTnIIkvxrs6HQPxOe0ekW5zU/340QMVYl5uZXtyYvmWjECfKjp3qHtR2vjfE+qjU8dPzAzrQrJGS1F7lRF1sbDrvOsETVB0PU25C53gLZeIsJL43uW4/4Ua3V/CEzI8P0Ysm8KIelXK62LZ+V6UQ/ArJTTOxsLl6nCMw+bQQ61W7aOASm1Qa+o4L4Wt+aK+Xww5PRtMY3Gzdewctk4U+bvO8GFQ4Dc2WZJKOFv+8MXrJvjs08M/2wj7ijJNN0UsWS4x4C5dJQ57L5PIuz5M4X8xbOReMEt73GE/nZPY9mV6geYcxu923Jkq8d6uDv76XYqqpyhC309WON/9796Kb4r2U/rF+rJs4ieqy15C32Eq8gYz7hUShd6pV1gqwiNLZk+vquiYm6/9xl3qLv1Gm98xt6Jq9vSSyIpwi97VkaxwM8FeU1GHnvBeVj0R23xZsX5Kv+j4plueZqv32sMP9JacurNvXAgQkfMv5LzgHmoonTa+KzKN7fd/hserU7i4t3bw6LLukrhtDVMX1ayJz+J7WPjizNm64oUl+YAOFaT2w5094dW6PpcJUG7256hgTU1kT5YvTrTaU1S/2d2zh5lfOx5wUyvMrRlLtPVYceE8fbErs81Tdayabda9ejJxMg3+EVMTKeolMfWjF+sCbAZ/U3cqhX6y9vRaev7Ci7zvtnG2pTdvQHT+7rjuuC174/aStdfvp6TGZgYZpBz76A+9QfJFO+/H7lk/bMkvyLcsfcs/Y/u48nH2ql82pr5BQc3tWStHBJ7fT0yUhMauYl8Pus5eFYuuXggwSCddS8MaOcQcxUq+3Ch/2O4Z/JUKdXLLeOYgvF85s9TUHaytyTGMswcrvW6v0lnlSsloSJxVUzpapflwOCHY0SMQ+10S00HZRUFWaXOAdtIaQgzD+h/QYI/G33Te7IjxBQmUAtO7JvM2Pdg3buaEyHiJrllzIZF3OM7dnJWUUuWGFnJJetopn5Q8RtK4l+9c10RHxCROI0jqmem6gNPE0plUAT+36jTnvNO7IsiaERke5QkOXfU3SQkJdiE+CZdy+R8O2KMHPhRRPIXkl0y9NfYXds1QUkZH2VDjBK/nFdH6waWVMWURIVh/K8K2c/VR9s6SYsvJZVFtxd4J09Zvavmy/0to76F4xCOc99d9qXd2vlix0kfQXHPEgiuRFbNpjps9yAkWJBUtp1MSspfEuqt6GP7Xy+OVMEGr/mHaOE9V+vaqdI95puwjg28gLyt9VmlKgi09Ncrwk0xfkVRgyox9KvMo94DZEmYJSIN671VNDM/og7Bu6Uk3l6xqSWj3RKo3xWxSq9J2HXTnuifOmBpTk55RWO5i5IyvfWJaC4w/1T88kdr23n7MFpvoWWW2RUnZ1r8Q8c9arXVNp86Zq2CQyJo+wKFJLCO0nQptz8BImjMAfIQlstDZj0EV6NC+LeadZkRZyRjnMZfump00Mjdp19K0AMxtnr3y7msRBZVh2lvGPH3oOdqW8PbeU9ag7HHp94k5vE9IUnPyJ3w/TS/FPP+VByReyYGV/rzVE4CozsGciwtzOhc60zl7XZ8KBHqnNeY6PREh5GCDUGo9ORyY3eULQS+1sK1meoSFFPmqsLiz4QUVC18l2ScEOP3hknvWnHJB+fu095Z8yBjs/Cu+Os8nu0rcpaOiy+SJ2UPSIRX9pOgo1kKVTKr52bMFHv0DBwObgWGF2GdWfYmPv5DClAqkZAuPF4kyL+xR3opHAn4X2AUay42gcOoX7QA9/QZT5Y1v/0xjy5Zkm1fV9f9M0DRR8fYNXXXo5MkzGwwagJea7nmb+0OTxRtvVygoPxNPfpVLVmpLn/X94q1WzzEkDPjOkHtXLS7Ibo9hU47DmTb2TZ6kUb+CKtNn99hzDJLU6wOwXvOr6Q74ydopdqwnrvPzNdP9Mw9t6tdoC4kMU3bOa0smw1NPvryxdYOhl3IqX3V9GK7WBHBf8TCU5LXW0Wc88esnzKisDrRGWBuae3jPhak3oMfozCGyUfT0YfDvUMMO95ANksqHse/n3d7xRyfFzL8Kr9F00V7q6taniM4ukqJiEr9YJG3YtzeGolKXI2PiFNz6zHIW0jkOJftOOyv4DLVV8ZV9XfTA9+mRO4hK55YHrYW4DznBnEuWkMzR8jamrXzUf/2MOI9H/bHQ4xqlaM8fFK+yd7Ah57pq/NwATdqoXsnDnVIJxmMlenTdUIiB/NJ/tteYh8Rj8dACbOXqvjadtE82vEVuhcFynELvY95xmpVf3YbGG6+YLLcKtD8iltJ8zry0iNzKrBizIfPnBSP71YFKHQf/KpX7fP1hWbEHj9il1ongHsNtgBBavajekEyeBczZTPQ2uwsmhDlYfP6ZdXQVVsV0k8dU9WxaLMytyE+Bdy7GnHtg7LRmZB/8Pgw3zVtvVsCNM1pNPwP3Z4R8TBlLF7NlFY8sZGbT38ANqloTsDATkgVWiVMD0JHAp1Haj97WjCrpj7p/uWnSoYQhcMdnfQWCgnv1YzRRcmnb8TkzEqCH6M7SrM7SdBMyJTtJuUhub+6/Sy70aKazRlBz09sM+2T/8axsZd4ADpxgBq+19cf/LH8CIv4v6WXpZb/wOZc4u1a27ebhpQKd6Il4ah0gYmsciyyovxaQ8dGEqU2HDhPfEuAZ5wJe4DGkrNnYLCMZXkywd6ylnWlvGdOndcOFFMFVwzf7ZGPJuheSBOZmIT0LURSnXKwUBM0hs0kLh8AB5zkNbODt3SDtdjFlXVpUOxHbh5v8wA0KhC70WUyvgjH64dbW7u6NHBwIAmctlQRNfMjzMzKKXvuXYSBdfuUKMz/CcqlGWn140e9qdZBRfq2jUipB2llpjcL1JMx+lyIprNYPoUebBaQD3+cn/Gr6pz2rODzyV6XiwgTJYy0WGyARXqiFcZDEpA3IUPyBLuOvLs2VmEIubaS5ha8y9ufwOZ37QaDiImOYRe4HbaaLeGzRzZaDnM5nsp4hYLjp2RaDTYOxe5CxCcOAEjIXJoal7J5Dltf1qNurNFwMDoko/Oceh83Gap1u4WolMOzx2qczST6ZNJfH+zQJXCK8KMSb+ibVIpdRl9g4M1D+3Jqat7KOzVtBieFndRoJQNhAfV7kSQBqhwiKamaQULoXwWXzf9bfVg5w8Ya4xPa874txiubvnBlz7z9jiDxL00vweJx5+Vx55WuR4SNTcJLi1E3OgBm6S55LOrXR4+efHEMzfBba0pLear6kxxzyBkzVMazbufGBVofr1MSgOE/LHy0c96UtfOjjKXb7Rt/Z0eVnYktb+Qwdg4sf06hWLwW+EWLugQlb8r9OtLTAgmBbSv4eS1ZPSYkvPzY8Zt1vtqiswjqDK7k1MyXa8sAZ1KvS5Y7zBSU1P5G89sBxP8IwO+0IgFHF/zHKoSppaSb4TPD8a01YI5tImq3AQpUqo+rWN6UBr2pkT6170r73t3ir4s0nDKE3lCRB4igmLpXVsQL5zP0J9ugtf4oISjbiytdbY0dlNY+TkivSY3PntGQkFIWCsmTbpHDzf+6ARI+h8lj8Y7XsqSxycnRacll4eFBQ8vdHH9VpFieFxZl/Mohktm85FBnYWGYMey9aZ/yF8pvKQzhUy0chvs/9u8w3+m2vv/+wIuvbCkL+Fo4nN/R+HwxqrvAbytkwH+aei4xFiDi58AmHjbMDOmqEOu5VDqeq3+bOSbFFOuLlwrytW/eZNGu3KAWyr1+7Yu6TUvxywjoIUZ3pRa721taamlLteyqFQyEoP8MTbhYqD2vdaUVyabP49rq1h5WpXt6ZckHrUb2nLWWLO/R2106/cuT2cUXFiZPfe3omlhSNBBBc8+TByWlcuN3A7pkx0DRAvcLbQwSf4a+t3Z5y+o3W06xKKTDzq+B0Fcij07GZ5ShO9cDJ2+/Kb8d33Mm8nyIOe8fsnazPESno/yKbJ1Telz3MpdnFAlZDg7tFEQt1sOaBuyktbdlv3180MqwMjI9LR9FcXxvYJ8Qn6FjgUUyzdlj/DlknjvUwRGCOBGXhhPZm2WTOKbsoeMU6VjFmjVYO5m3ikXmDlY/UWafL6qez1AHfJ30PHUopDCUWO/JMlglQT2Hof08DV+5wV3laXWG0vl+lPKjjUZzZFRJhrzA4LcJh8inVycn6qNg8AZW46V6A0Bk/MUzxkUytKUhm578qtnbHixzRJXLfhLkalEvz1Lmqx8mWNI+10hXjyQtP/CrqhH9cx+2/+Hh4R3ePLCAynaFpxUdHnB6j6Nwklz2y1tNcHmlSCw6JGS4tsmw2qh7t6ZLJ/+UyYkaovpCsH7PMyw5MNPJNubG+OW4UU/jV5CLfNwDgloNBYz/PPCcdEKo4LxGNiCUzpKEBtIHHt40FaZYhArBxhkeWzpCIR0SS8wrgBD9PVta8P+4JenVxMYD7DtulDxCgi5d8ppL2YTjO2Ug41YLu0ZeG2AewPin9tr0cY4DexZ4+rcNqwl3RUuEmRMJf6jucpjKz2Fxm6nDW15JC8TguxeV/Zsj5nTofXz2jih/3G8FtNKwv/y7jMq39KwnVhZ6AwIZiSgR0KL9EglArCQS680GE+gDZEjF//t1chGsSlknl9G6fjl3ZoU4OS5Z2ToIIWyRcs/BmpTIZRD6AfwArvw077HIKtUeA9QHQmP4wFbKrU76QP8EwgG8Bsj1tCHUp5POmNx9r2Ja16Qb8vH3ZmEKKOIbbzB3kVNxULl+bU7iFXKu2cpWN5A1g6u4CTSCFbgZ0CrnLx3GhXNIYr2dsasG78EBjwXAY44wpzqbhGLU2hWChmJ58scWPAPlddbv84MauVwEY2gDJobOhIEnuixCkBZdD4v7nutnFF+mmZ/Z0SLC36d+0CutPG6FDfMo+xAmw3ELuu9NUqSnDgud9WUtrKvChherYYLuArpvCkjfphUDK8+RQP1DSTgP7Ax+nd9X3S4WvNWUAbXvRNFFgFW0Pv3f0IcYKwN8bInkbwb9NTwUWk0LW0mOmxq+IRZhk9PMC2SagVxa7Bj0KKFAkZz1mm4b1NyjsJEG8el8UOGC9LwsCzVMxGn/5vmzgwnMVh9T/yZshGMkJUbkvCgxo7suCDBEqpvzB+7JB6q6K68DXfCZPD9yrX7O2wfXAd7v5vzCVIrUgVWjUp/Mm6Qb/mX4BgNRtdodeUu0mLVtPx8nKLs8ohg4YiRZ1cgjjXjZzshdd4B9su8/pjFnygdWui9EFIIXLOB1h7jCn3ckMEKp+Kvl069Ec2lvHEU7DX8v/Sa12kBcK882xL3A6P/CvejrEKMmKqumGadmO6/n4ERIRk5CSkYfxp+RPRU1DSyeAnoFRoCAmwcwsQoQKY2Vj5+Dk4hYuQqQo0WLEijNOvAQeEIIRFMMJkqIZluMFUZIV9avphmnZjuv5QRjFSZrlRVnVTdv1wzjNy7rtx3ndz/v7EyRFMyzHC6IkK6qmG6ZlO67nB2EUJ2mWT4uyqpu264dxNl8sV+vNdrc/HE/ny/V2fzxf78/396d6l9P3//ccSSptHNd6+cqEAkMupNLGcb/3twgihrx7HIeI5bBoKJXOlAsFhm62Agy50CZbJRQYciGVNjnVYBivEQoM03ON49q8+nzEoqIQu7xcQKHAkAup0hVCgaHJ6XxKKaXcNiUANKuGiIiIa2aDED3XOcCQC6m0m90PSCaSdYUiF1LpvN5s96cJGHIhlc70tz/aVRJK89jfEQptCXlihlTatXndLH09sk5bHrmECoVYmwprL9OHHzPM2ljv+QEAAA==) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.css b/data/Leipzig.css index d5738fe4f23..171e8eba364 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index ac4a5947a07..a62291dca4f 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -805,4 +805,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/Leipzig/E926.xml b/data/Leipzig/E926.xml new file mode 100644 index 00000000000..579b25d9c07 --- /dev/null +++ b/data/Leipzig/E926.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/E927.xml b/data/Leipzig/E927.xml new file mode 100644 index 00000000000..221e405dbaa --- /dev/null +++ b/data/Leipzig/E927.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/E928.xml b/data/Leipzig/E928.xml new file mode 100644 index 00000000000..b2ad64ae2ba --- /dev/null +++ b/data/Leipzig/E928.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/E929.xml b/data/Leipzig/E929.xml new file mode 100644 index 00000000000..f3d71370e40 --- /dev/null +++ b/data/Leipzig/E929.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/EE90.xml b/data/Leipzig/EE90.xml new file mode 100644 index 00000000000..3baa4150965 --- /dev/null +++ b/data/Leipzig/EE90.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/EE91.xml b/data/Leipzig/EE91.xml new file mode 100644 index 00000000000..546ba2ca94a --- /dev/null +++ b/data/Leipzig/EE91.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/EE92.xml b/data/Leipzig/EE92.xml new file mode 100644 index 00000000000..21467e1589f --- /dev/null +++ b/data/Leipzig/EE92.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/EE93.xml b/data/Leipzig/EE93.xml new file mode 100644 index 00000000000..1747954476c --- /dev/null +++ b/data/Leipzig/EE93.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/EE94.xml b/data/Leipzig/EE94.xml new file mode 100644 index 00000000000..6380e75f6a1 --- /dev/null +++ b/data/Leipzig/EE94.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leland.css b/data/Leland.css index c94c3c4eb2b..4ced21535d9 100644 --- a/data/Leland.css +++ b/data/Leland.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leland'; - src: url(data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAF90AA8AAAABFzAAAF8TAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGiQbIByCTgZWAIsgEQgKg/Bog4ZdC4UGAAE2AiQDiggEIAWCdweZFhuX3pcOsex2QEShYm8lopI2T6MoJa1uxP//35MbMgRroLaq7YcCi1JcDV+zsjBlJJJuslEqdOFOerqgtPjNiQ9rk7FfcY9no4KEw1/sYSof6gdGRj2ew6Xw0kAl6aAO7UBHdoY6qbnYZAUacEXvnN9aizq+xvmH0uR7I+N9vk78j3aExj7Jhe/7/dCz+37CkkFFIShGCaTiWRhqNftadJ0KH1mSdezoDs+vzff/v/8v/7//L5qL5AK4Bu6I45o6kBIQKUkVFRRREMUpYsUMjJosdEYt1IW6cG5zrlw7dXNz5cK5cDoIctvfiAAVKLURCFWd3c3N4uFaWch9J8lseA8I9wlRlUiYCl9fK+t/zG1a59e1FkJL1B2JRn17X1CJjHjgXr1H0QhGPDzGJoonlI5t3VTicAVa/jqcSgvhy9cpv/97p9XfSi9WbS8KQj3xvNhOHG7kQSipkvZA0pqZLGeB8yOd9yf9qiQ7jhudxOZuthPuLHRzQPAA8O89JGi6+y6hKBasWNs4ZGoE20mEpdane739Z9wzkHR20oYrfD3WbKSiD5Cm3Wmlb8YyjB2QG4ytuVGMHNg23nc8mywjiWy9RwfdtuN+qVSiJLocnHeUnCYcRgQgbANhVnbe35tqlf4PEKsGRc2A0hiN9gy1kWbOaaq2cvn1JjrjgvD3+0109280TDdANdCEBDY4MyA4nANIapZGumoCIAQQXIquNNQab0FSN0OOMYZrjImcic5Yn52nJmJNpLlo87PhXRBebXbOZumll10SXhBeEC0P/7/fL+KXs/GDv9WRF0rA5CImkUYjFEKxNqlO9Tyl1rExVCrKicY0NSLjhz+G2peMpmOeUUR4qLn83X+MmVYD+smuC26zBI8DlObzZbMKfvmX12kVQSGKCdfIblEESEtXWeaBAC1aECAXBvVXgsLjRRWAkRgjxgMiy71XsmGou6YftzcBBisIpotHgPEGgDgABaQqHZ7jMgwQQCGZk08AAjDw0VucBtAV/JGLtgoDSE8WgME1dAALT+0Y4ABmpJNxxeiApGThliUojgswcO1tJwHQ8Wgc0EwOQIauFBh8hhSlsMUqsKqKlMX8jCQaju0SWrZimFgQZTBFaEHzI4IFf6QKMyv8If4RnmAZOrJPwWmBXtb6tjsDXulzAK6Dz4tu4Mgb8LonkS7W4j7SAgLUf2eSushQW/ogm8tWA9T09NVEurPtCnSM0mgL6fibAlAgeU9t+KXdmEElJmMGViOHAmr4c/zmPUc6WMJeLtGX9S3d6VV/lt4etpk2Yhfsvu29cadXHlYej58OnKqX0uXuJ/1p/PNMjAOEzw5uVFGKOjSjH3sPN/2f4jtv2dLCABs5pKd0rz5V6MuC22VrsyWWtRv2j1643UsPQ8djn/rC5dJRPMSB8YDXN2/9y2w69WUr9SUDkeIB6GZfVWe6ueZZz6nxzfyWc7zMz/6EYEidDZWqXsd0QV/qW9113LVudovX+3Hv9F4f9MsAv+xLAH/pWwy11brXSKKkgmYhaGjp6BkYmSQzs7CySZEqjZ0DrelIzZ6d3Dy80rV/0/TJlMfaxtbO3sERCAJDoDA4AokBqGUmjsMTiAW3cQd3cQ/38QAP8QiPz2VCIlOoNLrhH+drJovN4fL4AqFILJHK5AqlSq3R6vQGo8ls8fEFgSEADjA4AolCY7A4POAIgBOBSCJTqDQ6I510AbhlstgcLo8vEIrEEqlMrlAC+FQD+Nbq9AZj6gD8AFn5AQT0t9rsDqfL7fH6AyAEMwBRpdlqz8zOISiGEyRFc7g8vkAoYsSWSGVyhZL9rdhbEaAYDtHwgAASKBQePgEhETEJqS/UGq1ObzCa0sv0rR9nRjPu23XlCDjP2a6MuZMpgI9V8Sdc69vpcmeQwC9enx8IRca9rukrlTbW+TzBDwUQBMEQAtEgDKKL/M2LATEhEyBjGABiAyAOAMIBkCESIqNjMhILSBQHSBwfkCQJQIqkACmTAaRODpA2BUB6QFg/RTu53B3dn2wAqPJq1vhifK2CnJdySchtXFbsP27YbuYBbGMoIy8ZFj9ENEqGnRhVUQYXUOBFQW8KnU0QFWcAOAtEYFTGXgotuwu2iVDhFUxzwpWgyHK42ttJHFUsARZYYIEssMDiZwgUWeDNBW0MMxqjmKESNGwCLjgZihQZCxxVRF0FVp0GaBKFMXchIwK9DTRrIMSiCyrCBeQeA0igMgRwAClGdIo4DBuFoOXPYQKxcGnvCIKhA4KBF0gV27y2NkFlW6VNsPfH8azx4fEPKVE5uTLjl5khrKzIEAhIVtY1pOsQXpVGnKmRfjwBa+0GQDXk7pK5NmT4/TugjMoVVYDrMT8bWVYJ5lAdo5AplvyNxB8puTnBzAd8Mh33xgEmEUoHNcP39KKHw0iMdEjG6XmrKOnTWE6nZZK94JihKmZPFpcO6rBvmKTLFcwM5XiOmyQBjQJbSUvcPVN4olnz2jM4kN0YkbL2mBBKKjHrsaSqY4G3jwPkdWcYzlFtlGUN60r2NW/L0jCqBe+nlJmQYmiBmrkpRRCd0HIdp1a0OB6f0kootpTMy1yWKtIkQYnSZkAeaDGsojYyCAiuNMnnEQu0QgskR5bJJtHc15yXA0LKLM3WaoyEw/oYxqhFNFmd6RmCnZRIpdLxhIwsQNaHlwPpbGqtNCDueTeSzdmXr3/5inbq8UraU4RzLCpS60ZybF4lPZ0cn0vjjumq2RRq1nCSJs0PcvM7sVdvupfjkbHr5SnsdufuN02iWm8vbPWnZ3ZHHYpIhIHRxhn7DxV8/x/Qi7cW4m9Hshh3nsgg55a4avuIGtbKscDA4nC/4wBAY1+RYaZx4OCzLJo5gU+SAJAnLL9qCbvZCoSGQiGAnMlYdaa3NhYFEEAX6bvWGvCwSc6sJEmUAJYwcBMBW3M5cilt2w5towiqEdOFxLoupNrjIUlIURBFUV4hXGrhezRF81dzYTGfS/Oa7dQ0iHFCjVyMJojJC2PcQzrzXhwo6oc0uIsWzbzRE+CbD0D4UMctf8VbrHgUv7rsfeePXG7gPvy+e9Wfar9olByJUa3Kx7y6CTuXauupgzEdu+lFq4jQJo58zxdr232qC2BjNSRLnYWwSBw5dEfRvdnWdjP+MfKCIGkp1Yr3+DnfxUG7fQmInFCxZFWlseg0iNoCD9/6MleWb3Ze7ETFni1IkKWb648InwDBzp95kQD+kXFtEOwyhf/Fh+hY3XTEe2cR2zJCiVm0+LQfKeq4SgzSViaQWPbORgtfbtNaK5nHXKPCkakhlZhUY6dqTL7a+SqT2baoRq0edOGtkwBicqAVEzhtF1JqVWib15q2DST3ZbS5bDzBFzMyU3+J3TyAz0R4h/2EzSGER3jSNCkUcngeDBfFqx3aD9rCRY+C61pxGIf/5CiHfeiWOjFSv4aksGkplbgKhcz1IF8zF+gwCUO+QvsUEfm8uGoIWYpRKZVfL3Kt34E6tqC090Ud+nV78d8VNINTC5iGmdKy2pKjG6s75Nji08sYt3GEI64I2/qzh5u52ObKVmyGUD5IKErsK1BbF2dEzgvvnUECx0HIXJoNiIVXNjbbFoRUhSMLackpSrPktxwZgMf4DLM0cDQFStMEc/twtiWEuFOU5hG2CZeAWuqUPoesW/fFZ7r7xqpDuTMvPoOW2uQ34PvkV4YX4xv3m49qdssz1qDuSskcqR/U4VuenwCTr7O8JdO+rd8Xo1huux8c+v01yFXzJfHhMJUs7liW9uxC6pmVGeiWVdnYjfx5U69uchN44HpaBG5LTjFACBFOOCga5C1CzNmWZJcUEDt1DfhgvF71gWt9HrWxwtHG8Nl2JizGT+1bcmrraVCoEeivlqwIkLPnX+t5s2iUrD8NusJrPK74wFv1sKkOwoq11HKdcBvh6RF12BZMyR4tqWpyUddDQoWDRXO59Qa2DbB0WEm6DxOVkv7UAyIi+n8E1Z+6oP2PR0FXKf4vUB1o/g+Z7pkQ36fEcJ3f8A7PgyXWfYVBGKLsHaUsj3WBH5pQ/b3Do0TCAo2RPFFc0m2tawo+9JlQgwCBLWseOjn/Be1RWqoXQ73wdCKvr9+IKb4P6Be2hAP2okGwBYnA2J19AhRege8qtw/PdRpbr6nzR/JgRGnEHDwX0bPpAi+QgoHu5gIKtk1dry8VYgiu6uHGqysPl0NUQcEd1bpUFQjrxUfo4fXCKciZvyNBgymNwyWEG/Of6JuvBvTtse2yAjO+OE87soBwYaroSJLjRLMPVTqYDzb2mdAnfEa0dKuBsmRZKyfd91cSZzMLc15tqV+l2GXFWiyavizGyI/De01OA2VlJUznrhQP4v363C0PVBL8FQYiO7GpoNU+BPUnCyEzAvxXeRROyu/XaesktOODu/cWcw28pv1BRmOQyo84Uy3cemExfqXzm9W+Rz1Hi6Zyhj4yE28TdrnpHt3X4u100yxAmS8iEOe564Tu0ewuP/u2kynZs0Lx1bwiywLGJeeakfZ3YbzvXiJdt7rWUW/OUB1T84JrZY9CNHX9QD9UAI8pmvX1IsIzwgYtIZQqrTGR0wLEkExn68iBwoSkc1M7bPD92Ox8q/F6sRWDAlitxWhhN/XBrMLqb/DJMW6meqHQeg64LEm5YXUoAVfqwU/75+ig1Tsk36T2iWDDj7yGgmWot6UNljIoHkRb3lE7OKswFg0rKHnTQqQ7752NUQ5mc8jvMrM1Xc7WwUv8UaXW96ml4vsG9ATEbobbV3NTP8To20G/49FGfy08OKXmv62h/EYnfgXGDsppjPSZcrf/Yya8eWemvohNws/rfI9uXkW4cKU6j61eTgnhFvr3WtPPTNUghOpp2Gm4Koje05WM9RdqVy8iXLnxMHbIAkDqslgAQfadnvKhRv+CJfULYkFXqT80cVf8dRKMi7G5dWckB0kinLKM7mLUvfu4zZQMB8GVZetReFIbhNOHcqvtrin6N22hjcmuO7fY31YIT9LzprdLYZVQ75SGQfyWbwS+nddKCPYkp33tdRVYsgjMpyW/4DaK+mRGyarwxndR6RtvGATsXDnQ4uJKHeRxZMQdzRfz9mjUmSHbXp6Lt1gWhJLb8Yz9XnHx2AzkbaephHMxF5XpVAufOSkMJEspv7GirZeZdNqACVCWOIibxElE837/vWHRn9sywKypaU71IIfmxzuhXrQwt5JkBKuYJimhv/NBOeqDvrKH1XdhIEQoztIfaQGeJAELiw5GO24PPsmU29XsULSR3CrkorkXiAAEpNAjNGeOGOutRd9iZks4oubyL2vlqHuSdsMtubeq5K3AdMsrPCuqUK1AKfw81Fjgw6PeWg0cLra5l40j8gJpnQvf33U3HBOK74SFKJUWUmyHEAqUpL2pbqLV1gvxJrVXJSyotsIOtkbCUoKXgqZ1W+0zpTw39a74aI9gL7fAo6JYckif/XRohGpWQlNVLO/VbVxYZloIjz4LGZKRRIXJ7R09QY9D8Yd1lhXCw2RD9M3iyrwv/ugJLf9czcBQdhhVswxVVrFqSqVXraviss7KvBZ3bEiRa4zKtbBKeM2px5IecKac2XaOBthAecLTXJTlUvgsRKj6UM4UZyqVdX52Wgktv5KlPuOWJwguMUQJ3SVloGmYK2pcUG0HDUozTX6G7NmXTEeSUMK3P/gHhKfYogLQrIjh1mrDaFTSaFew6aAWWOcYsothWxvFmphoFKQ9r6+PcKG7cjOBxeJpq8zLr+yAT7F6DJKol7KpCdfaI2Qo0M+jLiiHEsmIZykl6ThvGtJkouS0pgl24b1beVZykPxHOVL24/vYZh/2ZGt7a5l6fBcHuZg1uVgnvwi7ar61btwzV3UlE8o2tnmXWYYovbGNobmlHL5LoUBYy5yFFs8l6/OaUyDNOfSO5/CR2Shev9Wo9P5Rg6GpNvkUIcy+lQVhWnUwIubZqg/nD9i5MvAVS89+9ohw5kEiVhsq8xx/zkGKCZcF657vuXJgrupr1rqhuYZU/U9QOelhJ61UlX22gisIV8NtjrWlioWkN7BuLt+A8I0Fa2Sv+UuwHDq+Hr7M65C/i8SONqnPpfpSkxUy4zzoyg6PaVGyQbfwQyc/WmTptQVVngXFp0j9UVB+1Ph7oF9AIZhtUt6NIb6uSO9dhKC1ii2aA35LhVDLzaZKncvh2rJTAFYxHTVZC7Hrb+1Kqyy/RFuxkFrlj6Btg8+AzU+uaxptzVLD0SU1wTklpu7I8hUXFda6NaMzk7U3Ru08K9f4JJOwgwHgkylT6ZVS5A9x8XyZ9kVSFSIUmyubvDj18Pc+dww1pyqi3nL5dNoXeaFZ2cuAQ75DhVL66TmQPYdvjlKb4jsT36n+6YKfWKMZyTizHOCVd/3+wni4msOO4zHQDhCyLF/0bkqvmywImHFjIDm0kIhKcA7PEj5ThRVJK3DuJPFlFIFJpA4+xW9z0kW0xHShH+RuVOSicWOgiqJMeYUns9DKImTl43T54UlfP37lABQRha5eplwVnb8vgHDFpRgL1xOGyaXH4Za2U1MpIqXEb3B2LRa46QgGlgOK3iuusRTT4XViQHF+xvv9lrSGezgRyYqk+yYnSktOZEOpOFVHmvdcDvmMtUQfKGik+JqBCOycIXkplOltJ2mIYWrz+coLYrSrsmaZXKpeQbNUDUY3pbCLwXWuoFMYvrMpga9ID0b1jjmG7Foi1wXbVA831LZEHdZEsL0FdmvHNCji/6yyos7T6YqY9tdwUfGfzzuyp1qD4ICDepe7ITMy7n+EeSfNOsFBwZmd9/pW21Pw05u8RmtOlw6ZYdBFABX0uxfEHSXdktHGfAe4mH3XhxoZOQw75RDzxoBDWKeCD5ov136yFzeEfN6LXJ+Nw+4uhIPr7Bp9HLGbKpE5IoUgLGE5U0g0wCgB1DSECbfJu+HZFccvyptABGpPxm73KVnmBT/eeCThB2WXJOmS7cwcDtk6mm6OmKA7T5mNug0F52ppMomjrzpMpHqGaD+Iqh7PwIRN1cQuK9j0lks1D4rixmMtJfwySv/yvNtZvXF/4eJFH3uZgIknVSSaJjufwpqYmYu6ZzgdPh2+VCh8jq3RhAv83sXm7CM26dElUL6qNnMZWWEqqlw7DeR0HMlcnJMbJHIyVhy+15kRs+/yzY1qC3mh6fzfj2h9tV2fBZ+wC8J5d5inS5BlQ4WJI4/XVtamwqUzLNFBarwrvnA3/2+BQhqMyYvWMViZ3XXraJBysy9qmtDzmNAmEkOuOfGUKMZxLNHfaUF0fWmQ9mWfxGxu/29QOxv8CXtmo7B+a0Cno+G6cWxTG24eQ//bG5RxqFQ6TeSDY0S2hhmibB8cJEQX9gGWTsBy1kQC+eAuHx1GJJhMTyVKE3HKDfReY1F+10IPIIh8tdAZ5imXsUbWEN3UkX3cxDlC/cp+0d6KTpl8/k/IM0e7dWFpOt4xbciRsUHxRcROOiEp6XLRHIlmiacej+PiOdY2/Ym4X0FT11CqlvwjwWVatpVW6by6ZS1ipCbAjNyoDmIMjZrI7JLjFKsXrhhXL9ZKbotSB/ewzGwF7mz5lIpQ1KhdHFDhoa3N6Xmhkr2sX5ou520B7pbgcGmLGGrynSLBq3nzCPe/AUlsZBLDVOktau4Q/NR3Nl6Bt4kgiRFTwhd/E31kFnmTR8VK2KIUfSPgSrC3izkDmivuOQpfMqbkQBQhUccRuTAnkJw18YaN80rxa9YHCMX6YPxNFA2qHoosKqApRPD80aYx4B2uxfocSGYfp9/txoptfmcddJwGzNT9N1VdMJxZTaf5RUEwc6m0j7s0Gucj0sycDYNEn4UGjp5kFF7GjalyidQA25WIWyPyeoq2D7ooVC9tbIx5F/XlmQaX6UmmU1BoOv84GrXVb1HE1A2lrW3eAxTIpv+JbCEKDT7UImhY45SrTI4CF0pnWUN/S+jBdiysoHGIIkZ3aYI+8qWagRAq7dOisCBmqRRYeo+yMd7l0Uy6MPX18EkSu1AqNz9KlcPRHTtvrtoniTEkCRZEn+04t+STrmKiHrfZcS44PeGYwjG7K85cUiQCsPhuYzFq47Ll07tEuYKUNZFBdxTp5FBK1MpJJrZkpDKNUMrYGZYS2R6sELSFCKiASSM2umHek64lHue67XkwEPr+jx0TprazGKZSZdEzg+UbU5IhTyObWIpZ91EtI6OpnWd/oWcZ2/1ySgAbl9FU5ubN6W5L19OiPpHLSMGWpnbOthET1mqvD6HoOdMo/MBO1aMeki/dCXryHHq/WJY5pwqGQxnKqTr9p0t9Dy0XghMBtdNFbxoz138lfHSX8Hf+nwxiiS+a/q40FoR+G7zzMEVbDmgwsv5x4UGqcvHOfGT8UyLC54aHFp0sj7sWHOMHn9g+9TPs1kjSJbHUv7l+pkQ9SCOhUt+AsK3Jh1Lj9m0ZbrAwKKpEzajRmkaiOetvU4dHnPWZdUY/T0rNR1QRbzL/OQhL4m2ygcJ40h2mRRXOMjeQ/99A10IWYOApv3Kn8blyqDJ755dTUV1PyHtvH5RQKquCXlDr7yITo+ko4hg5C6dHnDvvHbgoXj8LPRBU4/I2qAXSjkp6s5VnjYsrLYpC4KRpnBsjntyA/2lngdV9NxwMHdQ+cGV51IzPVqXRJTrYBg1Gzzm7gGVG5H4GIaQ3M2MyUJ+cZOBZR1BAh35V49GL1ULGbDt//8OknVLIHViOlTEnl8I29oMezWBxqqF1b5QcgaEfw5jrXXGVCaC7zXW5pUymGiEIp+Uv/qL5e9TFRtmv4sk3Oz5nV04qNtNVJMceSef8fslFQYdbPFl+vM+0sfCUFqnEmEcHkClzJxnH6eiVkYeGbnLQaoZMliqoQ2m9EnKJAJWjVtind/FxWT2mkOquIxbRdF2X3UkLXpLY6D4la1DnQGXj52+dtwkKHCUtYGOAt8bXw7C7F1ffJAulKpWOrlaZFKLFiByKAuGZMJYUYxJl+vhSQn1f8PyyA2kTFgWkNqB45Z8LuP2zfA6OxiwjOxOd83S6IM7EioLh79R7I9FzjknVeuKaT84dHCSBnmTSJ1Rfslao72tO3JHuM6Paw9X1MD+8ME6n+8PlQUBKRnNWAZzqZpVm6RzaOjMIDtaDO0OnDSNe022xVHN5jKWMFAFHLEHLV1ODNxGmeORGSSgiwUtL+WeO05IIfW9YSqSNQdGVa8ND9L21gEyDKbjDJS8IoFtyOAVmxnkoTx8Oa/zqeTfsUphPeNDsHf204wC92zPmfzoAWttnQ/CxS7llXwpbjf40su5Oihx+oSLN9Ek8qVfkWDoqxjr2ZqqVXbvzNIJ7Lh55ypGhRu7Vr/GbV3733EKvLbXfcMDGwW7AN2O4SXxky0Ld10TLNLHkdVvbVuCEyt7p7CZiFCOsvx0OriAsY12bXJwZAOCvDllKUZvWhcOjqjR4PvwS7QBeoNEEkIQqu050h7Q7FJjIVuDhmWKAfA0M6n3Xuu6u7JDvpwuDTqJoYW/g4benmqsNoWVVW9CRVxQ+7rdMHpUfsvWHagLT4kz6Iz9J/TaILEsvze+b4b5UHhtc+tqz/UuO/ht59lTFvmBP1yxZ9k9iu29BamGaElMuzZhlSVoK5o+NE0zjsC5pAJ4YvQQz/wewOtUSO2hIRlWd5MoL8hMGMs3vL5YoM7B2pkSgaGNEYifUeVnBScgfmvT6UOTYcaUhJzq+ioxORWOBWQ1NSLNDgRS7/SgbNMfmU580MxQ0mmTXQ3TRDIwQhSrdaS4MlSrehsyN0pvZaawGmaALSIN8+uB/7Fju2VIggQDdWpWkXpYLlLZU+yf1K0sAhdU0D4zTKTagLgOJazE69ge2vvidTuETrvKsCtT7zwXiqyoTzM4eqxNQxB2fhmXI84UCVXpnmUGcvEctqYN8UZqA22mACANhg+vo88DupnFauofAcIBiTyUJWBBXErUw6QWSlwvYqt2QRAcxQsRdLi6yqfcKuKf+6RqgDJRgkdi2Vm+I0CHEZ4ksBIRBYkOdiy2ySzH6XI8CIVUjxAtmfE/Yow9JNQ4W7LQBTocyDbzSTPg1xILT9runuBNIK4UydQv9i52HGFHBmJGpr6/Db7o5OBoBbl0RgfELA0QSDs7cKVFCqQUZOXgsCB0zRhfSVB32tbtKnfjgdsrNmvN0pHEOEzdsm9I5JBmEJAJVMynYS6GIBtw7OQJiNrxN2uC0sf3AXCz6Ww0G+oA+cdg6Cx7Osjb3I/DznO5uIyThd88ZsJcvhYYt1rYO6AgcexRnioYvPYrAnheT9Hd2icSyjlAgnfTiFrZkPud7Gkzxh8urajcgJMcZ7h2wPSD6YmVcs54olLNsKgLkXG3HHgs8bQ49krM6s0CdlQx5bGiKHJ554K/w87w0l1v3lGrn8zmxZludqezUyuWFxezVMDJzixRrYUEu5Y4syF+ttovMzBCmS4ucbLVlCMdMQ7SldDIqMaXM1XidPbady0/vqBCOxZ06brhkG+1lEW6uNK1GvWAnE+Q9OoaFvOTRiphVAqLyOpU9XUpz/069biuaxQWFw2bVR0pDLXz1J4FthpyRenJ4sCt9hRRdyO4nJrglkzhX7PRxpFmEBatR7PKLAf5G6EAY6IWWFkx6tXfbLWky7jcoZ2q8opcLKwm39mZIiYkaEaACahtLZtRDs22d88oJqw9ZD3W5S0LViku4DBgK/wyQZ/oSQV1mqrmoPc+SDEzPqyQFa510wMBwqv1ldwqrDpksbMvtB3w4i8FEoTjfeB62Sm3s9lKlb1nUKzNByso6PDVHpPfjuT8BWjtup0Z2x0xYeghVGWXsd9VM2hAs5GPI6EQYEqaXdYqH4hREmcJGSy1ITk+DeLuOt3heJJUiymC92J1qFuLsNlQNUwmr3kceqELZJR2bEReopG//SNxxpDozt3J8YYGwxvImCYV96OLFQEAnhCjxjoo2dpXfYsKx/CWwqIAwYpadtGmmeCYHL0pQXXV/JdFWG7cTieMc7sY5hts2DlI9JxzUkyX0NQoEUa3a4MZKC4RSvvkVRjQOUWgthSIB+4XTrYtYu4o+bfILI6V1B+eZ8tvSevjzt0qwYzr+lIMl0oRhoHQJkvnoa6Ezb66tPaUZgEpdCOGa8rTncxZPSvvj2OfRhdgjplvWG/v9iSyg9qGSSbUaHFmTs3fmq+/Jw8af/TEfMdFX8Mypbp42gis3qsvFZWceZ5fNSngPP7FsJJ8A0hRZ8HxCM9exMPiCpxqfHA4b8iJnCnIFkSctXVh8ephLrnmrAeLjxmyAvCFqEXVZHIyf78M1NVz0Mg9y/cmr6XaRRysWkTB3sDKubHyAmfatnN6chjRl7FiAxmCDRzFyjcqasPEDcaFve7+H3YvKO3sn76TsWAsPRmf189Ogg8OsfnEqWjQxatGjiyIE059y5+G9MdeztPA0hzuMCre95zGSBkMVmzWCj9L+bm9unjaa5gyYZPg3R5hfPJIp2QbhoIvwR9LUhXms1m2Pc3dMh+a+xiizHCR/JnoU8Zix4g3yEH6OTFqP3qJ5eGOGMSlQCy0cXos92P2WW42jJcJY1oQslqQYqWgP1PoKYodyUdIjcaWmrHza66RWv7TL59oAGqpsIsTJibA16c91ACC/dP8zVKXkMK19gse9JMhX9R934bS8c1P/5tQQ/3tJnNX/m9T/32WhqVrTXntzh6lgmVJjMN9aSdM62rqfgYNWc97UWq76iZ4MmbpUzxa2a8xEqREINSv9eCKbhozSSmwSNhqzDULOf2oFmtQbjm+vfZcQ36GELPxzXV4+5cLWAVDz7UQe0ezmftR1d7sDX3/l+FZoBKana08t5H1UAczBG0u0AfGDOlHtdNaY0t6QHPSJu+HgoGtpq36ltW84lkrZYBIIg+E9Ot1SgR/YPBObEKXaDIrmq4IknXv3pPedNmttayjY+1hBExwIitTXqdDZel04lgnXG3V6RWlqPDzui20U+Gq2V1ePLNX2tFqsWUUokvr2+4QLg5n1GqREWnFrS0FqO3ltTWStDSaQZQPn95wygUi6B2ezN5iXUtU8SISfTP4ZiAX5DMnG+9zOaAuNVfUPBxj/a2mv3eYcR9UTtYHSIT8+4ZsGg9uYubdIcFOQ08yfTojE8n8r7/2XX2hW6+Q7I/K5ndjWWB6k3mAwqiKxBRMtsopHb8zcWyi4Icj+/OjdpWKHKIyG7H71skTfmRNAfj9dU83XHtTLovmKYJwViKrPi7HvbXUN797xDhp+eLXf+Pu6vd+c9AGJypyx6VBwAqEzOWj04RZoPJfwOUAgulOtJt91DCZzByek7mDE4NO3DCZzRLt2gsZuF+WB+i6KFPmTAc7cg30ophsef4efbocVyBUrQtQkyo/BPnEMTulY1gLBqoedHkGnXeWN/NfPO1DkQLf4tVhEcJBR+yt3FmIhBHIVE5kj038Xrd+blcepftL9seeDtxrwBCf+surpkxgjCqVMMDylbpDjLkyNHfhoNi06vUduhp4HgP/2F/BgkN2vfNlNPVGbV7JIWYfn91xPnxHRIQi5gCqlayTCEVlYR8+DAAQBj/gZGELkzHQWh3b3hrCAy9DCqMS1+nIu3UNz8REYydqx2sjl9JAIy1Jc9L2k0MEM7FN702m6xWZOIB4X8E+aVO0IK+0N4wcYo5lEmJcFDI3m7ylA7ZfkE6qVE8T0UAudOade0mimWbkv7PqZlsNiiVisInhfd3SyRl+t4whyDpCs2fdLpDTxT2VM9j6YhJhPvZDGL0D83P8jicP7P8i96dKm6C8bBeORdvsOSyAwZD8sanOrWMkmId8O9TU59RDeCe1rmliskd7buIfPSs49SbHKbpdKUZEmtSSM1MvhrKr5cJsqdBu4bPrPTZ+LLpDUEOqVPSjWEmIw57BwKmL98fqBa8jOwJXB2+RRRsFv965ZhPpyYRisBgsB3BNaIRily3NOksKrlFDAFv5UwcBJ9rI23gVFO5zFYqOkA0g/MpHBS99bQ3Y3QgpQsgdcGlokrLTats3M0gzvseQ+q7lBkbH4JPizEe9XgKFPzxHkfETyfcrfBe/riXUF80DuvkE7z0NYK0ZSEQiGJJeWwjvNFQ3JT3kYFzsoSF2UGjgI7ES+59F7sFOTl1tdXpFlwG8/gpLNJEIrtCmxWkJVXY3P6AxeRZKhgXwU98LobvsWAhDzk0pcHDBiY3DCeo2YtmGPQCOZXQSQyiKB/qoCj1R5IZ+VVTN0oD281260muaD+2PJQiDBBDuNTm5cXXPvwrfQ+YP89Tu4PDx8iRKUW7nBWfDiQvJ2JPkJWvPereF2cTeb7aD45OmOTd7CdnC0BOFEEydr1OU5CA29tupVyOhgngZgNUMzu1iN+BzxheEvJeRwg/Kt4bb57b6u9xiWZIWS/w/k99gGMevVEMqHmFAhymayoN1Dx7QpT5A8p5h/7B5EZ2AIlTG+dj6Ayeu9y/4nKZTzPQeDhgcY7KschdZcnGy+A4w8gzMYoGFb3LH0D0m0rIXJfuvMFhcFY2CT8v+H1KRaIONvJfBnLeS7Pwa3U5aAKSX5A42wXKbliR5AMFcvWccRFS69EdQla6g0U4UQg2Ji/CaLUusT48fmm8KcZt4kDSdEoS4X97FeXDxC356iC112PL67bzE1ey1LlLTv89U3IW/f4dHiWa9BY789eHu342anUk7qA6Ih3t21kjubHtsGgTs/UX9Xk/D+eb7p+/1ag/uPpXVdOE3i2xe735HdtSciboVFOqybhYczhDkF2qV85XBYgifb3LUxZ1C9lGStmFpRySZegGENgNHKcbsWhuHd5V0003MpvHm/+L+ja4vl6IYLelOy7rRWh/EdDM8z5KLD6qvGuBcrcBrHJHL5Iy0MXPxKU1JUb9buz06VUtB6D5emZmv3m/VJMWEbhAAY1j6SyyXGMWcB+EbQQocgOLmsYfub7BtkfwdO7PFO0Xwy915MwETANwIHwhTE7s39ZIrGu4fAO/orlrvz87yVd2wQDEF00L7Mdn2OJEnCxOoSvmlA//WXm8qt89SCwMtlxvSt/HqYLqXcxzpT0/UH/Urlp4eq3+nUT2Bofiyno9Db7eeaqxyz4l1X6jG5xpF2ETWVz51peLvDy6A5HpQzYD53srPVXVUN8md35eUqO8HiUC1OsAh8En32uv1TsCkEwcaJKU2M1fvp3bWEcz2ilta7+pkWpgdnE2+ONvqGZ/7pe3x2dH0R3cBmx5nhdd1rDGs7Y6siTMP/43F6/srpoC80Gcc5OD4Z4AD/WCwi5GvHKKgjXPVEHcd1DX+c1cnmrOCYtrVGsAKY3kqgS3Wsokrf2proqY4vTqtLDagKD6qSpzhqe5d2v5zV+SBJeViVp5r4ScKTGfWn2IzhtV8fLc/znlAZrcxt6q6Od9s7PQZhzB6tt+QZKriVmkUWXZXOOAj9ZPb6Im7bdOXKY+WLJua+6M8YPVrxcddyw/SuNUfLD0hnbo+82lNqZo6Kt2zesuwGFAQ3WprQdpSN47Oq9K0tJb3VkccUw+oLaW521vQOd5/P7HwkDuQHVdWfTHZnRnheraHi40ladC7K5nC8vQ/aDzJruyxzqwytrYk5laHKvIZZEwu60zq8iqIAIqfLCJgVl57wDCf/LAXyvOm6fAocZ+G4vXeK4Xgyayil65+FJ0+dTnv9ZO+UYXBsveFPd/L3A2Hcvbbg4Kcf2d+ibSAOZeZrH3827It9vC1E3eItm0eG//6H/Lvs0CnQD1w5MZcnJ2hLGahCEW1j0wR2UaI0kaPcOde05r0LqzKTphuLguXv2h+OCtdHiwx7Tt5kmGjETKG1ue2942cbBYMz1q6eVdw0EhfrD9G5tc6+UFp3uK6uHhd2H/TbWXFyqj0kDhs0byoY2tw22p+pmfHLvlCK7stbZLkmROlgGKcyEtN88coFQb6bqM/8Kunw5l0zt6+aoTj0crCqPEw0mG4JHJMB+SGqwY/XStNO5LBEfOMqI4+voczaJUIWW10Ji2xPBRjVpTlGn8E5c+04jaAWFMWX90JikwC5zRQ8pzAmsTAtiRPs1xVt6g6lxKSGUFTGVnBbNnh5ika55TGLqkhCUgTnEYd1pZsEi+BE47J8ybpqbq2zqaG9sa6u3ufKjTmtya7Mo9EqzYWtwZ+0Z4sRX6yZ9uYcCX35lXBDtMi4++TNfzJoeKPQVj/1yolzXt7cwIy1q1wX9nrsrLg27gqLQgbNJwqGNrsVcnuUz2XGDF2AnIuTGQSVkZjui1UOBnl+XvfDR4dHt8/cvup9Ht9Z76ObTHz7kB3gW+3+kzp1slmzRmoOVcjUIo28PzsvINKouGqCLAQk9RhZTgiOUKch0gF2q0inXQ+hO8Tg1rhmwUchayqlIQQ5rbug3HDMcr296YbCUMU7jmWztmSfCVvO27DkSB671IWTafed1sr0r586a21uvXr8bFi0YsbaVZzwca4apwoB6QuINA07SBelT3A3hGs1e3fqzIP+VFYBVesMicIGuzSCa3PboQq32IjCU2i/vFVPn9v4HmJyBpZv3bmOwDHZx1W93vLDRAMQn5pmXGXk87WQEclS9QlRjK2pgEXFT0E8++kcloha2ulKvcbVXgSrcfxrhawpA1UPJitpz8TGdkjqxb+v+ElD8gj8LkfoIWGmWPCGwqhk/aWl6pQnqY2sLjWRMA0VqO1GYs5iXM2kBoV5kwVejxx4xlbf+d6JczMEC2esXe26cCQ9lVXgfrAdyQQ6VfFxZvxtGnAVybHDQR1FUAPhvIA7v4b6WRlRn4pzlexKGrUjMPkLbXsWzKjLbTjMnx+Igxj3nLzxTwaNaERt+kT0UHNLJQfs0UboaZ5Gq781E3uCzuuKTLTbGn+NirCigL1k4QxbJHgugzf7UdwvNmbELvuCqVmngUHFnnjFAkg3fz6y7eLm6VfxrQvGnL0VMY7/vog3l38NZ2FhDuihtQ71NliKBZ9bDV8YNNuD8zS0AJzhMMqXjBhVohW8TKnyi0l9Upzg4orNd05tTUNrceUtfiqR0r4Mps3FCELWO+kLpTSTt0KkMo4skRsdGRX5mu0awxeGnO6scMNQb2tNw5IqDePYJAybNGpoaB1onReXRKnIanA4h+fFQqeKaTTdvi7kIqyWGuIVUg2mla2qcIPaErFa3tNxqmS3knRSZBkh3A1dA48eDK/eNrzo2D/zqta1ZZ5F1Fw/BENojUYDF7ZGPovnTfut+cG2DNd7OwI/p39LWR5ZHAG/w5sdzZjZGHU63kye99S3+ZssLyW/ZDmXLP8sTLYf5uDhg+E1W5e1hXsTSzdPX9fmO4vYCT8EwWgNpYGEc49EB7E+tNof2tNzIpnTfZIs35ouEWMifufwnAQ8iEIAvOgbTk4X57uLNJSvzjNwWxZ7ecqcJPMm8zbA284Ze1pjUmLpiONsVQESPUKu5dVzpzQtH1vv035yqFzwpfO9xZBu32taj6y3Rmr0huSa+3ilAb05W5DGIgkq0Y1fbfUUgn8UosqCgLQD6d8LGHN39LVverB56rpW3y+/8C3a/Ha9mtI0Wjsb7sz9qfu/UWtQRo/aks6aLT9b0oLZaen+ePr0xhg/iBZWVFbItWKdWpkihQzHX9MrorrNMHhuaaNEq+E6CIqWt1dJhbRTuKKXNafd9/z37n1DKPCu9/IFOi1Cy1L3CzkcTSU8q2srz3E2B8VqMBKifccSXkpKVrLpOoog8I+TOpC3ZJI4AlBMmJtn4s7ZGOUrG5Msp63qOinJ5+L/cwRfrF66e2H7Rqtw20a8ZZ8iLzqEfjl+XlZahj+W8bDPrNigZQbnvxk4fv1Nz6W3V/8FFOh+6J6wfG7j3BVwaOjAlRlPXOlU1h9U1fe2+E/30a7fKPZn6XbcjcnKzIHhqNz94dVXS1vbusNu8ZMn2Sd6BrqN0w2ZQHzvb++kB4EHdeKJMzz5XZ5ooieRuGuaXwI/vj4jWXl9ovT3dIH5AUerE69JH/KGwoWCNy7xhfST3L6iskL+az6FEUxaYLxtSn5fo915Ug1jhjl9aaLeMfXUHRs6PYrJ6YkjyqffIbtca3IKnu25jYiUAXV+0Y7hNR14TfnUjgpfvLU5aZqYna/euj4SiTI/Ei5MUQ24jcZCveSZ1zFZahFyUmu5Lz6hTX37X4zFsfhKbZ7cyUqIU5re++Li7t7y2R1l/CVrnXk5LiWnmD+7tN4QBddeKWqsjhWNZkrlDrnkdwmx6BPzYpNXH85W6vZ9Ks7mJ1gi1o/PfPNMjCVgHWIKmPGD2pKJbBGbn8gWf7ZPn5StD3tNi82fLCIkf0gUWrk0c3lxrHFiUTA2Jc/8gU7QufqdlpZ3VncKdLdU0cK6dz73MpmHWEwvi3WICYKjcc+8+mUvVWd/UmT9CCbZyTSalyLhlkJXGrvkCVoejMIo83xPWtxVp1WqOWq9Us9w78+fEPdczXSdgA6X72pURtSRO+Jc8agC5v+/d+qGz6eu33v1//z+3yLV21lvq87TgX9eK2JzRBw2j/P5Nz5OUWV0vgeDHZZyjAbNPO9ywFM4HFBpnMyh+AJZzZeXG6WOtzwPdilYyjQYI3qMGO7A9U7hTwPTZPGM51Lykq1nkkWnWjnMJuPTQtXBnDMq4Z2UQsXUuPTBSYHTF8sX3c3Trlxxiy2Lm7TMvy69smCJuQlwufDkpGpXdrBC7MmqDvjkld6IXGoYarAaRKlNQQavDK2EYQHOsXmeCRgzdKqENvSaSB72VsghCOUyb0SkjTEJnqylF4D3Fj5LK4NBrReiWVz5SZNPEsV8gZzzOOaq1NIZEVekZ+XNW7PL6K7aMtCDOE73sPbUYPNQPpeoKaghCD5BpKEjGB8bQcuB7DOh6FiMafEHuMdhHkETuvL1WYwCGsRm59LpCFiVkWioKE5MrSnNq8/Nltcq1EM6TrDu4JQtkJORt0XtSI67uyrwih+fWZduFJvokBpSn7Y3chBaQRb4Ky29IhRKTg5sVJOiD/UHCoOzI6uy6yuzQgWeC5MEiLzCG5ZTZX/BGrs54eYFbyD/On0Vfqe1Auzrvz39+XVfenT79+OSDQI1my1ms188/CFUe3qmViHy5RwkeBErh+DwWDiDyOb6n61cYGShqiQagqAp9PJmwAY0BkGhcunk5MHoWs9REKLCuorsGRU7Z1RkxyKmJVFvW9HGtiJv+kxidpZ2eZrujMKbkKVExfwLO4/JJzDeUprDDrA91ZB1Hzj9FT6n5c0fPkFi0ITMbEfE+crI4uCEaAqDgTMYcPGUquJ4mzaQlJhuk85OyhfVmXKyJiquRNwiwhgysL85k4MwHOCpMv5rVNpeXtbFZsvZ7Fm9JaY94oJYsrhRGt6csIbnp3SdN2tC0mG3K/dK1jeXb4xDJAJDdurlSFUeEmSIX3ilNSo5FuWDW6Kn0BGUjy5Hn9Ib5qECdB5+OsXH8aef3iQB9wSiEwSR30UXS3wy4/RaVgk1vYsqSaQJiJmRUE+fpk6oJWXsV/E8n5XMFxYdABZB7786sSBxgLTwwegxJk7ECTzNkLULO2IqocBconYR8fe69nWgduTVmsSsHny1lt6hF3br52p5wuIngEUmtBphQfFGAD5aYv/Nmjc8rSSj1xBe3pn9maQ1pTpPTJbzko+0aLH6VGAJrZhayNcCchvmv6YN0lHXOR0d0y5sWfTtTtedc6H0YGID5nq7HCFNXkgeWvnZeSA6eFE0xCb4OHuolaFNx2hdzMb5BHtxC/NThQnyGaN9WhTTcnuQExydt1Z1XpCOLs+PZyxtz8+MWckcnUsKCtuvtzA0UL+WtRWVBq2ijPOfoWmnDt7FadK8EfB9rl4ChHsTcb+kMBxniZm7Pt7zffNwFEg/Lvhhu2p7dlcti81C/LYFk+tHXgHBxz28SNAUskbXYBwczZweLJw/mm96ofifHHcxDpcjl7dVFU+eUTDenEexIj8ryqPaVTsKC69lg/DXLwSHp5bMns+wYOoubnH9TN94qfPQEwY/LxY0yYwDTsjRzMAy86EYyJ+/bXlXWs5JURdjVOZfMNQF+OdYwvijfyuCup2yT7kPZi5VQp0tdnVDYw/a1/3qW0bPkSeMWdo7wsLYqzajvHu01PLiNyGcXccMuHiPbGVRHTS9MXvhcCEAz0IUcKW+Xsj5jEqzBxdLvmCKYo9+qAyV+dOCbkrLRYv7s2vmjIuXjH5eLJAsM/Y7EXIKHcucFC2Yvy1ueAH1hUEzcS5Hoe34OdEwyzRe7XmXzi98+KAyrIMGS7MGhyfx36yc1RA+J4hiiWOP/q0M6t68whRF5xDU7e0+VAdBL7GE88eg8vZ4dTV6H4S/QjUG0D+XVdqOn9PV7isXLWom3J99kqxqF60Yb9p40ZF7p+DdA4NL6yAKZFCha6zykPZFuKsxy7o+4EEsYfzhvxWhs/C/EhiBIDL5xHssYTipg2c0e0fjnQMLKCNy+7e6Sefx1NYWgB0033K8ctqoFtna3zJ6PvkERuojT9QaEq7rTI7k94/GBtjXSvPs64DrLCgPFf6YVRE68CZI+vRG/Kw3yqc1hw1AQeSMSeeg1sc2gisf4DuNPSwB5P7r7nMTA13LZxcsmkrparkBQcHbaMsQz3lhBb1prkRjj+hRjf34SZN3Uj2rYtot0JLWlifEvUyABzGFEZBkzymAulQxH8ESaosa6ikpiO18uIIl7NMzOXtw8SQKuMH9kLWq8u+7ptDyzlsGWK/W5CwEUR2DkezcCMcXzhwxetM+b6Jr1EZSTlRg8NSrsOJWui18c6cyTR44wTqkPnRQe/BzwGXlqfUxYPI8zOdZh4emDvd5+Vt/Yj9EdkrUvrnVSV6MJTBUv+ysds++7PK7UP+wtVdyRszfnLFkW1+zkRc5/cBaML12dTeAbjG7r/fXWuLQXsEGU09KBSXmuCV1yriJxmwGNFZB/tIl3YkZ9Z2zg+aDu5hiFg4GBzlctlXeVpVomGkeL/WnNYnfCRE/VJuBsn27+xtLnr59deL9xXxW8H95eZRUCo64YjHAWotszsvD9/f+wMsVpX5+YxRiUMyWO2kTPLpIv90tq3GHV96bO7JGEWrMZWMr9OOSqwVpJ56H2y4uaNz5Cx8stJWvp+ztjQtT4jHOvquoeM4MYBYSkn13f0jMCY63/muqEhxxFSfApSJkc94z88O3J3lTP78BOTU8CMlzMhmqHcSyf6+VwiuYQaeC0YCzDc7w62yW+/glRHozZoxmGkvsbFq86pXHwt6G0IA2UPJ444bfO5d1ZGWDRXfUFrYge6a+8brF/sQThuzevn51AKaqXwwaO2mgv6H4zDdnV9bOzoGnN2Z3bfNSEMUO0uiAD2g4LfQ/ax5kxJY1OPT8/SExO+plD0NHXIWFYAndlq9BC4QYi8tfFt3S5ywCaXSb3x53CavmV2xCiZlQw/xXeqIZDZGb+RZ2KrXj2L4rNs2UQL/u1+aVbKpfh1zXMMDCo2Jn7na9GnvtdUtH7snj5YlbNNE9gpltNbLykA44a3Y+hXLRIloSRAnx8FHoxbsLPwE8SMh5Ct13C3ju/pDRPU4/1lD7R+PvtLr6Qe5F46FuiTe3djjTN/CKXSCP/P/zOPN5pfOfDXhYwTnZGP09HskcAJy+f7g9K4DduFps9OirIMRs9K993jwTbp4fWwoGO0E5S8fx06bMixetma9dtAHzvEStod0BDqfKmuyL/MuRR217FNJkgAeJWUNtV6PyOVR3ZpyGxCwCurOLJlTxCLeH72rPgf8WfU4QODMvdLuufPRUz9M/5skcfPkJ8g88Z753EnQoBHNvwsAMl+WG+XLMyGI1xSLQ4y8NnhLslsskwLcihx4ojd0pY9wiePAUA6h6OVpeX9XwykmT862w6y0QOcflSsF+R7w6Lq+Edlmg3TwVQcunCWpSaP38gj91sSODpbhGxJyL3l1PwFb45eNQXQ2eA6gP2RJoeXeG3ktrYEvm4HPmA22I+RHeH0LdlUxv7BoOuu7mahvj1u96xIe6tvc9isqYt8mI+QxUx6HpZjqN3tQwbfoTy3PqVPLza2RcfN3ifPuOjbGmqfnH2tcW3wvoKMXkKj47eCC004ap6AgTUBDBdfm0Zt6a+zouBM13VjwDtQ33xwquat0gkPCVOuQxoFJmqH/XPxd3iB8lXfihgt93M26MdAkotrHpnkqwm+6Pt+ev6dQTYizQQBL91W1Qm1vyeFPn+qaQgb7tlhXWeNvcuN5BIwhaNUFwCAIJfKuR1tpiuekm2RkLGHsuyPhVfnRQy8N/VWg3AkRiy2SzVWOz1eBOR9UYRhJ+zr6qTYo11dQOxWaMInyWSPvYVUwBxgs5pn6ymjcGKiMeAvcThB8nPBACzi7iaAqBMMHBlAvvzkWAartlubZcGZwFul2JupTHrvepnqRS2DZOyf3wkhHbD1y4FVXp/b3tmLb6rAZLy9+Etxvo9pTUpqzMCwE9BUEQDFwAQBAgEa2LaOkAmOSalGJBAKIFRNUI6PzGoEl/XGunaLS4XtZN+e0fTovDx18hd/L2qRk5w38e8Pfd74Opk0rPrzmGnDEW+SgcA2xv7L8UKIeyoLrpA9SwYBVXwuXXjdD3y3KWfU+Jfg2HtDSMTgPeyZ16bvzvmRAM0zEawK+kpGAYmvk3B/WdASFAK120ofCvIir3hYGM10Pg/BKSzFM7pjjUeST4YPOTUVcWvDZmWWGNtfXF9A4ahOO0f+drUYKADWVzPHSG53sx+DQCx7TzoRvfamQ1tnh2ZrJfaMYT5RfWXX/1CkW/KvzyXg/hKes1wDiBUVfeQidCOMEhcAjOldZ8W36OP5P/PFdOx7YKD2e/S2EEDhvyaFmo18Bxenb5O81V/y1VnXSvnsHUF4AIpuVK8/DfFMeYMwmC/ZV20gZIB3KywSuvl/fyabAc5JVTmFZj75s4hjG0Qbqfc2jerFgPdorhDGM+b7r9SYzCveAZUPTO+eK7Dpq93EVH+OW9enKGkaUD9r6g8mLZHD1C8/wolXHl1P0UkDXTgb/owTDmZz1RtUUq2p4pggDqCtIlc3/7mAgmCJ2fq+W9Nbw5nCimc6KQESqbE6HTZXUNNVazsL6UcZOrC8rGcQaOIzU4XvZMS9sJ0wx7QgpehwG2+2UwllmdHyXv/XCnjInyvlxQdpfLND5my98j54Tg5d0YgPHUUntixriwLk9PCXDGDqlPdGOrYoOAjjsC+Ngw/zbFxkMQ4BJ+DhB2ece+o3AOx72bFGsObZSvKdggX3Wbtzn25HfB1C751oKdW/rQDsXm72rulR2WjR0aqzw9VjAGzu+wrLAWFK56T2KeABM43Flos10NIL/AatNBov1mMvqitnnRQupvmx8FLR+p5bVV5LH4e3djItwOmmdRodW0QkfyRqFt8/AdxKD+j1TyuK0oO9vZVZUH6X7mxi3J5kkSCGAez2Q91izdJttgykQwKsjwMdmSJa83VgbZF/sl5uRJYuzbQIb9nNQcgYDgKqzFdRIzAwJIGbdksRb3c6hWvEclJMf9JSvNVaKoj70h1pQwNNmyVo7KNtZKbpKcInHcz27zJleKEXgOo1ZsrpXUbqmVaD0B9UNR2yYmaxNXg0guIWZu+jj0cdD2AhQTXBKQqWUH922DrKCaPnhWffZgdD/wlZogeARxGwOI9e/ps/4mcMcXObwc51U2zn1Mr6Vp9VqAJ/RYNrjspoE5pC8fNwgl8FyWsWo3ILDLXZCsKlMrOPooK6LStAyPGKCqbL2TZYUbbZ3kehVCo/Ezpo9ZfFTMRovHLMyBaHJUi9DRPO80GujXJ4kMaCLR13pkpNlsriQru5Bni3KmRKOJRk7K2eGnZuWq/aBoA1fieIwfUJYFlPzHHBLuBty/D+ootjx//AUx7AdFMUPiGX67ADJxHcxg0pztxWO9JbprL//7GdTDpNO0Q3iLv2t6i1+WgADF4f2mYFZ7O3O5qyd6u7QInYk462M7d9THehh0BOj6XcMxvZOa81NlqiQeMoEc0acO27gK+4phoqugoJ/Wv19yl2HiFTahtqUtNSDB1A+Gpbr59c7/Wb6+Uxz83rRiQPOPuFLzddLhDwKDiCGhEEmdU4dBwSuuZzYCM1jpnkf1/9V76NbNw+kcz3iIggHJ0YVCl6nJ4+AlTX1ETcOzH4ZC0WySykfvZrkw49pl30jGSGWA5maFRRfKUyN//DTw4TNKcCKVwCWKt3lPXM6Lv1utEF7e0yhRe6IpyidM1p54itn3dsamhr1s077+g4TNhiDozIdG+iGIAD7Aa/08QsF4rgQ4/aOcQyTf3X1HT2dzaFDJxpL7jIh0l4Jt9/y267t6Do5ULf2LEZaCrn8buNxr71QDthy7hvLciEYGvtOrxQVdrAu6J2Otxz0u+MhMENPf9NwbsKazXCwzWyf/RaVjm1kucHuMp0dQnRvr7viUJ/ldwkjj8//VGxCslaIhZRQoqC85IjFGjfiJmSu1KJPGbxnV0+n6wnY9J6RFkSAE9n2uRZkIv3WUj9KonjU6wLq8lE4Dq154qnvKmjzTJWPSqDJpNEkuiIYiGSYWRIKqFy+cerJ1uiMQBur53v0i5UDWU0kyNJ6fOGDbTURg80UsaeBV5fNJCiRC2ZtiDIR8jImALXZ9jVa5PEm52XerJiehMO5NzYsgAUZzyKDapkxapki6Jc4J34szELCRiQBiyXNjtT3OSIGl3aAcyVqapHw/xHP06Ld6r/wcBOt+LZ4n/ODdkNq5Ry7N1mmLpeLzT//vtEY7lZvzp31rTBpiV7BEaoM+VSK+EBCmdqt3G9//yqW0v6uS96vUAwo8SdfG2wo2y/PVO0BawQTDHKNqm+9pleiDrdzl/PMnp3kNNUwYbGDBAJkpXTKvHnz8VgiFEADx0RNjxb3T86+anEfl0oROm5CKLvSlaZIDK9T71EI5OEFcXsLfuFgvQqkX8oQps6Z3sYtpjdVMGkA+WCwDECSKPjOWmLteV46sh3VBu5r1jWpvjvLCJF/iZ/VOWzCMtWAhGGWl71edT4JQydVhqtcULVNvR1KDIQxej6RlwKg8vl8t1ZIuhkS2mTTl5pQ8bw87U+x1nxdCONuME2qaYolVeaEvbYs0pN61NARYEiycUCneJ8fZPy6DUdEHYaG1Rz0KPgS532rjKoPUS7oVbIlbk4Fuyps4WSlNXzNW1DPY+awBNE6rOyvhRX9m7AfN3uTIxOgG0QW/L/8x9yfHqlnPtCir+DP1Dv70ifuvyzDnPbvXBSqfVm6syb+PbEJMFqTxhehL61Xkq3elvzyuVTpeUio6A58m+VenlqPuXh+Wq7dyJ+0JoP3mc9/C20IRzM6sYoRUrG9UrybBqOTqVu5IzG/aQHtMCkOKO2NjiT5bXgQLsEVo5LxQZWjfbNX5sMjao9oKz+647H7kVSFMg4CtorxQl1n8SL392/GHePksv8BN/aNi80zVLmswgijFO6HIOK7+CpRoNcXKcJEtwRhmZxpKtSrWh+qjOarzk/LKRlTbzR7B6TqEliU+S3fL/Dowxkrfd2kA4zJEQiwTNLD29h/hpxqmCOSN+oucGy+Z8tZ0T9m6+gVAowNxv7cqHMkIOZJThlKyN6V5EsFs59fJWaXK5o8mKUM5m+SGJEfBpvKkY/sUv2WoZOqIZulacHGdeuazdNY4HYEX1SzNFwTOJUgv5eaYwpnKvDQ1J3qjHWOMUWzPk1HrllwYpmPolQus13//VqvsEDLesOme1XuLK53JrBrTZLNeISts0RM3OUmrHyhUfDMOwzdt03sK+LAiedWTKvXbmuyUVp9FpCH3WDcOIIwDPLYn8PxmMxfB5WUlJuIPjrReGrNuLkHpdPTKCVn26LdadRzmfG/VvaT3lsgOJk86fyqXKyxztdw/cNnhIni+8I0vK/N2sJiYTFF0p0geiAUtUE36VT+DK3X2D02SpEw45joa4GZYk0gSRlDNg//yZC8IYrtVrk9Swg3V+e7rybKXxIJlPDW7FGW+RBldEZf2YxkPL5EwDjL44OrCN7daqOPZAuKEXvyMULRdVpAMo0zsrg7jhTmpcQd/KYnvrW9msjlk7YAw/1ZcNfVzPptBrN34flrMuqGSjjLXFEuU0Bh0dlw8gU4V57E+Runrusz/1RibjKvl/h7TX148KT2gNettAUbK0ytx5n25oviHYrm9zWWBOjPeT8G4UsfUoTpJStkx19EY12FLAjBJQ3HtZVtuEbXXUjnPFe6wpIGvYTYjNbxrK+f/UiJlbGcINjDfWGYj9xYL8X0G0YuW7bJCIQ1jgLY+OyXfydtMEkcRYzmV/yXqqe+QKNWzfiJWT3Lcj7E5F0bqpaSeDexrNu1L9YlKZ5ZpZrIuw1LejrdwOYis6Etd/AihTtZH/fsYVhmQieIENvfjc3LBcoH4ptyZyMtyhl12f9C1tSFz+VddiscgGyvbykjWsTVyUTr3PsgEK/7aOHOwK6n797I+Yib2xpMW6niWkNhorHpV0XEjqgaKlHwnf/iTOtrQIGLzyiO86BFfUinLhlbyAGMv2orpzgv9lB0I6zur7hyKM1n+qzXWm/UZtl7d5dCgWxVUa9yF38NRvle6Ttdmhsorl+UbVYeVAzWt76DgjAdNCLaaGY2so9ypHqht2Qv9revC12K99KDqa6UzDWE/d5ocZ2F/B3pXcTYtDLhw7Y25Nup4lgAfMoleFFKHpYV6FC6Hsbs6Ed/FSY3b+cOtKdgg4g0zZ+DOXIApY5b1LcxC17i9/ebjndXksoczOXz/TNUY43GKbch9fsR0cDT/5cVl3sarfc9O+9VPjFqTLHpadFclQ1/aKSz848ESi9bdAxi75CiGMyxGCk6HiSdHYNm9W0OnrWs4HIb2/9FDt5Yy65/EtCQrDo8WsPjwd4ueMU9986crHM67L/+REp26P9DtquTjmOUUBft4PazUeuz7gvMipWSPVLBMFtGjdgCDsPiQsR+MII+nQKTuVF4NR8oCCOvz0G693lz/u4gvw1OGTPViVcCvSFlhu8rG5ZwMUYDm9I2HUSa6HIU1He2McF/N0FFTP4IEk+qeFct4xL+g63uTMzA1P6u+rKMpVccf5UNWJesIm9duSReZXHlujzeRZi9rxz3r+SvmATXfi9Mt7Tz2EZbSClFQnxpmlNnTEp6jx+3KM4nGhlG+LrVDqq9TpzoDRX4COjtxnEngqwJZRWdrOwmCiRNhOVVIfV9rynN9uaz8hKGp/VaAcxv6scdcy2yxKeCc2qV61jtuiReJpPbrEs+2zOWxf1SPPEH9rT/AMNjT4rt53HkukyjZqzoho8NQzxd6asAJXn1/4ZyzXgbTULwgPmDkGfMHfE3GxKtROvO05/zYfCOT7i0ZZPzISG/xAO8VgmAQxGILO6yY88FzSWgeI/PM3Pv+YO85L51pTAwWDwZJMklIGBkQs0cX+Jrz+kIGnqGgPzYPKr5HYqGvXuklPaoBKnpHxuCcc0aeMbUksVwK0luwoHAwzlZ/QLTJJfJQzysifPMnEi4b1WYQ7MlvMOlREFGiFIwPqDJjLqDaGs9xX+UjjCkUyvBI/ZG0kqJBKIrCNAeAAATBBfPj/R4G4nBpFIRUJ40eESoOuufFX0+qLSyKsBbEF3j1ujpplmjAfZ6PoMtRhlfmi6YVFy0yMBEHDEGC4QgwkkvXnxUw76IUKNXApHvA7LXi7HBaSUIGLyO/Lz4/0MewJcXEoolBRgtjBp5h4jUoEWxmg1Tnfc8EmT2cCQ8Dq+S07iw6D2sUnYYmR+kMj0cBAriag7PPP1vnx+d7TZzJcwYdQZkEvigJgt4A7b8PrXih0USxo5hZ2M+LYowoYtE+/nwx8gcy6gQROjaaK61I7QlEXejPAc25py0hbjvE95Cegv5Ir96QrYGcc3X9WZxgoiPoEFRYhXE/KD7bEx9oQ9X6iBpdWpShT+2EZgbjgtZ2HK1EmZnxfg9Gp1wU9b6I/VMBdslzN2q3YG3gtzm6HDEUJaL5C/552kvHwtHS/tb0RCXaDbVgya7o5EZ3vPOYFWB10MhQDWue9fvdwsDgEui8C/I2lB4FxPG+Pq6N+zgHoFCIJzNUBCFWXEbALF02kj6nitYGA6SUj8LQHF8GL6P2hGAuQTAzav7rLymDSTAAYvEwokYTXN43FQpqTxnCBmO4oLIg/J875A673W6wad40AieIafpIkTsjUZyRnkikuyNF/B+rf7ElF82ZSgSkps4pSrZN/1w+lj7yFNJPC2jmdFiZE+g8FHvTe49fwNx504JFEbQUJzLSN8l9ZMGiEh2Z1dcr91ps9QiGjnCx1/5WBw42JJvV+hxX3opFU9Nqk0IRu3aBNkuV10UX8ph3n6QkYtH5s9cMpTx2zFdGdYmtzxJXnk1iTrkDpikC6c1e5RmV7Y2iUazpGb5MuRMTrphlNIZ2+8xsGDDOQyQUnP3hxNRYVmZzJ530hxozdD8djsd9TlX+5XFyDUe060Pd3qkvYExOJvAoeN1xwalcmQ9QMPbzFgGxs9obXDPAk256YAo3ugv2a7gXhkhaU1ecEtR3ChZa+UatARJz//jqwwoqJef1wP6Fousq7V0RdsmlXWlcpR0wAv4W+iE66+5mSioWnT9z7Y+ZXE5ktSverBXlNdjy2sHnBDeFMQ6bZmLnZ0hEuz7kneh8AWOyC4BHTm3IYLFP5sozVbq4Qs5ud3p49xpSPHYnOZrTXe5XlUN2aErzZckWFY5oeY/EUGFct0rrb2/GPtCdJhmVEBDBbD5Jw9pG+pZAYywtOpuqhd7gUY32Jor3BlTLm41q2UshsLSvr5KOfLgBVaszzkkSDJOfRm/rc3FOQSDOloZ0aoyc7sHsorXTyTFNOopbr6F018JlS+dVSQ5KVGp0w4cIvbKvb+mWos/mFbiqyd6IqQRrDFrCvdvotJM9DAIiaB61GpVmI1jlPBeddYPZnZpVIjvmpP52A6BTHNe8NozmNzIJ0nZW0iaRtkplle4IIO80tq4Lp6VHfWWNu4tSPZECa0bqAsFCJXmP4D11Q9NVBO5OLVa3k/elssJP/TJQGRZ3us6b2IQoraO0UsQB44smWvt+pMlrEoXBP781S+5KhUICa339kJTfxhe9Ik2L+zOcn/xkTr/+U7lj3sHWvY13wYkXMkp1ztK8bNc5o3inkF9Jybc9gTGPUEZdnsWuRfdPwHgUj6I91sxSmJPE/UJyJwNZ9pBHVlD8DoE2L9XueMkQB9JFpTlh7W7H9J+x4n+rgVKTw8sVP//qyPmxGBhXr1gNPJdS99916rPVGI1DnvkxS7ycH8xWpy225laXhRyrDOLVIn4lpfxsAo2xi9Km5aWp90vJn7PFFI8Cu742B5Mzyif4nP6QL8PaVVvsMyAKfruAmk0SL7kRJi9DP/L4bt78F5xUukYlKze5ADEdjg3YO7dHpkzhC7bwpbZQ+7LkbuGBhwvnupc+nu4EyHRnaW7A/Ysxb6J6gtURy85MW23IyJenaXrk78qkhJi87X/ldN0TexXqJEWTVPfZgHqO4zMwb7U6uAL5rJpp2yuPqM8zhcRraBeK+65aOsHsVvNWPBOk0kHVi1ovGfacKDLK5WdsW91rv+vjCt95y4nSXOP6JOrH/BwtDdEedCKo6ytXfF0u71mEucAvFeyZVEXVXBdGM0yvRcr+2vYjlaQfd9EyzE70iOb7PUDy/+9t3CLbsv9sdTbG+orFiHqiUTrHQeCjDgQNfaVBafr0xepAhZvF/opFA58lMlhkuAqcj81MY73mZZsazHD188vraTdokZDP5Szs+CNiiLkqLoEPo0GZ4G0BeyMEv/f6Ahun9juV9XTH/Dcxu3baIt+8ypxMW+3UhZl+b+QSMDZ8ds45mdeQvdP9GLSECTs2cZjJ6qHFXl27syAcSd/Lq31+pCG7wp9Ze2b5ZNpV2pxXPudRJExmw0h6KYDEsZXJYXB2cMzVoCiEjdvoKHmSyxAmOSo9xQ+nNexDa04vb+bs/8q4W1V2fF1b2mkNmFMH059l8rLVo50LCmvKdzXN3bT5qSoDK/z6m54F3lNvHYEkW6QlvhqQUhx49/UF6KsAUN47ONFhH5Nwi3uXrJ2v1VE0CEAwpdWMXL7y6snhfNxseyLbeYqCqVPObNsTZjx/+OSrVy6PaLQUDAGIRr1TILGPOSYC+r2xIqdqTJL1BYAhSuNiiyN6s2J/dsqLFAxRL6ZmK/abcXNkSuOMqTGKR5EQDIEvsiSqMWcRwO5FipzyMUlwLHu99iEfSTqn9dH77NmSU4OnvLS+tgZeMxaUyI1silZlRFosk9Lf9U0DQVe8k/hrOkmoVwU06rphEiv1ypVUlnYQjT8dAoOD6oLBqVY9iwO5UmEvnRnZ5nh70JGojpGxjunr9IKm7Y+SwV9StLUcsxz8qwGemUubwLIyyxeWX+FUHnE1r8IQEPOjWa5CO6GFeaQMf5os1UqDlph3yC+37Dvam8rmL+Pgzyi+B9WnVjSTVXNcxsbzmroiEJEhJJwDAdg4r2raaGaxPWBZXBbtG3SWSWTZzYoYbHqaTmuCN5IyrW7DodqsXCoSmKP81HvvFzFe6yl6OLX2OLm8b+YlMPvQnz0ta9/BP2ePG2WJRVnz/KA6qu6Sam9krX1fKYESiwYB9bWCt2OIkUTtaLLxDrNSSfRRenMge3jZo+bF2MFTeBLNj5YN95OaefMoJhL/YcAyNaYkWSSyhk9r3JJP+jgyhrzpuRqgOtlRMCmAlFlhjEnfZGfIevLd+m9TZKPPf6VH6OVQHkTSI/mT8xB7kfKL1+xMWbcPpltc9R/Zr9KSDp3/zo1iQQfCBmlj5GuOYmwFnaSvwOgTd7FY7wg9/2QFmYPTCvIYGoDAE/pEmp/Uqss6lz+QORWUKhnMb1gsGYv1DfOx1Qxw49/mewtL8ptmd3iKLFOFvYnKgof/Jsvmlw6kePaNKWw74pQ6w4/I7kcMFEt8Keqyl5gIEzFX2ZLAUBqyDOcAeuqNypgrA3+84hbnFxY4Ys+dyy2I0DFXBR1zB4pCLpPm51QenSCxESHsEhauhCP+1Nmo03sYWChMuKZainGJRODGp4SC8CYmBuKHhQYmjSK1e4FHM5jGLMjAoEcSAsHzKB6YcSxeOrERTBDPmTDh/bv+gdXdF2qqCzw9sbmpxddXWVXeJG+sqIw0MXZMfqYuL48eKTDvxO+29M/7LZZfxcrv/U8r+nhc+5rH2zHETKKaH9U26RG/Ny6tXuCSyBou1bjFf7dzZFJp43Odbglo+rVmk6MiSkTn53X7mgZjGZOffclLArXGy8CVzY+pQrG4LFTXSRIQzsUJk0xXccJUsc3esNeDsvYHkwyUguYwgRPgwhfU1c3IgOjexO5mDxwfosyUl+Q2U50+f0AnV/sAwKv//VEIZsgkoMoxPBb4X+3ZQb3oOpoqFPNci2CPhTFUNyGUGYt+ut5eWgzVj+wXa2FgRr+R9PJi3XHXoEH6owvWYGxO9S5yHyjWE+UX06HzL5bOYLfHXQ1YjeZIca2hwm/gEugIphYnBeO6l/P2+1dAHtKzMdDG753fAhmYWAQU1BmKF+7havrFu89mdaR777sPFZvt9YZmQ0Vmdga7teCVi1GQtguwFmZNuVToHm5PLTCyOApz+J7SljFQ7fzIa40VW5370koSQMhklDwzPQIlJwd4mfVg4CGxn65jD/e1H1ZTAgvPzYgYXduBbXXkwMun88f0e66/f8HN+mewkTufiaXXMvWP5W3I+6ChfG9GYiKOBLWlEwFcNI4HJDke1J8r0WJAoLEvd8bG5Vi49J3b+9d+Fv/6tvzeMdUVfamduKyJy5nE5VZmuS3m6Ya6fjUgxQHZarffRkIuvZnhafGnv6IZ+UAwUozrDDjUceT2WvwDvuWybGaIywQrPBtvVbeQbxu51Yifx0Z8A39y4t8B6HG4UANejgfa3oKA5pO8kfCgum3VfA9AIBLL8THcO1SEakomsIwt5GLCuVsQcrN37x/GzLYtMZW/KtoXdn7QLQ32uCSbEj8UEhaOe44Xbn5jPkCnFpiHX5CvExLr7H65HQQmAHWS1BFuiWThEb9HT3K8dEurCslVV9NRx3ydsuq4ypIJBCWrrmQhi8gqivDdf6lTldvZJcIh4hnkeqxmZl0qcGsBnoUu1HHhdRGj+iFBlqqBoJGKn+r4IsaducnsB2JpcwIpseGob4ZbuRylO4eBekI5bYTq63jOJUUo+64ix5VQfezTSPyrmdHH3+NkAQLj15DHkI6XYy4B6vhap89oc8BPl24hvKbPyzHudfxvF+MM1U81GMerFcLRsJ4+hkAQjagQ30g1Vw9F6Q5hFZM18tGcQBX9UNzeHD+uhowf1gCgZ04Q454Abh7hbanTzGHjaBstDW3grGNHSBpk0O1L+Au8F2VzsnO8Y1RdfBN97c6+8J2OnP7WXUIC4ZHed4DAs/Fx2kIN6Pg/zeFSDedXdVqrJYPEQdANUhV1iOZVGDNzJCw7GPMyi8GWARIZzjBArNYGdGCoE9DoXs7C7KtzABu8upz7Wv48eBiiMZucubGGAQGENQKCUpZp1aYaBSJP/x0GOOAdRVpN12SGFmB29ymLjgBYtp+060076wH+w2eeABg3M3Z1TvCoR/vzXM0plZ3e+6jlzUU+ZUrh3Yad7Mcu4PNqd3ynp71VjVS72qcudVq/zkzxpnyqqTcArRrr0+Vktk4tTtQcJRfwedO+wqn2vDsGr2/HTpybxtXgXOCP/LKCLrO316z3KSuq9hFfxi3HfxkRjTTF28xx5jhznDn+UFFJWUVVTV3jY9P8W0dXT99AYNiIUWPGTZg0ZfpD1fwnBiEYQTGcICmaYTleEE/ny/V2fzxfb0lWVE03TMt2XM8PwihO0iwvyqpu2q4fxmlePl8IRlAMJ0iKZliO/3wFUZIVVdONn2nZjuv5QRjFSZrlRVnVTdv1wzjNy7rtx3ndz/sHEGFCGRdSacf1/CCM4iTN8qKs6qbt+mGc5mXd9uO87uf9fojSPLJ69p5mAKGomtQN07Id1/NphuX4wiKiYuISklLSMrJy8gqKSsoqqmrqGppa2jq6evoGAsNGjBozbsKkKdNmzOaPGcL/zxBBqbSxzosvhTAQyriQShvbvoqISGiqUCGVjkkjDIQyG5cOQhlXLl5Gm9hMwkAo40IqbWKzCQOhKr68ak/FRxgIE8q4+PIxYe2oAMJAqPbiS6HgVMJAKONCKm2s8+JLJwyEMi6k0sbGZRAGQsPmqyQAnKkSAY/IJgyEMi6s8+LLIQyEMi6k0sbG5RKmjAsZlceh+QgDoYwLqbSxcX0fKmPCQCjjQiptrIuXIAyEsvBOF1JpY128HDBp4nJdRM67ojiEnHO+58JKyJ7RiNtWZRIGQhkXUrW71CZVnXUrKe3ar9oWJqLmmtLdp0SEgcSXDIbpWox7n1HxpJKkjYuXIExocRPKEMw3xfVv093o2abrteXMiEa80KXJhSoZX3/GzDg7zkOW0/NVM+tiSyXtLY0hZWg9VSK0mMorAQAAAA==) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Petaluma.css b/data/Petaluma.css index fcf2a0f807f..ff8bb0e9234 100644 --- a/data/Petaluma.css +++ b/data/Petaluma.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Petaluma'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/fonts/supported.xml b/fonts/supported.xml index 695b187c50b..e899a6c2ff1 100644 --- a/fonts/supported.xml +++ b/fonts/supported.xml @@ -2236,10 +2236,10 @@ - - - - + + + + @@ -3190,11 +3190,11 @@ Medieval and Renaissance prolations supplement - - - - - + + + + + U+EE9F U+EE90 diff --git a/include/vrv/smufl.h b/include/vrv/smufl.h index 300b9559819..7ba69a44518 100644 --- a/include/vrv/smufl.h +++ b/include/vrv/smufl.h @@ -432,6 +432,10 @@ enum { SMUFL_E923_mensuralProlationCombiningThreeDotsTri = 0xE923, SMUFL_E924_mensuralProlationCombiningDotVoid = 0xE924, SMUFL_E925_mensuralProlationCombiningStroke = 0xE925, + SMUFL_E926_mensuralProportion1 = 0xE926, + SMUFL_E927_mensuralProportion2 = 0xE927, + SMUFL_E928_mensuralProportion3 = 0xE928, + SMUFL_E929_mensuralProportion4 = 0xE929, SMUFL_E938_mensuralNoteheadSemibrevisBlack = 0xE938, SMUFL_E939_mensuralNoteheadSemibrevisVoid = 0xE939, SMUFL_E93C_mensuralNoteheadMinimaWhite = 0xE93C, @@ -642,10 +646,15 @@ enum { SMUFL_ECB7_metAugmentationDot = 0xECB7, SMUFL_ED40_articSoftAccentAbove = 0xED40, SMUFL_ED41_articSoftAccentBelow = 0xED41, + SMUFL_EE90_mensuralProportion5 = 0xEE90, + SMUFL_EE91_mensuralProportion6 = 0xEE91, + SMUFL_EE92_mensuralProportion7 = 0xEE92, + SMUFL_EE93_mensuralProportion8 = 0xEE93, + SMUFL_EE94_mensuralProportion9 = 0xEE94, }; /** The number of glyphs for verification **/ -#define SMUFL_COUNT 620 +#define SMUFL_COUNT 629 } // namespace vrv From 3e321c840844c31049b4f9288d69561476d4da20 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 17 Jun 2024 14:05:29 +0200 Subject: [PATCH 269/383] Additional check for boundaries in Rest::GetRestOffsetFromOptions --- src/rest.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rest.cpp b/src/rest.cpp index ec76a9ed396..c9c3db2903b 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -540,7 +540,9 @@ int Rest::GetRestOffsetFromOptions( RestLayer layer, const std::pair &location, bool isTopLayer) const { int duration = this->GetActualDur(); - if (duration > DURATION_128) duration = DURATION_128; + // Make sure we are in the boundaries of g_defaultRests + if (duration > DUR_128) duration = DUR_128; + if (duration < DUR_LG) duration = DUR_LG; return g_defaultRests.at(layer) .at(RL_sameLayer == layer ? location.second : RA_none) .at(isTopLayer ? RLP_restOnTopLayer : RLP_restOnBottomLayer) From 1e19cec6efb02d992bee8a43590c745163e1207d Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 17 Jun 2024 07:22:40 -0700 Subject: [PATCH 270/383] Update humlib (and some coloring adjustments in iohumdrum.cpp) --- include/hum/humlib.h | 321 +++- src/hum/humlib.cpp | 3448 +++++++++++++++++++++++++++++++++++++++++- src/iohumdrum.cpp | 25 +- 3 files changed, 3702 insertions(+), 92 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 0a7e35d1a83..9e2cd80ca7d 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue May 14 03:29:40 PDT 2024 +// Last Modified: Mon Jun 17 07:10:09 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -54,6 +54,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include #include #include #include @@ -829,6 +831,7 @@ class HumAddress { int getLineNumber (void) const; int getFieldIndex (void) const; const HumdrumToken& getDataType (void) const; + HTp getExclusiveInterpretation(void); const std::string& getSpineInfo (void) const; int getTrack (void) const; int getSubtrack (void) const; @@ -1257,6 +1260,14 @@ class HumdrumLine : public std::string, public HumHash { bool isManipulator (void) const; bool hasSpines (void) const; bool isGlobal (void) const; + + int isKeySignature (int startTrack = 0, int stopTrack = 0); + int isKeyDesignation (int startTrack = 0, int stopTrack = 0); + int isTempo (int startTrack = 0, int stopTrack = 0); + int isTimeSignature (int startTrack = 0, int stopTrack = 0); + int isExpansionLabel (int startTrack = 0, int stopTrack = 0); + int isExpansionList (int startTrack = 0, int stopTrack = 0); + bool equalFieldsQ (const std::string& exinterp, const std::string& value); HTp token (int index) const; void getTokens (std::vector& list); @@ -1283,6 +1294,7 @@ class HumdrumLine : public std::string, public HumHash { std::string getXmlIdPrefix (void) const; void clearTokenLinkInfo (void); void createLineFromTokens (void); + void generateLineFromTokens (void) { createLineFromTokens(); } void removeExtraTabs (void); void addExtraTabs (std::vector& trackWidths); int getLineIndex (void) const; @@ -1508,6 +1520,7 @@ class HumdrumToken : public std::string, public HumHash { bool isNullData (void) const; bool isChord (const std::string& separator = " "); bool isLabel (void) const; + bool isExpansionLabel (void) const { return isLabel(); }; bool isExpansionList (void) const; bool hasRhythm (void) const; bool hasBeam (void) const; @@ -1703,6 +1716,7 @@ class HumdrumToken : public std::string, public HumHash { std::string getXmlIdPrefix (void) const; void setText (const std::string& text); std::string getText (void) const; + HTp getExclusiveInterpretation(void); int addLinkedParameterSet (HTp token); int getLinkedParameterSetCount(void); HumParamSet* getLinkedParameterSet (int index); @@ -2510,6 +2524,17 @@ class HumdrumFileContent : public HumdrumFileStructure { bool analyzeMensAccidentals (void); bool analyzeRScale (void); + // in HumdrumFileContent-hand.cpp + bool doHandAnalysis (bool attacksOnlyQ = false); + bool doHandAnalysis (HTp startSpine, bool attacksOnlyQ = false); + + // in HumdrumFileContent-kern.cpp + std::vector getTrackToKernIndex (void); + + // in HumdrumFileContent-midi.cpp + void fillMidiInfo(std::vector>>>& trackMidi); + void processStrandNotesForMidi(HTp sstart, HTp send, std::vector>>& trackInfo); + // in HumdrumFileContent-rest.cpp void analyzeRestPositions (void); void assignImplicitVerticalRestPositions (HTp kernstart); @@ -3133,10 +3158,12 @@ class MuseRecordBasic { void append (const char* format, ...); // mark-up accessor functions: - void setAbsBeat (HumNum value); + void setQStamp (HumNum value); void setAbsBeat (int topval, int botval = 1); + void setQStamp (int topval, int botval = 1); HumNum getAbsBeat (void); + HumNum getQStamp (void); void setLineDuration (HumNum value); void setLineDuration (int topval, int botval = 1); @@ -3159,23 +3186,29 @@ class MuseRecordBasic { int getNextTiedNoteLineIndex(void); void setLastTiedNoteLineIndex(int index); void setNextTiedNoteLineIndex(int index); + int hasTieGroupStart (void); + int isNoteAttack (void); + /* HumNum getTiedNoteDuration (void); */ std::string getLayoutVis (void); // boolean type fuctions: bool isAnyNote (void); + bool isRest (void); bool isAnyNoteOrRest (void); - bool isAttributes (void); + bool isAttributes (void); // starts with $ + bool isAttribute (void) { return isAttributes(); } bool isBackup (void); bool isBarline (void); + bool isMeasure (void) { return isBarline(); } bool isBodyRecord (void); bool isChordGraceNote (void); bool isChordNote (void); bool isDirection (void); // starts with "*" bool isMusicalDirection (void); // starts with "*" - bool isAnyComment (void); - bool isLineComment (void); - bool isBlockComment (void); + bool isAnyComment (void); // starts with "@" or between lines starting with &. + bool isLineComment (void); // starts with "@" + bool isBlockComment (void); // lines between lines starting with &. bool isCopyright (void); // 1st non-comment line in file bool isCueNote (void); // starts with "c" bool isEncoder (void); // 4th non-comment line in file @@ -3221,7 +3254,7 @@ class MuseRecordBasic { // mark-up data for the line: int m_lineindex; // index into original file int m_type; // category of MuseRecordBasic record - HumNum m_absbeat; // dur in quarter notes from start + HumNum m_qstamp; // dur in quarter notes from start HumNum m_lineduration; // duration of line HumNum m_noteduration; // duration of note @@ -3265,6 +3298,10 @@ class MuseRecord : public MuseRecordBasic { MuseRecord& operator= (MuseRecord& aRecord); + // MuseData part header information: + std::string getPartName(); + + ////////////////////////////// // // functions which process regular notes (A-G), cue notes (c), grace notes (g), @@ -3579,7 +3616,7 @@ class MuseRecord : public MuseRecordBasic { // in MuseRecord-suggestions.cpp. // - void addPrintSuggestion (int deltaIndex); + void addPrintSuggestion (int deltaIndex); bool hasPrintSuggestions (void); void getAllPrintSuggestions (std::vector& suggestions); void getPrintSuggestions (std::vector& suggestions, int column); @@ -3652,7 +3689,6 @@ class MuseData { void setFilename (const std::string& filename); std::string getFilename (void); - std::string getPartName (void); int isMember (const std::string& mstring); int getMembershipPartNumber(const std::string& mstring); void selectMembership (const std::string& selectstring); @@ -3673,6 +3709,10 @@ class MuseData { int readFile (const std::string& filename); void analyzeLayers (void); int analyzeLayersInMeasure(int startindex); + std::string getMeasureNumber (int index); + bool measureHasData (int index); + bool hasMeasureData (int index) { return measureHasData(index); } + int getNextMeasureIndex (int index); // aliases for access to MuseRecord objects based on line indexes: std::string getLine (int index); @@ -3697,6 +3737,7 @@ class MuseData { std::string getEncoder (void); std::string getEncoderDate (void); std::string getEncoderName (void); + std::string getPartName (void); std::string getId (void); std::string getMovementTitle (void); std::string getSource (void); @@ -3721,6 +3762,7 @@ class MuseData { HumNum getTiedDuration (int lindex); HumNum getAbsBeat (int lindex); + HumNum getQStamp (int lindex); HumNum getFileDuration (void); int getLineTickDuration (int lindex); @@ -3757,7 +3799,6 @@ class MuseData { void constructTimeSequence(void); void insertEventBackwards (HumNum atime, MuseRecord* arecord); int getPartNameIndex (void); - std::string getPartName (int index); void assignHeaderBodyState(void); void linkPrintSuggestions (void); void linkMusicDirections (void); @@ -3786,12 +3827,15 @@ class MuseDataSet { int readPart (std::istream& input); int readFile (const std::string& filename); int readString (const std::string& data); + int readString (std::istream& input); + int readString (std::stringstream& input); int read (std::istream& input); MuseData& operator[] (int index); int getFileCount (void); void deletePart (int index); void cleanLineEndings (void); std::vector getGroupIndexList (const std::string& group); + int appendPart (MuseData* musedata); std::string getError (void); bool hasError (void); @@ -3806,7 +3850,6 @@ class MuseDataSet { std::string m_error; protected: - int appendPart (MuseData* musedata); void analyzeSetType (std::vector& types, std::vector& lines); void analyzePartSegments (std::vector& startindex, @@ -4051,6 +4094,9 @@ class Convert { { return kernToBase7 ((std::string)*token); } static std::string kernToRecip (const std::string& kerndata); static std::string kernToRecip (HTp token); + static std::string base12ToKern (int aPitch); + static std::string base12ToPitch (int aPitch); + static int base12ToBase40 (int aPitch); static int kernToMidiNoteNumber (const std::string& kerndata); static int kernToMidiNoteNumber(HTp token) { return kernToMidiNoteNumber((std::string)*token); } @@ -4078,8 +4124,12 @@ class Convert { static int transToBase40 (const std::string& input); static int base40IntervalToLineOfFifths(int trans); static std::string keyNumberToKern (int number); + static int kernKeyToNumber (const std::string& aKernString); + static int base7ToBase40 (int base7); + static int base7ToBase12 (int aPitch, int alter = 0); static int base40IntervalToDiatonic(int base40interval); + static HumNum kernToDuration (const std::string& aKernString); // **mens, mensual notation, defiend in Convert-mens.cpp @@ -4107,39 +4157,36 @@ class Convert { static HumNum mensToDuration (HTp menstok); // older functions to enhance or remove: - static HumNum mensToDuration (const std::string& mensdata, - HumNum scale = 4, + static HumNum mensToDuration (const std::string& mensdata, HumNum scale = 4, const std::string& separator = " "); - static std::string mensToRecip (const std::string& mensdata, - HumNum scale = 4, + static std::string mensToRecip (const std::string& mensdata, HumNum scale = 4, const std::string& separator = " "); - static HumNum mensToDurationNoDots(const std::string& mensdata, - HumNum scale = 4, + static HumNum mensToDurationNoDots (const std::string& mensdata, HumNum scale = 4, const std::string& separator = " "); // MuseData conversions in Convert-musedata.cpp - static int museToBase40 (const std::string& pitchString); - static std::string musePitchToKernPitch(const std::string& museInput); - static std::string museClefToKernClef(const std::string& mclef); - static std::string museKeySigToKernKeySig(const std::string& mkeysig); - static std::string museTimeSigToKernTimeSig(const std::string& mtimesig); - static std::string museMeterSigToKernMeterSig(const std::string& mtimesig); + static int museToBase40 (const std::string& pitchString); + static std::string musePitchToKernPitch (const std::string& museInput); + static std::string museClefToKernClef (const std::string& mclef); + static std::string museKeySigToKernKeySig (const std::string& mkeysig); + static std::string museTimeSigToKernTimeSig (const std::string& mtimesig); + static std::string museMeterSigToKernMeterSig (const std::string& mtimesig); static std::string museFiguredBassToKernFiguredBass(const std::string& mfb); // Harmony processing, defined in Convert-harmony.cpp static std::vector minorHScaleBase40(void); static std::vector majorScaleBase40 (void); - static int keyToInversion (const std::string& harm); - static int keyToBase40 (const std::string& key); + static int keyToInversion (const std::string& harm); + static int keyToBase40 (const std::string& key); static std::vector harmToBase40 (HTp harm, const std::string& key) { - return harmToBase40(*harm, key); } + return harmToBase40(*harm, key); } static std::vector harmToBase40 (HTp harm, HTp key) { - return harmToBase40(*harm, *key); } + return harmToBase40(*harm, *key); } static std::vector harmToBase40 (const std::string& harm, const std::string& key); static std::vector harmToBase40 (const std::string& harm, int keyroot, int keymode); - static void makeAdjustedKeyRootAndMode(const std::string& secondary, - int& keyroot, int& keymode); - static int chromaticAlteration(const std::string& content); + static void makeAdjustedKeyRootAndMode(const std::string& secondary, + int& keyroot, int& keymode); + static int chromaticAlteration(const std::string& content); // data-type specific (other than pitch/rhythm), // defined in Convert-kern.cpp @@ -4154,35 +4201,33 @@ class Convert { static bool isKernSecondaryTiedNote (const std::string& kerndata); static std::string getKernPitchAttributes(const std::string& kerndata); - static int getKernSlurStartElisionLevel(const std::string& kerndata, int index); - static int getKernSlurEndElisionLevel (const std::string& kerndata, int index); + static int getKernSlurStartElisionLevel (const std::string& kerndata, int index); + static int getKernSlurEndElisionLevel (const std::string& kerndata, int index); static int getKernPhraseStartElisionLevel(const std::string& kerndata, int index); - static int getKernPhraseEndElisionLevel(const std::string& kerndata, int index); - - static int getKernBeamStartElisionLevel(const std::string& kerndata, int index); - static int getKernBeamEndElisionLevel (const std::string& kerndata, int index); - - + static int getKernPhraseEndElisionLevel (const std::string& kerndata, int index); + static int getKernBeamStartElisionLevel (const std::string& kerndata, int index); + static int getKernBeamEndElisionLevel (const std::string& kerndata, int index); // String processing, defined in Convert-string.cpp static std::vector splitString (const std::string& data, - char separator = ' '); - static void replaceOccurrences (std::string& source, - const std::string& search, - const std::string& replace); + char separator = ' '); + static void replaceOccurrences (std::string& source, + const std::string& search, + const std::string& replace); static std::string repeatString (const std::string& pattern, int count); static std::string encodeXml (const std::string& input); static std::string getHumNumAttributes (const HumNum& num); static std::string trimWhiteSpace (const std::string& input); - static bool startsWith (const std::string& input, - const std::string& searchstring); + static std::string generateRandomId (int length); + static bool startsWith (const std::string& input, + const std::string& searchstring); static bool contains(const std::string& input, const std::string& pattern); static bool contains(const std::string& input, char pattern); static bool contains(std::string* input, const std::string& pattern); static bool contains(std::string* input, char pattern); - static void makeBooleanTrackList(std::vector& spinelist, - const std::string& spinestring, - int maxtrack); + static void makeBooleanTrackList (std::vector& spinelist, + const std::string& spinestring, + int maxtrack); static std::vector extractIntegerList(const std::string& input, int maximum); // private functions for extractIntegerList: static void processSegmentEntry(std::vector& field, const std::string& astring, int maximum); @@ -6519,7 +6564,7 @@ class Tool_colortriads : public HumTool { int getDiatonicTransposition(HumdrumFile& infile); private: - std::vector m_colorState; + std::vector m_colorState; std::vector m_color; std::vector m_searches; std::vector m_marks; @@ -6558,7 +6603,7 @@ class Tool_composite : public HumTool { void getNumericGroupStates (std::vector& states, HumdrumFile& infile, const std::string& tgroup); int getGroupNoteType (HumdrumFile& infile, int line, const std::string& group); HumNum getLineDuration (HumdrumFile& infile, int index, - std::vector& isNull); + std::vector& isNull); void backfillGroup (std::vector>& curgroup, HumdrumFile& infile, int line, int track, int subtrack, const std::string& group); @@ -6660,7 +6705,7 @@ class Tool_composite : public HumTool { bool m_analysisOrnamentsQ = false; // used with -O option bool m_analysisSlursQ = false; // used with -S option bool m_analysisTotalQ = false; // used with -T option - std::vector m_analysisIndex; // -PAOST booleans in array + std::vector m_analysisIndex; // -PAOST booleans in array bool m_analysesQ = false; // union of -PAOST options int m_numericAnalysisSpineCount = 0; // sum of -PAOST options @@ -7811,6 +7856,43 @@ class Tool_half : public HumTool { }; +class Tool_hands : public HumTool { + public: + Tool_hands (void); + ~Tool_hands () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void colorHands (HumdrumFile& infile); + void doHandAnalysis (HumdrumFile& infile); + void doHandAnalysis (HTp startSpine); + void markNotes (HumdrumFile& infile); + void markNotes (HTp sstart, HTp send); + void removeNotes (HumdrumFile& infile, const std::string& htype); + void removeNotes (HTp sstart, HTp send, const std::string& htype); + + private: + bool m_colorQ = false; // used with -c option + std::string m_leftColor = "dodgerblue"; // used with --left-color option + std::string m_rightColor = "crimson"; // used with --right-color option + bool m_markQ = false; // used with -m option + std::string m_leftMarker = "🟦"; // + std::string m_rightMarker = "🟥"; // + bool m_leftOnlyQ = false; // used with -l option + bool m_rightOnlyQ = false; // used with -r option + bool m_attacksOnlyQ = false; // used with -a option + + std::vector m_trackHasHandMarkup; // given track number uses hand labels + +}; + + class HPNote { public: int track = -1; @@ -9983,6 +10065,145 @@ class Tool_pnum : public HumTool { + +class _VoiceInfo { + public: + std::vector> diatonic; + std::vector midibins; + std::string name; // name for instrument name of spine + std::string abbr; // abbreviation for instrument name of spine + int track; // track number for spine + bool kernQ; // is spine a **kern spine? + double hpos; // horiz. position on system for pitch range data for spine + std::vector diafinal; // finalis note diatonic pitch (4=middle-C octave) + std::vector accfinal; // finalis note accidental (0 = natural) + std::vector namfinal; // name of voice for finalis note (for "all" display) + int index = -1; + + public: + _VoiceInfo (void); + void clear (void); + std::ostream& print (std::ostream& out); + +}; + + + +class Tool_prange : public HumTool { + public: + Tool_prange (void); + ~Tool_prange () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + + void mergeAllVoiceInfo (std::vector<_VoiceInfo>& voiceInfo); + void getVoiceInfo (std::vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile); + std::string getHand (HTp sstart); + void fillHistograms (std::vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile); + void printFilenameBase (std::ostream& out, const std::string& filename); + void printReferenceRecords (std::ostream& out, HumdrumFile& infile); + void printScoreEncodedText (std::ostream& out, const std::string& strang); + void printXmlEncodedText (std::ostream& out, const std::string& strang); + void printScoreFile (std::ostream& out, std::vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile); + void printKeySigCompression (std::ostream& out, int keysig, int extra); + void assignHorizontalPosition (std::vector<_VoiceInfo>& voiceInfo, int minval, int maxval); + int getKeySignature (HumdrumFile& infile); + void printScoreVoice (std::ostream& out, _VoiceInfo& voiceInfo, double maxvalue); + void printDiatonicPitchName (std::ostream& out, int base7, int acc); + std::string getDiatonicPitchName (int base7, int acc); + void printHtmlStringEncodeSimple (std::ostream& out, const std::string& strang); + std::string getNoteTitle (double value, int diatonic, int acc); + int getDiatonicInterval (int note1, int note2); + int getTopQuartile (std::vector& midibins); + int getBottomQuartile (std::vector& midibins); + double getMaxValue (std::vector>& bins); + double getVpos (double base7); + int getStaffBase7 (int base7); + int getMaxDiatonicIndex (std::vector>& diatonic); + int getMinDiatonicIndex (std::vector>& diatonic); + int getMinDiatonicAcc (std::vector>& diatonic, int index); + int getMaxDiatonicAcc (std::vector>& diatonic, int index); + std::string getTitle (void); + void clearHistograms (std::vector >& bins, int start); + void printAnalysis (std::ostream& out, std::vector& midibins); + int getMedian12 (std::vector& midibins); + double getMean12 (std::vector& midibins); + int getTessitura (std::vector& midibins); + double countNotesInRange (std::vector& midibins, int low, int high); + void printPercentile (std::ostream& out, std::vector& midibins, double percentile); + void getRange (int& rangeL, int& rangeH, const std::string& rangestring); + void mergeFinals (std::vector<_VoiceInfo>& voiceInfo, + std::vector>& diafinal, + std::vector>& accfinal); + void getInstrumentNames (std::vector& nameByTrack, + std::vector& kernSpines, HumdrumFile& infile); + void printEmbeddedScore (std::ostream& out, std::stringstream& scoredata, HumdrumFile& infile); + void prepareRefmap (HumdrumFile& infile); + int getMaxStaffPosition (std::vector<_VoiceInfo>& voiceinfo); + int getPrangeId (HumdrumFile& infile); + void processStrandNotes (HTp sstart, HTp send, + std::vector>>& trackInfo); + void fillMidiInfo (HumdrumFile& infile); + void doExtremaMarkup (HumdrumFile& infile); + void applyMarkup (std::vector>& notelist, + const std::string& mark); + + private: + + bool m_accQ = false; // for --acc option + bool m_addFractionQ = false; // for --fraction option + bool m_allQ = false; // for --all option + bool m_debugQ = false; // for --debug option + bool m_defineQ = false; // for --score option (use text macro) + bool m_diatonicQ = false; // for --diatonic option + bool m_durationQ = false; // for --duration option + bool m_embedQ = false; // for --embed option + bool m_fillOnlyQ = false; // for --fill option + bool m_finalisQ = false; // for --finalis option + bool m_hoverQ = false; // for --hover option + bool m_instrumentQ = false; // for --instrument option + bool m_keyQ = true; // for --no-key option + bool m_listQ = false; + bool m_localQ = false; // for --local-maximum option + bool m_normQ = false; // for --norm option + bool m_notitleQ = false; // for --no-title option + bool m_percentileQ = false; // for --percentile option + bool m_pitchQ = false; // for --pitch option + bool m_printQ = false; // for --print option + bool m_quartileQ = false; // for --quartile option + bool m_rangeQ = false; // for --range option + bool m_reverseQ = false; // for --reverse option + bool m_scoreQ = false; // for --score option + bool m_titleQ = false; // for --title option + bool m_extremaQ = false; // for --extrema option + + std::string m_highMark = "🌸"; + std::string m_lowMark = "🟢"; + + double m_percentile = 50.0; // for --percentile option + std::string m_title; // for --title option + + int m_rangeL; // for --range option + int m_rangeH; // for --range option + + std::map m_refmap; + + // track >midi >tokens + std::vector>>> m_trackMidi; + + // m_trackToKernIndex: mapping from track to **kern index + std::vector m_trackToKernIndex; + +}; + + class Tool_recip : public HumTool { public: Tool_recip (void); diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 4d57aba0df3..407db92b986 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue May 14 03:29:40 PDT 2024 +// Last Modified: Mon Jun 17 07:10:09 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -1148,6 +1148,162 @@ string Convert::kernToRecip(HTp token) { +////////////////////////////// +// +// Convert::kernKeyToNumber -- convert a kern key signature into an integer. +// For example: *k[f#] == +1, *k[b-e-] == -2, *k[] == 0 +// Input string is expected to be in the form *k[] with the +// accidentals inside the brackets with no spaces. +// + +int Convert::kernKeyToNumber(const string& aKernString) { + int count = 0; + int length = (int)aKernString.size(); + int start = 0; + int sign = 1; + + if ((length == 0) || (aKernString.find("[]") != std::string::npos)) { + return 0; + } + + for (int i=0; i= (int)aKernString.size()) { + // no rhythm data found + return zero; + } + + // should now be at start of kern rhythm + int orhythm = 0; + while ((index < (int)aKernString.size()) && isdigit(aKernString[index])) { + orhythm *= 10; + orhythm += aKernString[index] - '0'; + index++; + } + + HumNum oduration(0,1); + if ((aKernString.find('0') != std::string::npos) && + (aKernString.find('1') == std::string::npos) && + (aKernString.find('2') == std::string::npos) && + (aKernString.find('3') == std::string::npos) && + (aKernString.find('4') == std::string::npos) && + (aKernString.find('5') == std::string::npos) && + (aKernString.find('6') == std::string::npos) && + (aKernString.find('7') == std::string::npos) && + (aKernString.find('8') == std::string::npos) && + (aKernString.find('9') == std::string::npos) ) { + if (aKernString.find("0000000000") != std::string::npos) { // exotic rhythm + oduration = 4096; + } else if (aKernString.find("000000000") != std::string::npos) { // exotic rhythm + oduration = 2048; + } else if (aKernString.find("00000000") != std::string::npos) { // exotic rhythm + oduration = 1024; + } else if (aKernString.find("0000000") != std::string::npos) { // exotic rhythm + oduration = 512; + } else if (aKernString.find("000000") != std::string::npos) { // exotic rhythm + oduration = 256; + } else if (aKernString.find("00000") != std::string::npos) { // exotic rhythm + oduration = 128; + } else if (aKernString.find("0000") != std::string::npos) { // exotic rhythm + oduration = 64; + } else if (aKernString.find("000") != std::string::npos) { // 000 = maxima + oduration = 32; + } else if (aKernString.find("00") != std::string::npos) { // 00 = long + oduration = 16; + } else { // 0 == breve + oduration = 8; + } + + } else { + // now know everything to create a duration + if (orhythm == 0) { + oduration = 8; + } else { + oduration = 4; + oduration /= orhythm; + } + } + + HumNum duration = oduration; + for (int i=0; i 0 + case 1: output = 2; break; // 1 = D -> 2 + case 2: output = 4; break; // 2 = E -> 4 + case 3: output = 5; break; // 3 = F -> 5 + case 4: output = 7; break; // 4 = G -> 7 + case 5: output = 9; break; // 5 = A -> 9 + case 6: output = 11; break; // 6 = B -> 11 + default: output = 0; + } + // need to add 1 to octave since C4 is MIDI note 60 which is 5 * 12: + return output + 12 * (octave+1) + alter; +} + + + ////////////////////////////// // // Convert::kernToStaffLocation -- 0 = bottom line of staff, 1 = next space higher, @@ -3720,6 +3906,143 @@ int Convert::kernToStaffLocation(const string& token, const string& clef) { +////////////////////////////// +// +// Convert::base12ToKern -- Convert MIDI note numbers to Kern pitches. +// It might be nice to also add a reference key to minimize +// diatonic pitch errors (for example 61 in A-flat major is probably +// a D-flat, but in B-major it is probably a C-sharp. +// + +string Convert::base12ToKern(int aPitch) { + int octave = aPitch / 12 - 1; + if (octave > 12 || octave < -1) { + cerr << "Error: unreasonable octave value: " << octave << endl; + cerr << "For base-12 input pitch " << aPitch << endl; + return "c"; + } + int chroma = aPitch % 12; + string output; + + switch (chroma) { + case 0: output = "c"; break; + case 1: output = "c#"; break; + case 2: output = "d"; break; + case 3: output = "e-"; break; + case 4: output = "e"; break; + case 5: output = "f"; break; + case 6: output = "f#"; break; + case 7: output = "g"; break; + case 8: output = "g#"; break; + case 9: output = "a"; break; + case 10: output = "b-"; break; + case 11: output = "b"; break; + } + + if (octave >= 4) { + output[0] = std::tolower(output[0]); + } else { + output[0] = std::toupper(output[0]); + } + int repeat = 0; + switch (octave) { + case 4: repeat = 0; break; + case 5: repeat = 1; break; + case 6: repeat = 2; break; + case 7: repeat = 3; break; + case 8: repeat = 4; break; + case 9: repeat = 5; break; + case 3: repeat = 0; break; + case 2: repeat = 1; break; + case 1: repeat = 2; break; + case 0: repeat = 3; break; + case -1: repeat = 4; break; + default: + cerr << "Error: unknown octave value: " << octave << endl; + cerr << "for base-12 pitch: " << aPitch << endl; + return "c"; + } + if (repeat == 0) { + return output; + } + + string rstring; + for (int i=0; i 12 || octave < -1) { + cerr << "Error: unreasonable octave value: " << octave << endl; + cerr << "For base-12 input pitch: " << aPitch << endl; + return "C4"; + } + int chroma = aPitch % 12; + string output; + switch (chroma) { + case 0: output = "C"; break; + case 1: output = "C#"; break; + case 2: output = "D"; break; + case 3: output = "E-"; break; + case 4: output = "E"; break; + case 5: output = "F"; break; + case 6: output = "F#"; break; + case 7: output = "G"; break; + case 8: output = "G#"; break; + case 9: output = "A"; break; + case 10: output = "B-"; break; + case 11: output = "B"; break; + } + output += to_string(octave); + return output; +} + + + +////////////////////////////// +// +// Convert::base12ToBase40 -- assume fixed accidentals. +// + +int Convert::base12ToBase40(int aPitch) { + int octave = aPitch / 12 - 1; + int chroma = aPitch % 12; + + int output = 0; + switch (chroma) { + case 0: output = 2; break; // 0 = C + case 1: output = 3; break; // 1 = C# + case 2: output = 8; break; // 2 = D + case 3: output = 13; break; // 3 = E- + case 4: output = 14; break; // 4 = E + case 5: output = 19; break; // 5 = F + case 6: output = 20; break; // 6 = F# + case 7: output = 25; break; // 7 = G + case 8: output = 30; break; // 8 = A- + case 9: output = 31; break; // 9 = A + case 10: output = 36; break; // 10 = B- + case 11: output = 37; break; // 11 = B + default: output = 2; break; // give up and set to C + } + output = output + 40 * octave; + return output; +} + @@ -5490,6 +5813,26 @@ void Convert::removeDollarsFromString(string& buffer, int maximum) { +////////////////////////////// +// +// Convert::generateRandomId -- using characters 0-9, A-Z, a-z with the +// given length. +// + +string Convert::generateRandomId(int length) { + const string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::random_device rd; // Non-deterministic generator + std::mt19937 gen(rd()); // Seed the generator + std::uniform_int_distribution<> distr(0, characters.size() - 1); + string randomId; + std::generate_n(std::back_inserter(randomId), length, [&]() { + return characters[distr(gen)]; + }); + return randomId; +} + + + @@ -9308,6 +9651,26 @@ const HumdrumToken& HumAddress::getDataType(void) const { +////////////////////////////// +// +// HumAddress::getExclusiveInterpretation -- Return the exclusive +// interpretation token of the token associated with the address. +// + +HTp HumAddress::getExclusiveInterpretation(void) { + static HumdrumToken null(""); + if (m_owner == NULL) { + return &null; + } + HumdrumToken* tok = m_owner->getTrackStart(getTrack()); + if (tok == NULL) { + return &null; + } + return tok; +} + + + ////////////////////////////// // // HumAddress::getSpineInfo -- Return the spine information for the token @@ -23789,6 +24152,147 @@ void HumdrumFileContent::markBeamSpanMembers(HTp beamstart, HTp beamend) { + +////////////////////////////// +// +// HumdrumFileContent::doHandAnalysis -- Returns true if any **kern spine has hand markup. +// default value: +// attacksOnlyQ = false; +// + +bool HumdrumFileContent::doHandAnalysis(bool attacksOnlyQ) { + HumdrumFileContent& infile = *this; + vector kstarts = infile.getKernSpineStartList(); + bool status = 0; + for (int i=0; i<(int)kstarts.size(); i++) { + status |= doHandAnalysis(kstarts[i], attacksOnlyQ); + } + return status; +} + + +bool HumdrumFileContent::doHandAnalysis(HTp startSpine, bool attacksOnlyQ) { + if (!startSpine->isKern()) { + return false; + } + bool output = false; + vector states(20); + states[0] = "none"; + HTp current = startSpine->getNextToken(); + while (current) { + int subtrack = current->getSubtrack(); + if (subtrack == 0) { + for (int i=1; i<(int)states.size(); i++) { + states[i] = states[0]; + } + } + + if (current->isInterpretation()) { + if (subtrack == 0) { + if (*current == "*LH") { + states[0] = "LH"; + output = true; + for (int i=1; i<(int)states.size(); i++) { + states[i] = states[0]; + } + } else if (*current == "*RH") { + states[0] = "RH"; + output = true; + for (int i=1; i<(int)states.size(); i++) { + states[i] = states[0]; + } + } + } else { + int ttrack = current->getTrack(); + HTp c2 = current; + while (c2) { + int track = c2->getTrack(); + if (track != ttrack) { + break; + } + int sub = c2->getSubtrack(); + if (*c2 == "*LH") { + states.at(sub) = "LH"; + if (sub == 1) { + states.at(0) = "LH"; + } + } else if (*c2 == "*RH") { + states.at(sub) = "RH"; + if (sub == 1) { + states.at(0) = "RH"; + } + } + c2 = c2->getNextFieldToken(); + } + } + } + + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + + if (subtrack == 0) { + // no subspines + if (attacksOnlyQ && current->isNoteAttack()) { + current->setValue("auto", "hand", states[0]); + } else { + current->setValue("auto", "hand", states[0]); + } + } else { + int ttrack = current->getTrack(); + HTp c2 = current; + while (c2) { + int track = c2->getTrack(); + if (track != ttrack) { + break; + } + if (attacksOnlyQ && !c2->isNoteAttack()) { + c2 = c2->getNextFieldToken(); + continue; + } + int sub = c2->getSubtrack(); + if (states.at(sub).empty()) { + c2->setValue("auto", "hand", states.at(0)); + } else { + c2->setValue("auto", "hand", states.at(sub)); + } + c2 = c2->getNextFieldToken(); + } + } + current = current->getNextToken(); + continue; + } + if (output) { + startSpine->setValue("auto", "hand", 1); + } + return output; +} + + + + + +////////////////////////////// +// +// HumdrumFileContent::getTrackToKernIndex -- return a list indexed by file track +// numbers (first entry not used), with non-zero values in the vector being +// the **kern index with the given track number. +// + +vector HumdrumFileContent::getTrackToKernIndex(void) { + HumdrumFileContent& infile = *this; + vector ktracks = infile.getKernSpineStartList(); + vector trackToKernIndex(infile.getMaxTrack() + 1, -1); + for (int i=0; i<(int)ktracks.size(); i++) { + int track = ktracks[i]->getTrack(); + trackToKernIndex[track] = i; + } + return trackToKernIndex; +} + + + ////////////////////////////// // // HumdrumFileStructure::getMetricLevels -- Each line in the output @@ -23881,6 +24385,87 @@ void HumdrumFileContent::getMetricLevels(vector& output, +///////////////////////////////// +// +// HumdrumFileContent::fillMidiInfo -- Create a data structure that +// organizes tokens by track/midi note number. +// Input object to fill is firsted indexed the **kern track +// number, then by MIDI note number, and then an array of pairs +// where int is the subtoken number in the token +// for the given MIDI note. + + +void HumdrumFileContent::fillMidiInfo(vector>>>& trackMidi) { + HumdrumFileContent& infile = *this; + vector ktracks = infile.getKernSpineStartList(); + trackMidi.clear(); + trackMidi.resize(ktracks.size()); + for (int i=0; i<(int)trackMidi.size(); i++) { + trackMidi[i].resize(128); // 0 used for rests + } + // each entry is trackMidi[track][key] is an array of pairs. + // using trackMidi[track][0] for rests; + + vector trackToKernIndex = infile.getTrackToKernIndex(); + + for (int i=0; iisKern()) { + continue; + } + + int track = sstart->getTrack(); + HTp send = infile.getStrandEnd(i); + processStrandNotesForMidi(sstart, send, trackMidi[trackToKernIndex[track]]); + } +} + + + +///////////////////////////////// +// +// HumdrumFileContent::processStrandNotesForMidi -- store strand tokens/subtokens by MIDI note +// in the midi track entry. +// +// First index if track info is the MIDI note number, second is a list +// of tokens for that note number, with the second value of the pair +// giving the subtoken index of the note in the token. +// + +void HumdrumFileContent::processStrandNotesForMidi(HTp sstart, HTp send, vector>>& trackInfo) { + HTp current = sstart->getNextToken(); + while (current && (current != send)) { + if (!current->isData() || current->isNull()) { + current = current->getNextToken(); + continue; + } + vector subtokens = current->getSubtokens(); + for (int i=0; i<(int)subtokens.size(); i++) { + if (subtokens[i] == ".") { + // something strange happened (no null tokens expected) + continue; + } + if (subtokens[i].find("r") != string::npos) { + // rest, so store in MIDI[0] + trackInfo.at(0).emplace_back(current, 0); + } else if (subtokens[i].find("R") != string::npos) { + // unpitched or quasi-pitched note, so store in MIDI[0] + trackInfo.at(0).emplace_back(current, 0); + } else { + int keyno = Convert::kernToMidiNoteNumber(subtokens[i]); + if ((keyno >= 0) && (keyno < 128)) { + trackInfo.at(keyno).emplace_back(current, i); + } + } + } + current = current->getNextToken(); + } +} + + + + + ////////////////////////////// // // HumdrumFileContent::analyzeCrossStaffStemDirections -- Calculate stem directions @@ -31711,6 +32296,191 @@ void HumdrumLine::clearTokenLinkInfo(void) { } +////////////////////////////// +// +// HumdrumLine::isKeySignature -- Returns 0 if no key signature on line, otherwise returns +// the field index + 1 of the first key signature found in the given range. If startTrack == 0, +// then start at first field, if stopTrack == 0, then end at last field. +// Default values: +// startTrack = 0 +// stopTrack = 0 +// + +int HumdrumLine::isKeySignature(int startTrack, int stopTrack) { + HumdrumLine& line = *this; + if (!line.isInterpretation()) { + return 0; + } + for (int i=0; igetTrack(); + if ((startTrack == 0) || (track >= startTrack)) { + if ((stopTrack == 0) || (track <= stopTrack)) { + if (token->isKeySignature()) { + return i+1; + } + } + } + } + return 0; +} + + + +////////////////////////////// +// +// HumdrumLine::isKeyDesignation -- Returns 0 if no key designation on line, otherwise returns +// the field index + 1 of the first key designation found in the given range. If startTrack == 0, +// then start at first field, if stopTrack == 0, then end at last field. +// Default values: +// startTrack = 0 +// stopTrack = 0 +// + +int HumdrumLine::isKeyDesignation(int startTrack, int stopTrack) { + HumdrumLine& line = *this; + if (!line.isInterpretation()) { + return 0; + } + for (int i=0; igetTrack(); + if ((startTrack == 0) || (track >= startTrack)) { + if ((stopTrack == 0) || (track <= stopTrack)) { + if (token->isKeyDesignation()) { + return i+1; + } + } + } + } + return 0; +} + + + +////////////////////////////// +// +// HumdrumLine::isTempo -- Returns 0 if no tempo on line, otherwise returns +// the field index + 1 of the first tempo found in the given range. If startTrack == 0, +// then start at first field, if stopTrack == 0, then end at last field. +// Default values: +// startTrack = 0 +// stopTrack = 0 +// + +int HumdrumLine::isTempo(int startTrack, int stopTrack) { + HumdrumLine& line = *this; + if (!line.isInterpretation()) { + return 0; + } + for (int i=0; igetTrack(); + if ((startTrack == 0) || (track >= startTrack)) { + if ((stopTrack == 0) || (track <= stopTrack)) { + if (token->isTempo()) { + return i+1; + } + } + } + } + return 0; +} + + + +////////////////////////////// +// +// HumdrumLine::isTimeSignature -- Returns 0 if no time signature on line, otherwise returns +// the field index + 1 of the first time signature found in the given range. If startTrack == 0, +// then start at first field, if stopTrack == 0, then end at last field. +// Default values: +// startTrack = 0 +// stopTrack = 0 +// + +int HumdrumLine::isTimeSignature(int startTrack, int stopTrack) { + HumdrumLine& line = *this; + if (!line.isInterpretation()) { + return 0; + } + for (int i=0; igetTrack(); + if ((startTrack == 0) || (track >= startTrack)) { + if ((stopTrack == 0) || (track <= stopTrack)) { + if (token->isTimeSignature()) { + return i+1; + } + } + } + } + return 0; +} + + + +////////////////////////////// +// +// HumdrumLine::isLabel -- Returns 0 if no label on line, otherwise returns +// the field index + 1 of the first label found in the given range. If startTrack == 0, +// then start at first field, if stopTrack == 0, then end at last field. +// Default values: +// startTrack = 0 +// stopTrack = 0 +// + +int HumdrumLine::isExpansionLabel(int startTrack, int stopTrack) { + HumdrumLine& line = *this; + if (!line.isInterpretation()) { + return 0; + } + for (int i=0; igetTrack(); + if ((startTrack == 0) || (track >= startTrack)) { + if ((stopTrack == 0) || (track <= stopTrack)) { + if (token->isExpansionLabel()) { + return i+1; + } + } + } + } + return 0; +} + + + +////////////////////////////// +// +// HumdrumLine::isLabelExpansionList -- Returns 0 if no label expansion list on line, otherwise returns +// the field index + 1 of the first label expansion list found in the given range. If startTrack == 0, +// then start at first field, if stopTrack == 0, then end at last field. +// Default values: +// startTrack = 0 +// stopTrack = 0 +// + +int HumdrumLine::isExpansionList(int startTrack, int stopTrack) { + HumdrumLine& line = *this; + if (!line.isInterpretation()) { + return 0; + } + for (int i=0; igetTrack(); + if ((startTrack == 0) || (track >= startTrack)) { + if ((stopTrack == 0) || (track <= stopTrack)) { + if (token->isExpansionList()) { + return i+1; + } + } + } + } + return 0; +} + + ////////////////////////////// // @@ -32509,6 +33279,16 @@ const string& HumdrumToken::getDataType(void) const { } +///////////////////////////// +// +// HumdrumToken::getExclusiveInterpretation -- Get the exclusive +// interpretation token that owns the given token. +// + +HTp HumdrumToken::getExclusiveInterpretation(void) { + return m_address.getExclusiveInterpretation(); +} + ////////////////////////////// // @@ -36555,7 +37335,7 @@ int MuseData::append(string& charstring) { temprec = new MuseRecord; temprec->setString(charstring); temprec->setType(E_muserec_unknown); - temprec->setAbsBeat(0); + temprec->setQStamp(0); m_data.push_back(temprec); temprec->setLineIndex((int)m_data.size() - 1); temprec->setOwner(this); @@ -36859,7 +37639,7 @@ void MuseData::processTie(int eindex, int rindex, int lastindex) { // There is another note tied to this one in the future, so // first get the absolute time location of the future tied note - HumNum abstime = m_data[lineindex]->getAbsBeat(); + HumNum abstime = m_data[lineindex]->getQStamp(); HumNum notedur = m_data[lineindex]->getNoteDuration(); HumNum searchtime = abstime + notedur; @@ -37166,7 +37946,7 @@ void MuseData::analyzeRhythm(void) { if (m_data[i]->isChordNote()) { // insert an automatic back command for chord tones // also deal with cue-size note chords? - m_data[i]->setAbsBeat(cumulative - primarychordnoteduration); + m_data[i]->setQStamp(cumulative - primarychordnoteduration); // Check to see if the secondary chord note has a duration. // If so, then set the note duration to that value; otherwise, @@ -37185,7 +37965,7 @@ void MuseData::analyzeRhythm(void) { // cumulative timestamp; instead they temporarily advance // the time placement of the next figure if it occurs // during the same note as the previous figure. - m_data[i]->setAbsBeat(cumulative + figadj); + m_data[i]->setQStamp(cumulative + figadj); HumNum tick = m_data[i]->getLineTickDuration(); if (tick == 0) { figadj = 0; @@ -37195,7 +37975,7 @@ void MuseData::analyzeRhythm(void) { figadj += dur; } } else { - m_data[i]->setAbsBeat(cumulative); + m_data[i]->setQStamp(cumulative); m_data[i]->setNoteDuration(m_data[i]->getNoteTickDuration(), tpq); m_data[i]->setLineDuration(m_data[i]->getNoteDuration()); linedur.setValue(m_data[i]->getLineTickDuration(), tpq); @@ -37215,7 +37995,7 @@ void MuseData::analyzeRhythm(void) { switch (m_data[i]->getType()) { case E_muserec_print_suggestion: case E_muserec_sound_directives: - m_data[i]->setAbsBeat(m_data[i-1]->getAbsBeat()); + m_data[i]->setQStamp(m_data[i-1]->getQStamp()); } } @@ -37226,7 +38006,9 @@ void MuseData::analyzeRhythm(void) { ////////////////////////////// // // MuseData::getInitialTpq -- return the Q: field in the first $ record -// at the top of the file. +// at the top of the file. If there is an updated Q: field, this +// function will need to be improved since TPQ cannot change in MIDI files, +// for example. // int MuseData::getInitialTpq(void) { @@ -37279,7 +38061,7 @@ void MuseData::constructTimeSequence(void) { MuseData& thing = *this; for (int i=0; i<(int)m_data.size(); i++) { - insertEventBackwards(thing[i].getAbsBeat(), &thing[i]); + insertEventBackwards(thing[i].getQStamp(), &thing[i]); if (hasError()) { return; } @@ -37510,13 +38292,18 @@ int MuseData::getType(int eindex, int erecord) { ////////////////////////////// // -// MuseData::getAbsBeat -- return the absolute beat time (quarter +// MuseData::getQStamp -- return the absolute beat time (quarter // note durations from the start of the music until the current // object. // +HumNum MuseData::getQStamp(int lindex) { + return m_data[lindex]->getQStamp(); +} + + HumNum MuseData::getAbsBeat(int lindex) { - return m_data[lindex]->getAbsBeat(); + return m_data[lindex]->getQStamp(); } @@ -37590,11 +38377,7 @@ string MuseData::getPartName(void) { if (line < 0) { return ""; } - HumRegex hre; - string output = m_data[line]->getLine(); - hre.replaceDestructive(output, "", "^\\s+"); - hre.replaceDestructive(output, "", "\\s+$"); - return output; + return m_data[line]->getPartName(); } @@ -37769,7 +38552,7 @@ void MuseData::setError(const string& error) { // HumNum MuseData::getFileDuration(void) { - return getRecord(getLineCount()-1).getAbsBeat(); + return getRecord(getLineCount()-1).getQStamp(); } @@ -38985,6 +39768,114 @@ void MuseData::linkMusicDirections(void) { } +////////////////////////////// +// +// MuseData::getMeasureNumber -- If index == 0, return the next barnumber +// minus 1. If on a measure record, return the number on that line. +// If neither, then search backwards for the last measure line (or 0 +// index) and return the measure number for that barline (or 0 index). +// + +string MuseData::getMeasureNumber(int index) { + MuseData& md = *this; + if ((index > 0) && !md[index].isMeasure()) { + for (int i=index-1; i>= 0; i--) { + if (md[i].isMeasure()) { + index = i; + break; + } + } + } + if ((index != 0) && !md[index].isMeasure()) { + index = 0; + } + if (index == 0) { + // search for the first measure, and return + // that number. If there are notes before the + // first barline, return the next measure number + // minus 1. + bool dataQ = false; + for (int i=0; i datalines; datalines.reserve(100000); @@ -40089,6 +40995,28 @@ string MuseRecord::getFigure(int index) { +////////////////////////////// +// +// MuseRecord::getPartName -- return the name line in the +// MuseData part's header. This is the 9th non-comment +// line in a part file. +// + +string MuseRecord::getPartName(void) { + if (isPartName()) { + HumRegex hre; + string raw = this->getLine(); + hre.replaceDestructive(raw, "", "^\\s+"); + hre.replaceDestructive(raw, "", "\\s+$"); + return raw; + } else { + return ""; + } +} + + + + ////////////////////////////// // // MuseRecord::getKernBeamStyle -- @@ -43241,7 +44169,7 @@ MuseRecord& MuseRecord::operator=(MuseRecord& aRecord) { setType(aRecord.getType()); m_lineindex = aRecord.m_lineindex; - m_absbeat = aRecord.m_absbeat; + m_qstamp = aRecord.m_qstamp; m_lineduration = aRecord.m_lineduration; m_noteduration = aRecord.m_noteduration; @@ -43934,9 +44862,6 @@ void MuseRecord::getAllPrintSuggestions(vector& suggestions) { - - - ////////////////////////////// // // MuseRecordBasic::isPartName -- @@ -44382,7 +45307,7 @@ MuseRecordBasic::MuseRecordBasic(void) { setType(E_muserec_unknown); m_owner = NULL; m_lineindex = -1; - m_absbeat = 0; + m_qstamp = 0; m_lineduration = 0; m_noteduration = 0; m_b40pitch = -100; @@ -44401,7 +45326,7 @@ MuseRecordBasic::MuseRecordBasic(const string& aLine, int index) { setType(E_muserec_unknown); m_lineindex = index; m_owner = NULL; - m_absbeat = 0; + m_qstamp = 0; m_lineduration = 0; m_noteduration = 0; m_b40pitch = -100; @@ -44428,7 +45353,7 @@ MuseRecordBasic::~MuseRecordBasic() { m_recordString.resize(0); m_owner = NULL; m_lineindex = -1; - m_absbeat = 0; + m_qstamp = 0; m_lineduration = 0; m_noteduration = 0; m_b40pitch = -100; @@ -44448,7 +45373,7 @@ MuseRecordBasic::~MuseRecordBasic() { void MuseRecordBasic::clear(void) { m_recordString.clear(); m_owner = NULL; - m_absbeat = 0; + m_qstamp = 0; m_lineindex = -1; m_lineduration = 0; m_noteduration = 0; @@ -44631,7 +45556,7 @@ MuseRecordBasic& MuseRecordBasic::operator=(MuseRecordBasic& aRecord) { setType(aRecord.getType()); m_lineindex = aRecord.m_lineindex; - m_absbeat = aRecord.m_absbeat; + m_qstamp = aRecord.m_qstamp; m_lineduration = aRecord.m_lineduration; m_noteduration = aRecord.m_noteduration; @@ -44654,7 +45579,7 @@ MuseRecordBasic& MuseRecordBasic::operator=(const string& aLine) { setType(aLine[0]); m_lineindex = -1; - m_absbeat = 0; + m_qstamp = 0; m_lineduration = 0; m_noteduration = 0; m_b40pitch = -100; @@ -44906,24 +45831,39 @@ void MuseRecordBasic::setString(string& astring) { void MuseRecordBasic::setAbsBeat(HumNum value) { - m_absbeat = value; + m_qstamp = value; +} + + +void MuseRecordBasic::setQStamp(HumNum value) { + m_qstamp = value; } // default value botval = 1 void MuseRecordBasic::setAbsBeat(int topval, int botval) { - m_absbeat.setValue(topval, botval); + m_qstamp.setValue(topval, botval); +} + + +void MuseRecordBasic::setQStamp(int topval, int botval) { + m_qstamp.setValue(topval, botval); } ////////////////////////////// // -// MuseRecordBasic::getAbsBeat -- +// MuseRecordBasic::getAbsBeat -- Quarter notes from the +// start of the music. // HumNum MuseRecordBasic::getAbsBeat(void) { - return m_absbeat; + return m_qstamp; +} + +HumNum MuseRecordBasic::getQStamp(void) { + return m_qstamp; } @@ -45050,6 +45990,48 @@ int MuseRecordBasic::getNextTiedNoteLineIndex(void) { +////////////////////////////// +// +// MuseRecordBasic::hasTieGroupStart -- Return true if note is +// at the start of a tied group. +// + +int MuseRecordBasic::hasTieGroupStart(void) { + if (getLastTiedNoteLineIndex() > 0) { + // Note is in the middle of a tie group. + return 0; + } + if (getNextTiedNoteLineIndex() > 0) { + return 1; + } else { + // no tie on note. + return 0; + } +} + + + +////////////////////////////// +// +// MuseRecordBasic::isNoteAttack -- Return true if note is +// at the start of a tied group or is a regular note. +// + +int MuseRecordBasic::isNoteAttack(void) { + if (getLastTiedNoteLineIndex() > 0) { + // Note is in the middle of a tie group. + return 0; + } + if (getNextTiedNoteLineIndex() > 0) { + return 1; + } else { + // no tie on note. + return 1; + } +} + + + ////////////////////////////// // // MuseRecordBasic::setLastTiedNoteLineIndex -- @@ -51527,7 +52509,13 @@ int Options::getRegIndex(const string& optionName) { if (it == m_optionList.end()) { if (m_options_error_checkQ) { m_error << "Error: unknown option \"" << optionName << "\"." << endl; - print(cout); + #ifndef __EMSCRIPTEN__ + cerr << "Error: unknown option \"" << optionName << "\"." << endl; + #endif + print(cerr); + #ifndef __EMSCRIPTEN__ + exit(1); + #endif return -1; } else { return -1; @@ -66167,9 +67155,9 @@ void Tool_composite::analyzeFullCompositeRhythm(HumdrumFile& infile) { durations[i] = infile[i].getDuration(); } - vector isRest(infile.getLineCount(), false); - vector isNull(infile.getLineCount(), false); - vector isSustain(infile.getLineCount(), false); + vector isRest(infile.getLineCount(), false); + vector isNull(infile.getLineCount(), false); + vector isSustain(infile.getLineCount(), false); for (int i=0; i isRest(infile.getLineCount(), false); - vector isNull(infile.getLineCount(), false); + vector isRest(infile.getLineCount(), false); + vector isNull(infile.getLineCount(), false); for (int i=0; i>& groupstates, HumdrumFil // 0 if the line only contains nulls. Also add the duration of any subsequent // lines that are null lines before any data content lines. -HumNum Tool_composite::getLineDuration(HumdrumFile& infile, int index, vector& isNull) { +HumNum Tool_composite::getLineDuration(HumdrumFile& infile, int index, vector& isNull) { if (isNull[index]) { return 0; } @@ -82118,6 +83106,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(gasparize, infile, commands[i].second, status); } else if (commands[i].first == "half") { RUNTOOL(half, infile, commands[i].second, status); + } else if (commands[i].first == "hands") { + RUNTOOL(hands, infile, commands[i].second, status); } else if (commands[i].first == "homorhythm") { RUNTOOL(homorhythm, infile, commands[i].second, status); } else if (commands[i].first == "homorhythm2") { @@ -82160,6 +83150,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(phrase, infile, commands[i].second, status); } else if (commands[i].first == "pline") { RUNTOOL(pline, infile, commands[i].second, status); + } else if (commands[i].first == "prange") { + RUNTOOL(prange, infile, commands[i].second, status); } else if (commands[i].first == "recip") { RUNTOOL(recip, infile, commands[i].second, status); } else if (commands[i].first == "restfill") { @@ -85071,6 +86063,254 @@ void Tool_half::terminalLongToTerminalBreve(HumdrumFile& infile) { +///////////////////////////////// +// +// Tool_hands::Tool_hands -- Set the recognized options for the tool. +// + +Tool_hands::Tool_hands(void) { + define("c|color=b", "color right-hand notes red and left-hand notes blue"); + define("lcolor|left-color=s:dodgerblue", "color of left-hand notes"); + define("rcolor|right-color=s:crimson", "color of right-hand notes"); + define("l|left-only=b", "remove right-hand notes"); + define("r|right-only=b", "remove left-hand notes"); + define("m|mark=b", "mark left and right-hand notes"); + define("a|attacks-only=b", "only mark note attacks and not note sustains"); +} + + + +////////////////////////////// +// +// Tool_hands::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. +// + +void Tool_hands::initialize(void) { + m_colorQ = getBoolean("color"); + m_leftColor = getString("left-color"); + m_rightColor = getString("right-color"); + m_leftOnlyQ = getBoolean("left-only"); + m_rightOnlyQ = getBoolean("right-only"); + m_markQ = getBoolean("mark"); + m_attacksOnlyQ = getBoolean("attacks-only"); +} + + + +///////////////////////////////// +// +// Tool_hands::run -- Do the main work of the tool. +// + +bool Tool_hands::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; igetExclusiveInterpretation(); + int hasHandMarkup = xtok->getValueInt("auto", "hand"); + if (!hasHandMarkup) { + continue; + } + HTp send = infile.getStrandEnd(i); + removeNotes(sstart, send, htype); + counter++; + } + + + if (counter) { + infile.createLinesFromTokens(); + } +} + + +void Tool_hands::removeNotes(HTp sstart, HTp send, const string& htype) { + HTp current = sstart; + while (current && (current != send)) { + if (!current->isData() || current->isNull()) { + current = current->getNextToken(); + continue; + } + + HumRegex hre; + string ttype = current->getValue("auto", "hand"); + if (ttype != htype) { + current = current->getNextToken(); + continue; + } + string text = *current; + hre.replaceDestructive(text, "", "[^0-9.%q ]", "g"); + hre.replaceDestructive(text, "ryy ", " ", "g"); + text += "ryy"; + current->setText(text); + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_hands::markNotes -- +// + +void Tool_hands::markNotes(HumdrumFile& infile) { + HumRegex hre; + + int counter = 0; + int scount = infile.getStrandCount(); + for (int i=0; igetExclusiveInterpretation(); + int hasHandMarkup = xtok->getValueInt("auto", "hand"); + if (!hasHandMarkup) { + continue; + } + HTp send = infile.getStrandEnd(i); + markNotes(sstart, send); + counter++; + } + + if (counter) { + infile.appendLine("!!!RDF**kern: " + m_leftMarker + " = marked note, color=\"" + m_leftColor + "\", left-hand note"); + infile.appendLine("!!!RDF**kern: " + m_rightMarker + " = marked note, color=\"" + m_rightColor + "\", right-hand note"); + infile.createLinesFromTokens(); + } +} + + +void Tool_hands::markNotes(HTp sstart, HTp send) { + HTp current = sstart; + while (current && (current != send)) { + if (!current->isData() || current->isNull() || current->isRest()) { + current = current->getNextToken(); + continue; + } + + HumRegex hre; + string text = *current; + string htype = current->getValue("auto", "hand"); + if (htype == "LH") { + hre.replaceDestructive(text, " " + m_leftMarker, " +", "g"); + text = m_leftMarker + text; + } else if (htype == "RH") { + hre.replaceDestructive(text, " " + m_rightMarker, " +", "g"); + text = m_rightMarker + text; + } + current->setText(text); + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_hands::colorHands -- Convert for example *LH into *color:dodgerblue. +// + +void Tool_hands::colorHands(HumdrumFile& infile) { + string left = "*color:" + m_leftColor; + string right = "*color:" + m_rightColor; + for (int i=0; iisKern()) { + continue; + } + if (*token == "*LH") { + token->setText(left); + changed = true; + } + if (*token == "*RH") { + token->setText(right); + changed = true; + } + } + if (changed) { + infile[i].createLineFromTokens(); + } + } +} + + + + ///////////////////////////////// // // Tool_homorhythm::Tool_homorhythm -- Set the recognized options for the tool. @@ -113631,6 +114871,2136 @@ string Tool_pnum::convertSubtokenToBase(const string& text) { +#define OBJTAB "\t\t\t\t\t\t" +#define SVGTAG "_99%svg%"; + +#define SVGTEXT(out, text) \ + if (m_defineQ) { \ + out << "SVG "; \ + } else { \ + out << "t 1 1\n"; \ + out << SVGTAG; \ + } \ + printScoreEncodedText((out), (text)); \ + out << "\n"; + + +////////////////////////////// +// +// _VoiceInfo::_VoiceInfo -- +// + +_VoiceInfo::_VoiceInfo(void) { + clear(); +} + + + +////////////////////////////// +// +// _VoiceInfo::clear -- +// + +void _VoiceInfo::clear(void) { + name = ""; + abbr = ""; + midibins.resize(128); + fill(midibins.begin(), midibins.end(), 0.0); + diatonic.resize(7 * 12); + for (int i=0; i<(int)diatonic.size(); i++) { + diatonic[i].resize(6); + fill(diatonic[i].begin(), diatonic[i].end(), 0.0); + } + track = -1; + kernQ = false; + diafinal.clear(); + accfinal.clear(); + namfinal.clear(); + index = -1; +} + + +////////////////////////////// +// +// _VoiceInfo::print -- +// + +ostream& _VoiceInfo::print(ostream& out) { + out << "==================================" << endl; + out << "track: " << track << endl; + out << " name: " << name << endl; + out << " abbr: " << abbr << endl; + out << " kern: " << kernQ << endl; + out << " final:"; + for (int i=0; i<(int)diafinal.size(); i++) { + out << " " << diafinal.at(i) << "/" << accfinal.at(i); + } + out << endl; + out << " midi: "; + for (int i=0; i<(int)midibins.size(); i++) { + if (midibins.at(i) > 0.0) { + out << " " << i << ":" << midibins.at(i); + } + } + out << endl; + out << " diat: "; + for (int i=0; i<(int)diatonic.size(); i++) { + if (diatonic.at(i).at(0) > 0.0) { + out << " " << i << ":" << diatonic.at(i).at(0); + } + } + out << endl; + out << "==================================" << endl; + return out; +} + + + +///////////////////////////////// +// +// Tool_prange::Tool_prange -- Set the recognized options for the tool. +// + +Tool_prange::Tool_prange(void) { + + define("A|acc|color-accidentals=b", "add color to accidentals in histogram"); + define("D|diatonic=b", "diatonic counts ignore chormatic alteration"); + define("K|no-key=b", "do not display key signature"); + define("N|norm=b", "normalize pitch counts"); + define("S|score=b", "convert range info to SCORE"); + define("T|no-title=b", "do not display a title"); + define("a|all=b", "generate all-voice analysis"); + define("c|range|count=s:60-71", "count notes in a particular MIDI note number range (inclusive)"); + define("debug=b", "trace input parsing"); + define("d|duration=b", "weight pitches by duration"); + define("e|embed=b", "embed SCORE data in input Humdrum data"); + define("fill=b", "change color of fill only"); + define("finalis|final|last=b", "include finalis note by voice"); + define("f|fraction=b", "display histogram fractions"); + define("h|hover=b", "include svg hover capabilities"); + define("i|instrument=b", "categorize multiple inputs by instrument"); + define("j|jrp=b", "set options for JRP style"); + define("l|local|local-maximum|local-maxima=b", "use maximum values by voice rather than all voices"); + define("no-define=b", "do not use defines in output SCORE data"); + define("pitch=b", "display pitch info in **pitch format"); + define("print=b", "count printed notes rather than sounding"); + define("p|percentile=d:0.0", "display the xth percentile pitch"); + define("q|quartile=b", "display quartile notes"); + define("r|reverse=b", "reverse list of notes in analysis from high to low"); + define("x|extrema=b", "highlight extrema notes in each part"); + define("sx|scorexml|score-xml|ScoreXML|scoreXML=b", "output ScoreXML format"); + define("title=s:", "title for SCORE display"); + +} + + +///////////////////////////////// +// +// Tool_prange::run -- Do the main work of the tool. +// + +bool Tool_prange::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i 1) { + m_percentile = m_percentile / 100.0; + } + + #ifdef __EMSCRIPTEN__ + // Default styling for JavaScript version of program: + m_accQ = !getBoolean("color-accidentals"); + m_scoreQ = !getBoolean("score"); + m_embedQ = !getBoolean("embed"); + m_hoverQ = !getBoolean("hover"); + m_notitleQ = !getBoolean("no-title"); + #endif +} + + + +////////////////////////////// +// +// Tool_prange::processFile -- +// + +void Tool_prange::processFile(HumdrumFile& infile) { + prepareRefmap(infile); + vector<_VoiceInfo> voiceInfo; + infile.fillMidiInfo(m_trackMidi); + getVoiceInfo(voiceInfo, infile); + fillHistograms(voiceInfo, infile); + + if (m_debugQ) { + for (int i=0; i<(int)voiceInfo.size(); i++) { + voiceInfo[i].print(cerr); + } + } + + if (m_scoreQ) { + stringstream scoreout; + printScoreFile(scoreout, voiceInfo, infile); + if (m_embedQ) { + if (m_extremaQ) { + doExtremaMarkup(infile); + } + m_humdrum_text << infile; + printEmbeddedScore(m_humdrum_text, scoreout, infile); + } else { + if (m_extremaQ) { + doExtremaMarkup(infile); + } + m_humdrum_text << scoreout.str(); + } + } else { + printAnalysis(m_humdrum_text, voiceInfo[0].midibins); + } +} + + + +////////////////////////////// +// +// Tool_prange::doExtremaMarkup -- Mark highest and lowest note +// in each **kern spine. +// +// + +void Tool_prange::doExtremaMarkup(HumdrumFile& infile) { + bool highQ = false; + bool lowQ = false; + for (int i=0; i<(int)m_trackMidi.size(); i++) { + int maxindex = -1; + int minindex = -1; + + for (int j=(int)m_trackMidi[i].size()-1; j>=0; j--) { + if (m_trackMidi[i][j].empty()) { + continue; + } + if (maxindex < 0) { + maxindex = j; + break; + } + } + + for (int j=1; j<(int)m_trackMidi[i].size(); j++) { + if (m_trackMidi[i][j].empty()) { + continue; + } + if (minindex < 0) { + minindex = j; + break; + } + } + + if ((maxindex < 0) || (minindex < 0)) { + continue; + } + applyMarkup(m_trackMidi[i][maxindex], m_highMark); + applyMarkup(m_trackMidi[i][minindex], m_lowMark); + highQ = true; + lowQ = true; + } + if (highQ) { + string highRdf = "!!!RDF**kern: " + m_highMark + " = marked note, color=\"hotpink\", highest note"; + infile.appendLine(highRdf); + } + if (lowQ) { + string lowRdf = "!!!RDF**kern: " + m_lowMark + " = marked note, color=\"limegreen\", lowest note"; + infile.appendLine(lowRdf); + } + if (highQ || lowQ) { + infile.createLinesFromTokens(); + } +} + + + +////////////////////////////// +// +// Tool_prange::applyMarkup -- +// + +void Tool_prange::applyMarkup(vector>& notelist, const string& mark) { + for (int i=0; i<(int)notelist.size(); i++) { + HTp token = notelist[i].first; + int subtoken = notelist[i].second; + int tokenCount = token->getSubtokenCount(); + if (tokenCount == 1) { + string text = *token; + text += mark; + token->setText(text); + } else { + string stok = token->getSubtoken(subtoken); + stok = mark + stok; + token->replaceSubtoken(subtoken, stok); + } + } +} + + + +////////////////////////////// +// +// Tool_prange::printEmbeddedScore -- +// + +void Tool_prange::printEmbeddedScore(ostream& out, stringstream& scoredata, HumdrumFile& infile) { + int id = getPrangeId(infile); + + out << "!!@@BEGIN: PREHTML\n"; + out << "!!@CONTENT:

\n"; + out << "!!@@END: PREHTML\n"; + out << "!!@@BEGIN: SCORE\n"; + out << "!!@ID: prange-" << id << "\n"; + out << "!!@OUTPUTFORMAT: svg\n"; + out << "!!@CROP: yes\n"; + out << "!!@PADDING: 10\n"; + out << "!!@SCALING: 1.5\n"; + out << "!!@SVGFORMAT: yes\n"; + out << "!!@TRANSPARENT: yes\n"; + out << "!!@ANTIALIAS: no\n"; + out << "!!@EMBEDPMX: yes\n"; + out << "!!@ANNOTATE: no\n"; + out << "!!@CONTENTS:\n"; + string line; + while(getline(scoredata, line)) { + out << "!!" << line << endl; + } + out << "!!@@END: SCORE\n"; +} + + + +////////////////////////////// +// +// Tool_prange::getPrangeId -- Find a line in this form +// ^!!@ID: prange-(\d+)$ +// and return $1+1. Searching backwards since the HTML section +// will likely be at the bottom. Assuming that the prange +// SVG images are stored in sequence, with the highest ID last +// in the file if there are more than one. +// + +int Tool_prange::getPrangeId(HumdrumFile& infile) { + string search = "!!@ID: prange-"; + int length = (int)search.length(); + for (int i=infile.getLineCount() - 1; i>=0; i--) { + HTp token = infile.token(i, 0); + if (token->compare(0, length, search) == 0) { + HumRegex hre; + if (hre.search(token, "prange-(\\d+)")) { + return hre.getMatchInt(1) + 1; + } + } + } + return 1; +} + + + +////////////////////////////// +// +// Tool_prange::mergeAllVoiceInfo -- +// + +void Tool_prange::mergeAllVoiceInfo(vector<_VoiceInfo>& voiceInfo) { + voiceInfo.at(0).diafinal.clear(); + voiceInfo.at(0).accfinal.clear(); + + for (int i=1; i<(int)voiceInfo.size(); i++) { + if (!voiceInfo[i].kernQ) { + continue; + } + for (int j=0; j<(int)voiceInfo.at(i).diafinal.size(); j++) { + voiceInfo.at(0).diafinal.push_back(voiceInfo.at(i).diafinal.at(j)); + voiceInfo.at(0).accfinal.push_back(voiceInfo.at(i).accfinal.at(j)); + voiceInfo.at(0).namfinal.push_back(voiceInfo.at(i).name); + } + + for (int j=0; j<(int)voiceInfo[i].midibins.size(); j++) { + voiceInfo[0].midibins[j] += voiceInfo[i].midibins[j]; + } + + for (int j=0; j<(int)voiceInfo.at(i).diatonic.size(); j++) { + for (int k=0; k<(int)voiceInfo.at(i).diatonic.at(k).size(); k++) { + voiceInfo[0].diatonic.at(j).at(k) += voiceInfo.at(i).diatonic.at(j).at(k); + } + } + } +} + + + +////////////////////////////// +// +// Tool_prange::getVoiceInfo -- get names and track info for **kern spines. +// + +void Tool_prange::getVoiceInfo(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { + voiceInfo.clear(); + voiceInfo.resize(infile.getMaxTracks() + 1); + for (int i=0; i<(int)voiceInfo.size(); i++) { + voiceInfo.at(i).index = i; + } + + vector kstarts = infile.getKernSpineStartList(); + + if (kstarts.size() == 2) { + voiceInfo[0].name = "both"; + voiceInfo[0].abbr = "both"; + voiceInfo[0].track = 0; + } else { + voiceInfo[0].name = "all"; + voiceInfo[0].abbr = "all"; + voiceInfo[0].track = 0; + } + + + for (int i=0; igetTrack(); + voiceInfo[track].track = track; + if (token->isKern()) { + voiceInfo[track].kernQ = true; + } + if (!voiceInfo[track].kernQ) { + continue; + } + if (token->isInstrumentName()) { + voiceInfo[track].name = token->getInstrumentName(); + } + if (token->isInstrumentAbbreviation()) { + voiceInfo[track].abbr = token->getInstrumentAbbreviation(); + } + } + } + + + // Check for piano/Grand Staff parts with LH/RH encoding. + if (kstarts.size() == 2) { + string bottomStaff = getHand(kstarts[0]); + string topStaff = getHand(kstarts[1]); + if (!bottomStaff.empty() && !topStaff.empty()) { + int track = kstarts[0]->getTrack(); + voiceInfo[track].name = "left hand"; + track = kstarts[1]->getTrack(); + voiceInfo[track].name = "right hand"; + } + } +} + + + +////////////////////////////// +// +// Tool_prange::getHand -- +// + +string Tool_prange::getHand(HTp sstart) { + HTp current = sstart->getNextToken(); + HTp target = NULL; + while (current) { + if (current->isData()) { + break; + } + if (*current == "*LH") { + target = current; + break; + } + if (*current == "*RH") { + target = current; + break; + } + current = current->getNextToken(); + } + + if (target) { + if (*current == "*LH") { + return "LH"; + } else if (*current == "*RH") { + return "RH"; + } else { + return ""; + } + } else { + return ""; + } +} + + + +////////////////////////////// +// +// Tool_prange::getInstrumentNames -- Find any instrument names which are listed +// before the first data line. Instrument names are in the form: +// +// *I"name +// + +void Tool_prange::getInstrumentNames(vector& nameByTrack, vector& kernSpines, + HumdrumFile& infile) { + HumRegex hre; + + int track; + string name; + // nameByTrack.resize(kernSpines.size()); + nameByTrack.resize(infile.getMaxTrack() + 1); + fill(nameByTrack.begin(), nameByTrack.end(), ""); + vector kspines = infile.getKernSpineStartList(); + if (kspines.size() == 2) { + nameByTrack.at(0) = "both"; + } else { + nameByTrack.at(0) = "all"; + } + + for (int i=0; igetTrack(); + for (int k=0; k<(int)kernSpines.size(); k++) { + if (track == kernSpines[k]) { + nameByTrack[k] = name; + } + } + } + } + } +} + + + +////////////////////////////// +// +// Tool_prange::fillHistograms -- Store notes in score by MIDI note number. +// + +void Tool_prange::fillHistograms(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { + // storage for finals info: + vector> diafinal; + vector> accfinal; + diafinal.resize(infile.getMaxTracks() + 1); + accfinal.resize(infile.getMaxTracks() + 1); + + for (int i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + int track = token->getTrack(); + + diafinal.at(track).clear(); + accfinal.at(track).clear(); + + vector tokens = token->getSubtokens(); + for (int k=0; k<(int)tokens.size(); k++) { + if (tokens[k].find("r") != string::npos) { + continue; + } + if (tokens[k].find("R") != string::npos) { + // non-pitched note + continue; + } + bool hasPitch = false; + for (int m=0; m<(int)tokens[k].size(); m++) { + char test = tokens[k].at(m); + if (!isalpha(test)) { + continue; + } + test = tolower(test); + if ((test >= 'a') && (test <= 'g')) { + hasPitch = true; + break; + } + } + if (!hasPitch) { + continue; + } + int octave = Convert::kernToOctaveNumber(tokens[k]) + 3; + if (octave < 0) { + cerr << "Note too low: " << tokens[k] << endl; + continue; + } + if (octave >= 12) { + cerr << "Note too high: " << tokens[k] << endl; + continue; + } + int dpc = Convert::kernToDiatonicPC(tokens[k]); + int acc = Convert::kernToAccidentalCount(tokens[k]); + if (acc < -2) { + cerr << "Accidental too flat: " << tokens[k] << endl; + continue; + } + if (acc > +2) { + cerr << "Accidental too sharp: " << tokens[k] << endl; + continue; + } + int diatonic = dpc + 7 * octave; + int realdiatonic = dpc + 7 * (octave-3); + + diafinal.at(track).push_back(realdiatonic); + accfinal.at(track).push_back(acc); + + acc += 3; + int midi = Convert::kernToMidiNoteNumber(tokens[k]); + if (midi < 0) { + cerr << "MIDI pitch too low: " << tokens[k] << endl; + } + if (midi > 127) { + cerr << "MIDI pitch too high: " << tokens[k] << endl; + } + if (m_durationQ) { + double duration = Convert::kernToDuration(tokens[k]).getFloat(); + voiceInfo[track].diatonic.at(diatonic).at(0) += duration; + voiceInfo[track].diatonic.at(diatonic).at(acc) += duration; + voiceInfo[track].midibins.at(midi) += duration; + } else { + if (tokens[k].find("]") != string::npos) { + continue; + } + if (tokens[k].find("_") != string::npos) { + continue; + } + voiceInfo[track].diatonic.at(diatonic).at(0)++; + voiceInfo[track].diatonic.at(diatonic).at(acc)++; + voiceInfo[track].midibins.at(midi)++; + } + } + } + } + + mergeFinals(voiceInfo, diafinal, accfinal); + + // Sum all voices into midibins and diatonic arrays of vector position 0: + mergeAllVoiceInfo(voiceInfo); +} + + + +////////////////////////////// +// +// Tool_prange::mergeFinals -- +// + +void Tool_prange::mergeFinals(vector<_VoiceInfo>& voiceInfo, vector>& diafinal, + vector>& accfinal) { + for (int i=0; i<(int)voiceInfo.size(); i++) { + voiceInfo.at(i).diafinal = diafinal.at(i); + voiceInfo.at(i).accfinal = accfinal.at(i); + } +} + + + +////////////////////////////// +// +// Tool_prange::printFilenameBase -- +// + +void Tool_prange::printFilenameBase(ostream& out, const string& filename) { + HumRegex hre; + if (hre.search(filename, "([^/]+)\\.([^.]*)", "")) { + if (hre.getMatch(1).size() <= 8) { + printXmlEncodedText(out, hre.getMatch(1)); + } else { + // problem with too long a name (MS-DOS will have problems). + // optimize to chop off everything after the dash in the + // name (for Josquin catalog numbers). + string shortname = hre.getMatch(1); + if (hre.search(shortname, "-.*")) { + hre.replaceDestructive(shortname, "", "-.*"); + printXmlEncodedText(out, shortname); + } else { + printXmlEncodedText(out, shortname); + } + } + } +} + + + +////////////////////////////// +// +// Tool_prange::printReferenceRecords -- +// + +void Tool_prange::printReferenceRecords(ostream& out, HumdrumFile& infile) { + for (int i=0; i\n"; + } +} + + + +////////////////////////////// +// +// Tool_prange::printScoreEncodedText -- print SCORE text string +// See SCORE 3.1 manual additions (page 19) for more. +// + +void Tool_prange::printScoreEncodedText(ostream& out, const string& strang) { + string newstring = strang; + HumRegex hre; + + hre.replaceDestructive(newstring, "<<$1", "&([aeiou])acute;", "gi"); + hre.replaceDestructive(newstring, "<<$1", "([áéíóú])", "gi"); + + hre.replaceDestructive(newstring, ">>$1", "&([aeiou])grave;", "gi"); + hre.replaceDestructive(newstring, ">>$1", "([àèìòù])", "gi"); + + hre.replaceDestructive(newstring, "%%$1", "&([aeiou])uml;", "gi"); + hre.replaceDestructive(newstring, "%%$1", "([äëïöü])", "gi"); + + hre.replaceDestructive(newstring, "^^$1", "&([aeiou])circ;", "gi"); + hre.replaceDestructive(newstring, "^^$1", "([âêîôû])", "gi"); + + hre.replaceDestructive(newstring, "##c", "ç", "g"); + hre.replaceDestructive(newstring, "##C", "Ç", "g"); + hre.replaceDestructive(newstring, "?\\|", "\\|", "g"); + hre.replaceDestructive(newstring, "?\\", "\\\\", "g"); + hre.replaceDestructive(newstring, "?m", "---", "g"); + hre.replaceDestructive(newstring, "?n", "--", "g"); + hre.replaceDestructive(newstring, "?2", "-sharp", "g"); + hre.replaceDestructive(newstring, "?1", "-flat", "g"); + hre.replaceDestructive(newstring, "?3", "-natural", "g"); + hre.replaceDestructive(newstring, "\\", "/", "g"); + hre.replaceDestructive(newstring, "?[", "\\[", "g"); + hre.replaceDestructive(newstring, "?]", "\\]", "g"); + + out << newstring; +} + + + +////////////////////////////// +// +// Tool_prange::printXmlEncodedText -- convert +// & to & +// " to " +// ' to &spos; +// < to < +// > to > +// + +void Tool_prange::printXmlEncodedText(ostream& out, const string& strang) { + HumRegex hre; + string astring = strang; + + hre.replaceDestructive(astring, "&", "&", "g"); + hre.replaceDestructive(astring, "'", "'", "g"); + hre.replaceDestructive(astring, "\"", """, "g"); + hre.replaceDestructive(astring, "<", "<", "g"); + hre.replaceDestructive(astring, ">", ">", "g"); + + out << astring; +} + + + +////////////////////////////// +// +// Tool_prange::printScoreFile -- +// + +void Tool_prange::printScoreFile(ostream& out, vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { + string titlestring = getTitle(); + + if (m_defineQ) { + out << "#define SVG t 1 1 \\n_99%svg%\n"; + } + + string acctext = "g.bar.doubleflat path{color:darkorange;stroke:darkorange;}g.bar.flat path{color:brown;stroke:brown;}g.bar.sharp path{color:royalblue;stroke:royalblue;}g.bar.doublesharp path{color:aquamarine;stroke:aquamarine;}"; + string hovertext = ".bar:hover path{fill:red;color:red;stroke:red !important}"; + string hoverfilltext = hovertext; + + string text1 = ""; + string text2 = text1; + + + // print CSS style information if requested + if (m_hoverQ) { + SVGTEXT(out, text1); + } + + int maxStaffPosition = getMaxStaffPosition(voiceInfo); + + if (!titlestring.empty()) { + // print title + int vpos = 54; + if (maxStaffPosition > 12) { + vpos = maxStaffPosition + 3; + } + out << "t 2 10 "; + out << vpos; + out << " 1 1 0 0 0 0 -1.35\n"; + // out << "_03"; + printScoreEncodedText(out, titlestring); + out << "\n"; + } + + // print duration label if duration weighting is being used + SVGTEXT(out, ""); + if (m_durationQ) { + out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n"; + out << "_00(durations)\n"; + } else { + out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n"; + out << "_00(attacks)\n"; + } + SVGTEXT(out, ""); + + // print staff lines + out << "8 1 0 0 0 200\n"; // staff 1 + out << "8 2 0 -6 0 200\n"; // staff 2 + + int keysig = getKeySignature(infile); + // print key signature + if (keysig) { + out << "17 1 10 0 " << keysig << " 101.0"; + printKeySigCompression(out, keysig, 0); + out << endl; + out << "17 2 10 0 " << keysig; + printKeySigCompression(out, keysig, 1); + out << endl; + } + + // print barlines + out << "14 1 0 2\n"; // starting barline + out << "14 1 200 2\n"; // ending barline + out << "14 1 0 2 8\n"; // curly brace at start + + // print clefs + out << "3 2 2\n"; // treble clef + out << "3 1 2 0 1\n"; // bass clef + + assignHorizontalPosition(voiceInfo, 25.0, 170.0); + + double maxvalue = 0.0; + for (int i=1; i<(int)voiceInfo.size(); i++) { + double tempvalue = getMaxValue(voiceInfo.at(i).diatonic); + if (tempvalue > maxvalue) { + maxvalue = tempvalue; + } + } + for (int i=(int)voiceInfo.size()-1; i>0; i--) { + if (voiceInfo.at(i).kernQ) { + printScoreVoice(out, voiceInfo.at(i), maxvalue); + } + } + if (m_allQ) { + printScoreVoice(out, voiceInfo.at(0), maxvalue); + } +} + + +////////////////////////////// +// +// Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceinfo) { +// + +int Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceInfo) { + int maxi = getMaxDiatonicIndex(voiceInfo[0].diatonic); + int maxdiatonic = maxi - 3 * 7; + int staffline = maxdiatonic - 27; + return staffline; +} + + + + +////////////////////////////// +// +// Tool_prange::printKeySigCompression -- +// + +void Tool_prange::printKeySigCompression(ostream& out, int keysig, int extra) { + double compression = 0.0; + switch (abs(keysig)) { + case 0: compression = 0.0; break; + case 1: compression = 0.0; break; + case 2: compression = 0.0; break; + case 3: compression = 0.0; break; + case 4: compression = 0.9; break; + case 5: compression = 0.8; break; + case 6: compression = 0.7; break; + case 7: compression = 0.6; break; + } + if (compression <= 0.0) { + return; + } + for (int i=0; i& voiceInfo, int minval, int maxval) { + int count = 0; + for (int i=1; i<(int)voiceInfo.size(); i++) { + if (voiceInfo[i].kernQ) { + count++; + } + } + if (m_allQ) { + count++; + } + + vector hpos(count, 0); + hpos[0] = maxval; + hpos.back() = minval; + + if (hpos.size() > 2) { + for (int i=1; i<(int)hpos.size()-1; i++) { + int ii = hpos.size() - i - 1; + hpos[i] = (double)ii / (hpos.size()-1) * (maxval - minval) + minval; + } + } + + int position = 0; + if (m_allQ) { + position = 1; + voiceInfo[0].hpos = hpos[0]; + } + for (int i=0; i<(int)voiceInfo.size(); i++) { + if (voiceInfo.at(i).kernQ) { + voiceInfo.at(i).hpos = hpos.at(position++); + } + } +} + + + +////////////////////////////// +// +// Tool_prange::getKeySignature -- find first key signature in file. +// + +int Tool_prange::getKeySignature(HumdrumFile& infile) { + for (int i=0; iisKeySignature()) { + return Convert::kernKeyToNumber(*token); + } + } + } + + return 0; // C major key signature +} + + + +////////////////////////////// +// +// Tool_prange::printScoreVoice -- print the range information for a particular voice (in SCORE format). +// + +void Tool_prange::printScoreVoice(ostream& out, _VoiceInfo& voiceInfo, double maxvalue) { + int mini = getMinDiatonicIndex(voiceInfo.diatonic); + int maxi = getMaxDiatonicIndex(voiceInfo.diatonic); + + if ((mini < 0) || (maxi < 0)) { + // no data for voice so skip + return; + } + + // int minacci = getMinDiatonicAcc(voiceInfo.diatonic, mini); + // int maxacci = getMaxDiatonicAcc(voiceInfo.diatonic, maxi); + int mindiatonic = mini - 3 * 7; + int maxdiatonic = maxi - 3 * 7; + // int minacc = minacci - 3; + // int maxacc = maxacci - 3; + + int staff; + double vpos; + + int voicevpos = -3; + staff = getStaffBase7(mindiatonic); + int lowestvpos = getVpos(mindiatonic); + if ((staff == 1) && (lowestvpos <= 0)) { + voicevpos += lowestvpos - 2; + } + + if (m_localQ || (voiceInfo.index == 0)) { + double localmaxvalue = getMaxValue(voiceInfo.diatonic); + maxvalue = localmaxvalue; + } + double width; + double hoffset = 2.3333; + double maxhist = 17.6; + int i; + int base7; + + // print histogram bars + for (i=mini; i<=maxi; i++) { + if (voiceInfo.diatonic.at(i).at(0) <= 0.0) { + continue; + } + base7 = i - 3 * 7; + staff = getStaffBase7(base7); + vpos = getVpos(base7); + + // staring positions of accidentals: + vector starthpos(6, 0.0); + for (int j=1; j<(int)starthpos.size(); j++) { + double width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue; + starthpos[j] = starthpos[j-1] + width; + } + for (int j=(int)starthpos.size() - 1; j>0; j--) { + starthpos[j] = starthpos[j-1]; + } + + // print chromatic alterations + for (int j=(int)voiceInfo.diatonic.at(i).size()-1; j>0; j--) { + if (voiceInfo.diatonic.at(i).at(j) <= 0.0) { + continue; + } + int acc = 0; + switch (j) { + case 1: acc = -2; break; + case 2: acc = -1; break; + case 3: acc = 0; break; + case 4: acc = +1; break; + case 5: acc = +2; break; + } + + width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue + hoffset; + if (m_hoverQ) { + string title = getNoteTitle((int)voiceInfo.diatonic.at(i).at(j), base7, acc); + SVGTEXT(out, title); + } + out << "1 " << staff << " " << (voiceInfo.hpos + starthpos.at(j) + hoffset) << " " << vpos; + out << " 0 -1 4 0 0 0 99 0 0 "; + out << width << "\n"; + if (m_hoverQ) { + SVGTEXT(out, "
"); + } + } + } + + string voicestring = voiceInfo.name; + if (voicestring.empty()) { + voicestring = voiceInfo.abbr; + } + if (!voicestring.empty()) { + HumRegex hre; + hre.replaceDestructive(voicestring, "", "(\\\\n)+$"); + vector pieces; + hre.split(pieces, voicestring, "\\\\n"); + + if (pieces.size() > 1) { + voicestring = ""; + for (int i=0; i<(int)pieces.size(); i++) { + voicestring += pieces[i]; + if (i < (int)pieces.size() - 1) { + voicestring += "/"; + } + } + } + + double increment = 4.0; + for (int i=0; i<(int)pieces.size(); i++) { + // print voice name + double tvoffset = -4.0; + out << "t 1 " << voiceInfo.hpos << " " + << (voicevpos - increment * i) + << " 1 1 0 0 0 0 " << tvoffset; + out << "\n"; + + if (pieces[i] == "all") { + out << "_02"; + } else if (pieces[i] == "both") { + out << "_02"; + } else { + out << "_00"; + } + printScoreEncodedText(out, pieces[i]); + out << "\n"; + } + } + + // print the lowest pitch in range + staff = getStaffBase7(mindiatonic); + vpos = getVpos(mindiatonic); + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(mindiatonic, 0); + content += ": lowest note"; + if (!voicestring.empty()) { + content += " of "; + content += voicestring; + content += "'s range"; + } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos + << " 0 0 4 0 0 -2\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + + // print the highest pitch in range + staff = getStaffBase7(maxdiatonic); + vpos = getVpos(maxdiatonic); + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(maxdiatonic, 0); + content += ": highest note"; + if (!voicestring.empty()) { + content += " of "; + content += voicestring; + content += "'s range"; + } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos + << " 0 0 4 0 0 -2\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + + double goffset = -1.66; + double toffset = 1.5; + double median12 = getMedian12(voiceInfo.midibins); + double median40 = Convert::base12ToBase40(median12); + double median7 = Convert::base40ToDiatonic(median40); + // int acc = Convert::base40ToAccidental(median40); + + staff = getStaffBase7(median7); + vpos = getVpos(median7); + + // these offsets are useful when the quartile pitches are not shown... + int vvpos = maxdiatonic - median7 + 1; + int vvpos2 = median7 - mindiatonic + 1; + double offset = goffset; + if (vvpos <= 2) { + offset += toffset; + } else if (vvpos2 <= 2) { + offset -= toffset; + } + + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(median7, 0); + content += ": median note"; + if (!voicestring.empty()) { + content += " of "; + content += voicestring; + content += "'s range"; + } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 1 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + + if (m_finalisQ) { + for (int f=0; f<(int)voiceInfo.diafinal.size(); f++) { + int diafinalis = voiceInfo.diafinal.at(f); + int accfinalis = voiceInfo.accfinal.at(f); + int staff = getStaffBase7(diafinalis); + int vpos = getVpos(diafinalis); + double goffset = -1.66; + double toffset = 3.5; + + // these offsets are useful when the quartile pitches are not shown... + double offset = goffset; + offset += toffset; + + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(diafinalis, accfinalis); + content += ": last note"; + if (!voicestring.empty()) { + content += " of "; + if (voiceInfo.index == 0) { + content += voiceInfo.namfinal.at(f); + } else { + content += voicestring; + } + } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 0 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + } + } + + /* Needs fixing + int topquartile; + if (m_quartileQ) { + // print top quartile + topquartile = getTopQuartile(voiceInfo.midibins); + if (m_diatonicQ) { + topquartile = Convert::base7ToBase12(topquartile); + } + staff = getStaffBase7(topquartile); + vpos = getVpos(topquartile); + vvpos = median7 - topquartile + 1; + if (vvpos <= 2) { + offset = goffset + toffset; + } else { + offset = goffset; + } + vvpos = maxdiatonic - topquartile + 1; + if (vvpos <= 2) { + offset = goffset + toffset; + } + + if (m_hoverQ) { + if (m_defineQ) { + out << "SVG "; + } else { + out << "t 1 1\n"; + out << SVGTAG; + } + printScoreEncodedText(out, ""); + printDiatonicPitchName(out, topquartile, 0); + out << ": top quartile note"; + if (voicestring.size() > 0) { + out << " of " << voicestring << "\'s range"; + } + printScoreEncodedText(out, "\n"); + } + out << "1 " << staff << " " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 0 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + } + + // print bottom quartile + if (m_quartileQ) { + int bottomquartile = getBottomQuartile(voiceInfo.midibins); + if (m_diatonicQ) { + bottomquartile = Convert::base7ToBase12(bottomquartile); + } + staff = getStaffBase7(bottomquartile); + vpos = getVpos(bottomquartile); + vvpos = median7 - bottomquartile + 1; + if (vvpos <= 2) { + offset = goffset + toffset; + } else { + offset = goffset; + } + vvpos = bottomquartile - mindiatonic + 1; + if (vvpos <= 2) { + offset = goffset - toffset; + } + if (m_hoverQ) { + if (m_defineQ) { + out << "SVG "; + } else { + out << "t 1 1\n"; + out << SVGTAG; + } + printScoreEncodedText(out, ""); + printDiatonicPitchName(out, bottomquartile, 0); + out << ": bottom quartile note"; + if (voicestring.size() > 0) { + out << " of " << voicestring << "\'s range"; + } + printScoreEncodedText(out, "\n"); + } + out << "1.0 " << staff << ".0 " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 0 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + } + */ + +} + + + +////////////////////////////// +// +// Tool_prange::printDiatonicPitchName -- +// + +void Tool_prange::printDiatonicPitchName(ostream& out, int base7, int acc) { + out << getDiatonicPitchName(base7, acc); +} + + + +////////////////////////////// +// +// Tool_prange::getDiatonicPitchName -- +// + +string Tool_prange::getDiatonicPitchName(int base7, int acc) { + string output; + int dpc = base7 % 7; + char letter = (dpc + 2) % 7 + 'A'; + output += letter; + switch (acc) { + case -1: output += "♭"; break; + case +1: output += "♯"; break; + case -2: output += "𝄫"; break; + case +2: output += "𝄪"; break; + } + int octave = base7 / 7; + output += to_string(octave); + return output; +} + + + +////////////////////////////// +// +// Tool_prange::printHtmlStringEncodeSimple -- +// + +void Tool_prange::printHtmlStringEncodeSimple(ostream& out, const string& strang) { + string newstring = strang; + HumRegex hre; + hre.replaceDestructive(newstring, "&", "&", "g"); + hre.replaceDestructive(newstring, "<", "<", "g"); + hre.replaceDestructive(newstring, ">", "<", "g"); + out << newstring; +} + + + +////////////////////////////// +// +// Tool_prange::getNoteTitle -- return the title of the histogram bar. +// value = duration or count of notes +// diatonic = base7 value for note +// acc = accidental for diatonic note. +// + +string Tool_prange::getNoteTitle(double value, int diatonic, int acc) { + stringstream output; + output << ""; + if (m_durationQ) { + output << value / 8.0; + if (value/8.0 == 1.0) { + output << " long on "; + } else { + output << " longs on "; + } + output << getDiatonicPitchName(diatonic, acc); + } else { + output << value; + output << " "; + output << getDiatonicPitchName(diatonic, acc); + if (value != 1.0) { + output << "s"; + } + } + output << ""; + return output.str(); +} + + + +////////////////////////////// +// +// Tool_prange::getDiatonicInterval -- +// + +int Tool_prange::getDiatonicInterval(int note1, int note2) { + int vpos1 = getVpos(note1); + int vpos2 = getVpos(note2); + return abs(vpos1 - vpos2) + 1; +} + + + +////////////////////////////// +// +// Tool_prange::getTopQuartile -- +// + +int Tool_prange::getTopQuartile(vector& midibins) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + + double cumsum = 0.0; + int i; + for (i=midibins.size()-1; i>=0; i--) { + if (midibins[i] <= 0.0) { + continue; + } + cumsum += midibins[i]/sum; + if (cumsum >= 0.25) { + return i; + } + } + + return -1; +} + + + +////////////////////////////// +// +// Tool_prange::getBottomQuartile -- +// + +int Tool_prange::getBottomQuartile(vector& midibins) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + + double cumsum = 0.0; + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + cumsum += midibins[i]/sum; + if (cumsum >= 0.25) { + return i; + } + } + + return -1; +} + + + +////////////////////////////// +// +// Tool_prange::getMaxValue -- +// + +double Tool_prange::getMaxValue(vector>& bins) { + double maxi = 0; + for (int i=1; i<(int)bins.size(); i++) { + if (bins.at(i).at(0) > bins.at(maxi).at(0)) { + maxi = i; + } + } + return bins.at(maxi).at(0); +} + + + +////////////////////////////// +// +// Tool_prange::getVpos == return the position on the staff given the diatonic pitch. +// and the staff. 1=bass, 2=treble. +// 3 = bottom line of clef, 0 = space below first ledger line. +// + +double Tool_prange::getVpos(double base7) { + double output = 0; + if (base7 < 4 * 7) { + // bass clef + output = base7 - (1 + 2*7); // D2 + } else { + // treble clef + output = base7 - (6 + 3*7); // B3 + } + return output; +} + + + +////////////////////////////// +// +// Tool_prange::getStaffBase7 -- return 1 if less than middle C; otherwise return 2. +// + +int Tool_prange::getStaffBase7(int base7) { + if (base7 < 4 * 7) { + return 1; + } else { + return 2; + } +} + + +////////////////////////////// +// +// Tool_prange::getMaxDiatonicIndex -- return the highest non-zero content. +// + +int Tool_prange::getMaxDiatonicIndex(vector>& diatonic) { + for (int i=diatonic.size()-1; i>=0; i--) { + if (diatonic.at(i).at(0) != 0.0) { + return i; + } + } + return -1; +} + + + +////////////////////////////// +// +// Tool_prange::getMinDiatonicIndex -- return the lowest non-zero content. +// + +int Tool_prange::getMinDiatonicIndex(vector>& diatonic) { + for (int i=0; i<(int)diatonic.size(); i++) { + if (diatonic.at(i).at(0) != 0.0) { + return i; + } + } + return -1; +} + + + +////////////////////////////// +// +// Tool_prange::getMinDiatonicAcc -- return the lowest accidental. +// + +int Tool_prange::getMinDiatonicAcc(vector>& diatonic, int index) { + for (int i=1; i<(int)diatonic.at(index).size(); i++) { + if (diatonic.at(index).at(i) != 0.0) { + return i; + } + } + return -1; +} + + + +////////////////////////////// +// +// Tool_prange::getMaxDiatonicAcc -- return the highest accidental. +// + +int Tool_prange::getMaxDiatonicAcc(vector>& diatonic, int index) { + for (int i=(int)diatonic.at(index).size() - 1; i>0; i--) { + if (diatonic.at(index).at(i) != 0.0) { + return i; + } + } + return -1; +} + + + +////////////////////////////// +// +// Tool_prange::prepareRefmap -- +// + +void Tool_prange::prepareRefmap(HumdrumFile& infile) { + vector refrecords = infile.getGlobalReferenceRecords(); + m_refmap.clear(); + HumRegex hre; + for (int i = (int)refrecords.size()-1; i>=0; i--) { + string key = refrecords[i]->getReferenceKey(); + string value = refrecords[i]->getReferenceValue(); + m_refmap[key] = value; + if (key.find("@") != string::npos) { + // create default value + hre.replaceDestructive(key, "", "@.*"); + if (m_refmap[key].empty()) { + m_refmap[key] = value; + } + } + } + // fill in @{} templates (mostly for !!!title:) + int counter = 0; // prevent recursions + for (auto& entry : m_refmap) { + + if (entry.second.find("@") != string::npos) { + while (hre.search(entry.second, "@\\{(.*?)\\}")) { + string key = hre.getMatch(1); + string value = m_refmap[key]; + hre.replaceDestructive(entry.second, value, "@\\{" + key + "\\}", "g"); + counter++; + if (counter > 1000) { + break; + } + } + } + + } + + // prepare title + if (m_refmap["title"].empty()) { + m_refmap["title"] = m_refmap["OTL"]; + } +} + + + +////////////////////////////// +// +// Tool_prange::getTitle -- +// + +string Tool_prange::getTitle(void) { + string titlestring = "_00"; + HumRegex hre; + if (m_notitleQ) { + return ""; + } else if (m_titleQ) { + titlestring = m_title; + int counter = 0; + + if (titlestring.find("@") != string::npos) { + while (hre.search(titlestring, "@\\{(.*?)\\}")) { + string key = hre.getMatch(1); + string value = m_refmap[key]; + hre.replaceDestructive(titlestring, value, "@\\{" + key + "\\}", "g"); + counter++; + if (counter > 1000) { + break; + } + } + } + if (!titlestring.empty()) { + titlestring = "_00" + titlestring; + } + } else { + titlestring = m_refmap["title"]; + if (!titlestring.empty()) { + titlestring = "_00" + titlestring; + } + } + return titlestring; +} + + + +////////////////////////////// +// +// Tool_prange::clearHistograms -- +// + +void Tool_prange::clearHistograms(vector >& bins, int start) { + int i; + for (i=start; i<(int)bins.size(); i++) { + bins[i].resize(40*11); + fill(bins[i].begin(), bins[i].end(), 0.0); + // bins[i].allowGrowth(0); + } + for (int i=0; i<(int)bins.size(); i++) { + if (bins[i].size() == 0) { + bins[i].resize(40*11); + fill(bins[i].begin(), bins[i].end(), 0.0); + } + } +} + + + + +////////////////////////////// +// +// Tool_prange::printAnalysis -- +// + +void Tool_prange::printAnalysis(ostream& out, vector& midibins) { + if (m_percentileQ) { + printPercentile(out, midibins, m_percentile); + return; + } else if (m_rangeQ) { + double notesinrange = countNotesInRange(midibins, m_rangeL, m_rangeH); + out << notesinrange << endl; + return; + } + + int i; + double normval = 1.0; + + // print the pitch histogram + + double fracL = 0.0; + double fracH = 0.0; + double fracA = 0.0; + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + if (m_normQ) { + normval = sum; + } + double runningtotal = 0.0; + + + out << "**keyno\t"; + if (m_pitchQ) { + out << "**pitch"; + } else { + out << "**kern"; + } + out << "\t**count"; + if (m_addFractionQ) { + out << "\t**fracL"; + out << "\t**fracA"; + out << "\t**fracH"; + } + out << "\n"; + + + int base12; + + if (!m_reverseQ) { + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + if (m_diatonicQ) { + base12 = Convert::base7ToBase12(i); + } else { + base12 = i; + } + out << base12 << "\t"; + if (m_pitchQ) { + out << Convert::base12ToPitch(base12); + } else { + out << Convert::base12ToKern(base12); + } + out << "\t"; + out << midibins[i] / normval; + fracL = runningtotal/sum; + runningtotal += midibins[i]; + fracH = runningtotal/sum; + fracA = (fracH + fracL)/2.0; + fracL = (int)(fracL * 10000.0 + 0.5)/10000.0; + fracH = (int)(fracH * 10000.0 + 0.5)/10000.0; + fracA = (int)(fracA * 10000.0 + 0.5)/10000.0; + if (m_addFractionQ) { + out << "\t" << fracL; + out << "\t" << fracA; + out << "\t" << fracH; + } + out << "\n"; + } + } else { + for (i=(int)midibins.size()-1; i>=0; i--) { + if (midibins[i] <= 0.0) { + continue; + } + if (m_diatonicQ) { + base12 = Convert::base7ToBase12(i); + } else { + base12 = i; + } + out << base12 << "\t"; + if (m_pitchQ) { + out << Convert::base12ToPitch(base12); + } else { + out << Convert::base12ToKern(base12); + } + out << "\t"; + out << midibins[i] / normval; + fracL = runningtotal/sum; + runningtotal += midibins[i]; + fracH = runningtotal/sum; + fracA = (fracH + fracL)/2.0; + fracL = (int)(fracL * 10000.0 + 0.5)/10000.0; + fracH = (int)(fracH * 10000.0 + 0.5)/10000.0; + fracA = (int)(fracA * 10000.0 + 0.5)/10000.0; + if (m_addFractionQ) { + out << "\t" << fracL; + out << "\t" << fracA; + out << "\t" << fracH; + } + out << "\n"; + } + } + + out << "*-\t*-\t*-"; + if (m_addFractionQ) { + out << "\t*-"; + out << "\t*-"; + out << "\t*-"; + } + out << "\n"; + + out << "!!tessitura:\t" << getTessitura(midibins) << " semitones\n"; + + double mean = getMean12(midibins); + if (m_diatonicQ && (mean > 0)) { + mean = Convert::base7ToBase12(mean); + } + out << "!!mean:\t\t" << mean; + out << " ("; + if (mean < 0) { + out << "unpitched"; + } else { + out << Convert::base12ToKern(int(mean+0.5)); + } + out << ")" << "\n"; + + int median12 = getMedian12(midibins); + out << "!!median:\t" << median12; + out << " ("; + if (median12 < 0) { + out << "unpitched"; + } else { + out << Convert::base12ToKern(median12); + } + out << ")" << "\n"; + +} + + + +////////////////////////////// +// +// Tool_prange::getMedian12 -- return the pitch on which half of pitches are above +// and half are below. +// + +int Tool_prange::getMedian12(vector& midibins) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + + double cumsum = 0.0; + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + cumsum += midibins[i]/sum; + if (cumsum >= 0.50) { + return i; + } + } + + return -1000; +} + + + +////////////////////////////// +// +// Tool_prange::getMean12 -- return the interval between the highest and lowest +// pitch in terms if semitones. +// + +double Tool_prange::getMean12(vector& midibins) { + double top = 0.0; + double bottom = 0.0; + + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + top += midibins[i] * i; + bottom += midibins[i]; + + } + + if (bottom == 0) { + return -1000; + } + return top / bottom; +} + + + +////////////////////////////// +// +// Tool_prange::getTessitura -- return the interval between the highest and lowest +// pitch in terms if semitones. +// + +int Tool_prange::getTessitura(vector& midibins) { + int minn = -1000; + int maxx = -1000; + int i; + + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + if (minn < 0) { + minn = i; + } + if (maxx < 0) { + maxx = i; + } + if (minn > i) { + minn = i; + } + if (maxx < i) { + maxx = i; + } + } + if (m_diatonicQ) { + maxx = Convert::base7ToBase12(maxx); + minn = Convert::base7ToBase12(minn); + } + + return maxx - minn + 1; +} + + + +////////////////////////////// +// +// Tool_prange::countNotesInRange -- +// + +double Tool_prange::countNotesInRange(vector& midibins, int low, int high) { + int i; + double sum = 0; + for (i=low; i<=high; i++) { + sum += midibins[i]; + } + return sum; +} + + + +////////////////////////////// +// +// Tool_prange::printPercentile -- +// + +void Tool_prange::printPercentile(ostream& out, vector& midibins, double m_percentile) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + double runningtotal = 0.0; + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0) { + continue; + } + runningtotal += midibins[i] / sum; + if (runningtotal >= m_percentile) { + out << i << endl; + return; + } + } + + out << "unknown" << endl; +} + + + +////////////////////////////// +// +// Tool_prange::getRange -- +// + +void Tool_prange::getRange(int& rangeL, int& rangeH, const string& rangestring) { + rangeL = -1; rangeH = -1; + if (rangestring.empty()) { + return; + } + int length = (int)rangestring.length(); + char* buffer = new char[length+1]; + strcpy(buffer, rangestring.c_str()); + char* ptr; + if (std::isdigit(buffer[0])) { + ptr = strtok(buffer, " \t\n:-"); + sscanf(ptr, "%d", &rangeL); + ptr = strtok(NULL, " \t\n:-"); + if (ptr != NULL) { + sscanf(ptr, "%d", &rangeH); + } + } else { + ptr = strtok(buffer, " :"); + if (ptr != NULL) { + rangeL = Convert::kernToMidiNoteNumber(ptr); + ptr = strtok(NULL, " :"); + if (ptr != NULL) { + rangeH = Convert::kernToMidiNoteNumber(ptr); + } + } + } + + if (rangeH < 0) { + rangeH = rangeL; + } + + if (rangeL < 0) { rangeL = 0; } + if (rangeH < 0) { rangeH = 0; } + if (rangeL > 127) { rangeL = 127; } + if (rangeH > 127) { rangeH = 127; } + if (rangeL > rangeH) { + int temp = rangeL; + rangeL = rangeH; + rangeH = temp; + } + +} + + + + + ///////////////////////////////// // // Tool_gridtest::Tool_recip -- Set the recognized options for the tool. @@ -117402,7 +120772,7 @@ vector Tool_shed::addToExInterpList(void) { hre.split(pieces, elist, "[,;\\s*]+"); vector output; - for (int i=0; igetLineIndex(); int field = token->getFieldIndex(); std::string ccolor = getSpineColor(line, field); + size_t norest = ccolor.find("NOREST"); + if (norest != std::string::npos) { + ccolor = ccolor.substr(0, norest); + } if (!ccolor.empty()) { harmRend->SetColor(ccolor); if (token->getValueInt("auto", "circle")) { @@ -16444,8 +16448,16 @@ void HumdrumInput::embedTieInformation(Note *note, const std::string &token) void HumdrumInput::colorNote(Note *note, hum::HTp token, const std::string &subtoken, int line, int field) { std::string spinecolor = getSpineColor(line, field); - if (spinecolor != "") { - note->SetColor(spinecolor); + if (!spinecolor.empty()) { + size_t norest = spinecolor.find("NOREST"); + bool isRest = false; + if (norest != std::string::npos) { + isRest = true; + spinecolor = spinecolor.substr(0, norest); + } + if ((!spinecolor.empty()) && !isRest) { + note->SetColor(spinecolor); + } } if (m_mens) { @@ -16593,6 +16605,9 @@ void HumdrumInput::colorRest(Rest *rest, const std::string &token, int line, int std::string spinecolor; if ((line >= 0) && (field >= 0)) { spinecolor = getSpineColor(line, field); + if (spinecolor.find("NOREST") != std::string::npos) { + spinecolor = ""; + } } if (spinecolor != "") { rest->SetColor(spinecolor); @@ -16665,7 +16680,8 @@ string HumdrumInput::getSpineColor(int line, int field) return output; } for (int i = field + 1; i < infile[line].getFieldCount(); ++i) { - if (!infile.token(line, i)->isDataType("**color")) { + hum::HTp token = infile.token(line, i); + if (!(token->isDataType("**color") || token->isDataType("**coloR"))) { continue; } output = *infile.token(line, i)->resolveNull(); @@ -16681,6 +16697,9 @@ string HumdrumInput::getSpineColor(int line, int field) else if (output == "#000") { output = ""; } + if (token->isDataType("**coloR")) { + output += "NOREST"; + } break; } From 99e7bc0f5c4ec668e810c6438b5edebce395e7ab Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 17 Jun 2024 08:49:33 -0700 Subject: [PATCH 271/383] Fix for issue https://github.com/rism-digital/verovio/issues/3706 --- src/iohumdrum.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 415a6ad544f..6dc7d52574e 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -27696,7 +27696,8 @@ template hum::HumNum HumdrumInput::convertRhythm(ELEMENT element // Restore the tuplet factors before calling setRhythmFromDuration // as they will be removed (again) in getDurAndDots(). newdur /= m_tupletscaling; - setRhythmFromDuration(element, newdur); + // Do not run the following function since it somehow removes @dur: + // setRhythmFromDuration(element, newdur); return newdur; } From 24196fbbfc65cbf99ae337f36fc359a4200e1db6 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 18 Jun 2024 08:35:05 +0200 Subject: [PATCH 272/383] Apply tuplet ratio including with single elements. Fixes #3639 --- src/layerelement.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 25dd5d9ad77..603220d73c6 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -688,7 +688,7 @@ double LayerElement::GetAlignmentDuration( ListOfConstObjects objects; ClassIdsComparison ids({ CHORD, NOTE, REST, SPACE }); tuplet->FindAllDescendantsByComparison(&objects, &ids); - if (objects.size() > 1) { + if (objects.size() > 0) { num = tuplet->GetNum(); numbase = tuplet->GetNumbase(); // 0 is not valid in MEI anyway - just correct it silently From 5389918578e217a6677695510f3b25284ee40e1a Mon Sep 17 00:00:00 2001 From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:23:48 -0700 Subject: [PATCH 273/383] Fix 1: Don't overwrite ottava start note if ottava starts in multiple voices. (see beethoven sonata29-1.krn's first ottava (starting in measure 8). --- src/iohumdrum.cpp | 64 ++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 5f3805487fc..48f9f2f0f02 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -24332,40 +24332,48 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) int staffindex = m_currentstaff - 1; if (*token == "*8va") { - // turn on ottava - ss[staffindex].ottavameasure = m_measure; - ss[staffindex].ottavanotestart = NULL; - ss[staffindex].ottavanoteend = NULL; - ss[staffindex].ottavaendtimestamp = token->getDurationFromStart(); - // When a new note is read, check if ottavameasure - // is non-null, and if so, store the new note in ottavanotestart. + if (ss[staffindex].ottavameasure == NULL) { + // turn on ottava + ss[staffindex].ottavameasure = m_measure; + ss[staffindex].ottavanotestart = NULL; + ss[staffindex].ottavanoteend = NULL; + ss[staffindex].ottavaendtimestamp = token->getDurationFromStart(); + // When a new note is read, check if ottavameasure + // is non-null, and if so, store the new note in ottavanotestart. + } } else if (*token == "*8ba") { - // turn on ottava down - ss[staffindex].ottavadownmeasure = m_measure; - ss[staffindex].ottavadownnotestart = NULL; - ss[staffindex].ottavadownnoteend = NULL; - ss[staffindex].ottavadownendtimestamp = token->getDurationFromStart(); - // When a new note is read, check if ottavadownmeasure - // is non-null, and if so, store the new note in ottavadownnotestart. + if (ss[staffindex].ottavadownmeasure == NULL) { + // turn on ottava down + ss[staffindex].ottavadownmeasure = m_measure; + ss[staffindex].ottavadownnotestart = NULL; + ss[staffindex].ottavadownnoteend = NULL; + ss[staffindex].ottavadownendtimestamp = token->getDurationFromStart(); + // When a new note is read, check if ottavadownmeasure + // is non-null, and if so, store the new note in ottavadownnotestart. + } } else if (*token == "*15ba") { - // turn on two ottava down - ss[staffindex].ottava2downmeasure = m_measure; - ss[staffindex].ottava2downnotestart = NULL; - ss[staffindex].ottava2downnoteend = NULL; - ss[staffindex].ottava2downendtimestamp = token->getDurationFromStart(); - // When a new note is read, check if ottava2downmeasure - // is non-null, and if so, store the new note in ottava2downnotestart. + if (ss[staffindex].ottava2downmeasure == NULL) { + // turn on two ottava down + ss[staffindex].ottava2downmeasure = m_measure; + ss[staffindex].ottava2downnotestart = NULL; + ss[staffindex].ottava2downnoteend = NULL; + ss[staffindex].ottava2downendtimestamp = token->getDurationFromStart(); + // When a new note is read, check if ottava2downmeasure + // is non-null, and if so, store the new note in ottava2downnotestart. + } } else if (*token == "*15ma") { - // turn on ottava - ss[staffindex].ottava2measure = m_measure; - ss[staffindex].ottava2notestart = NULL; - ss[staffindex].ottava2noteend = NULL; - ss[staffindex].ottava2endtimestamp = token->getDurationFromStart(); - // When a new note is read, check if ottava2measure - // is non-null, and if so, store the new note in ottava2notestart. + if (ss[staffindex].ottava2measure == NULL) { + // turn on ottava + ss[staffindex].ottava2measure = m_measure; + ss[staffindex].ottava2notestart = NULL; + ss[staffindex].ottava2noteend = NULL; + ss[staffindex].ottava2endtimestamp = token->getDurationFromStart(); + // When a new note is read, check if ottava2measure + // is non-null, and if so, store the new note in ottava2notestart. + } } else if (*token == "*X8va") { // turn off ottava From 7d85717068613dc85cddb94b928760f9d8f924bf Mon Sep 17 00:00:00 2001 From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:23:14 -0700 Subject: [PATCH 274/383] Fix occasional missed start ottava in the same way that getEndIdForOttava fixed occasional missed end ottavas. The missing start ottavas were either rests, or in voices that split out _after_ the ottava start marker, but before the first note in the ottava. --- include/vrv/iohumdrum.h | 5 ++ src/iohumdrum.cpp | 127 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index cc5a009737b..92795b20182 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -278,6 +278,7 @@ namespace humaux { // an ottava line which will be turned off later. ottavameasure == the // starting measure of the ottava mark. Note *ottavanotestart; + std::string ottavanotestartid; Note *ottavanoteend; hum::HumNum ottavaendtimestamp; Measure *ottavameasure; @@ -286,6 +287,7 @@ namespace humaux { // an ottava down line which will be turned off later. ottavadownmeasure == the // starting measure of the ottava down mark. Note *ottavadownnotestart; + std::string ottavadownnotestartid; Note *ottavadownnoteend; hum::HumNum ottavadownendtimestamp; Measure *ottavadownmeasure; @@ -294,6 +296,7 @@ namespace humaux { // an ottava2 line which will be turned off later. ottava2measure == the // starting measure of the ottava2 mark. Note *ottava2notestart; + std::string ottava2notestartid; Note *ottava2noteend; hum::HumNum ottava2endtimestamp; Measure *ottava2measure; @@ -302,6 +305,7 @@ namespace humaux { // an ottava2 down line which will be turned off later. ottava2downmeasure == the // starting measure of the ottava2 down mark. Note *ottava2downnotestart; + std::string ottava2downnotestartid; Note *ottava2downnoteend; hum::HumNum ottava2downendtimestamp; Measure *ottava2downmeasure; @@ -710,6 +714,7 @@ class HumdrumInput : public vrv::Input { std::string getInstrumentClass(hum::HTp start); void removeInstrumentName(StaffDef *sd); void removeInstrumentAbbreviation(StaffDef *sd); + std::string getStartIdForOttava(hum::HTp token); std::string getEndIdForOttava(hum::HTp token); void prepareInitialOttavas(hum::HTp measure); void linkFingeringToNote(Fing *fing, hum::HTp token, int xstaffindex); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 48f9f2f0f02..11ba40cc81a 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -449,18 +449,22 @@ namespace humaux { righthalfstem = false; ottavanotestart = ottavanoteend = NULL; + ottavanotestartid = ""; ottavaendtimestamp = 0; ottavameasure = NULL; ottavadownnotestart = ottavadownnoteend = NULL; + ottavadownnotestartid = ""; ottavadownendtimestamp = 0; ottavadownmeasure = NULL; ottava2notestart = ottava2noteend = NULL; + ottava2notestartid = ""; ottava2endtimestamp = 0; ottava2measure = NULL; ottava2downnotestart = ottava2downnoteend = NULL; + ottava2downnotestartid = ""; ottava2downendtimestamp = 0; ottava2downmeasure = NULL; @@ -24336,6 +24340,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) // turn on ottava ss[staffindex].ottavameasure = m_measure; ss[staffindex].ottavanotestart = NULL; + ss[staffindex].ottavanotestartid = getStartIdForOttava(token); ss[staffindex].ottavanoteend = NULL; ss[staffindex].ottavaendtimestamp = token->getDurationFromStart(); // When a new note is read, check if ottavameasure @@ -24347,6 +24352,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) // turn on ottava down ss[staffindex].ottavadownmeasure = m_measure; ss[staffindex].ottavadownnotestart = NULL; + ss[staffindex].ottavadownnotestartid = getStartIdForOttava(token); ss[staffindex].ottavadownnoteend = NULL; ss[staffindex].ottavadownendtimestamp = token->getDurationFromStart(); // When a new note is read, check if ottavadownmeasure @@ -24358,6 +24364,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) // turn on two ottava down ss[staffindex].ottava2downmeasure = m_measure; ss[staffindex].ottava2downnotestart = NULL; + ss[staffindex].ottava2downnotestartid = getStartIdForOttava(token); ss[staffindex].ottava2downnoteend = NULL; ss[staffindex].ottava2downendtimestamp = token->getDurationFromStart(); // When a new note is read, check if ottava2downmeasure @@ -24369,6 +24376,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) // turn on ottava ss[staffindex].ottava2measure = m_measure; ss[staffindex].ottava2notestart = NULL; + ss[staffindex].ottava2notestartid = getStartIdForOttava(token); ss[staffindex].ottava2noteend = NULL; ss[staffindex].ottava2endtimestamp = token->getDurationFromStart(); // When a new note is read, check if ottava2measure @@ -24383,7 +24391,13 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) ss[staffindex].ottavameasure->AddChild(octave); setStaff(octave, staffindex + 1); octave->SetDis(OCTAVE_DIS_8); - octave->SetStartid("#" + ss[staffindex].ottavanotestart->GetID()); + std::string startid = ss[staffindex].ottavanotestartid; + if (startid != "") { + octave->SetStartid("#" + startid); + } + else { + octave->SetStartid("#" + ss[staffindex].ottavanotestart->GetID()); + } std::string endid = getEndIdForOttava(token); if (endid != "") { octave->SetEndid("#" + endid); @@ -24394,6 +24408,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) octave->SetDisPlace(STAFFREL_basic_above); } ss[staffindex].ottavanotestart = NULL; + ss[staffindex].ottavanotestartid = ""; ss[staffindex].ottavanoteend = NULL; ss[staffindex].ottavameasure = NULL; ss[staffindex].ottavaendtimestamp = 0; @@ -24406,7 +24421,13 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) ss[staffindex].ottavadownmeasure->AddChild(octave); setStaff(octave, staffindex + 1); octave->SetDis(OCTAVE_DIS_8); - octave->SetStartid("#" + ss[staffindex].ottavadownnotestart->GetID()); + std::string startid = ss[staffindex].ottavadownnotestartid; + if (startid != "") { + octave->SetStartid("#" + startid); + } + else { + octave->SetStartid("#" + ss[staffindex].ottavadownnotestart->GetID()); + } std::string endid = getEndIdForOttava(token); if (endid != "") { octave->SetEndid("#" + endid); @@ -24417,6 +24438,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) octave->SetDisPlace(STAFFREL_basic_below); } ss[staffindex].ottavadownnotestart = NULL; + ss[staffindex].ottavadownnotestartid = ""; ss[staffindex].ottavadownnoteend = NULL; ss[staffindex].ottavadownmeasure = NULL; ss[staffindex].ottavadownendtimestamp = 0; @@ -24429,8 +24451,13 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) ss[staffindex].ottava2measure->AddChild(octave); setStaff(octave, staffindex + 1); octave->SetDis(OCTAVE_DIS_15); - octave->SetStartid("#" + ss[staffindex].ottava2notestart->GetID()); - octave->SetEndid("#" + ss[staffindex].ottava2noteend->GetID()); + std::string startid = ss[staffindex].ottava2notestartid; + if (startid != "") { + octave->SetStartid("#" + startid); + } + else { + octave->SetStartid("#" + ss[staffindex].ottava2notestart->GetID()); + } std::string endid = getEndIdForOttava(token); if (endid != "") { octave->SetEndid("#" + endid); @@ -24441,6 +24468,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) octave->SetDisPlace(STAFFREL_basic_above); } ss[staffindex].ottava2notestart = NULL; + ss[staffindex].ottava2notestartid = ""; ss[staffindex].ottava2noteend = NULL; ss[staffindex].ottava2measure = NULL; ss[staffindex].ottava2endtimestamp = 0; @@ -24453,7 +24481,13 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) ss[staffindex].ottava2downmeasure->AddChild(octave); setStaff(octave, staffindex + 1); octave->SetDis(OCTAVE_DIS_15); - octave->SetStartid("#" + ss[staffindex].ottava2downnotestart->GetID()); + std::string startid = ss[staffindex].ottava2downnotestartid; + if (startid != "") { + octave->SetStartid("#" + startid); + } + else { + octave->SetStartid("#" + ss[staffindex].ottava2downnotestart->GetID()); + } std::string endid = getEndIdForOttava(token); if (endid != "") { octave->SetEndid("#" + endid); @@ -24464,6 +24498,7 @@ void HumdrumInput::handleOttavaMark(hum::HTp token, Note *note) octave->SetDisPlace(STAFFREL_basic_below); } ss[staffindex].ottava2downnotestart = NULL; + ss[staffindex].ottava2downnotestartid = ""; ss[staffindex].ottava2downnoteend = NULL; ss[staffindex].ottava2downmeasure = NULL; ss[staffindex].ottava2downendtimestamp = 0; @@ -24552,6 +24587,88 @@ std::string HumdrumInput::getEndIdForOttava(hum::HTp token) return getLocationId(prefix, target); } +////////////////////////////// +// +// HumdrumInput::getStartIdForOttava -- Find the first note after an ottava +// line start across multiple layers. If there is a tie between the +// durations of the notes in different layers, choose the note from +// a lower layer. Do this by searching forward in the token's strand +// to find the first data token, then search accross all subspines +// for the latest note. It is assumed that the ottava marking is +// placed in the position of the first layer (this is similar to +// the assumption for clef changes). +// + +std::string HumdrumInput::getStartIdForOttava(hum::HTp token) +{ + hum::HTp tok = token->getNextToken(); + while (tok && !tok->isData()) { + tok = tok->getNextToken(); + } + if (!tok) { + // couldn't find a next data line + return ""; + } + int track = tok->getTrack(); + int ttrack = track; + std::vector notes; + std::vector timestamps; + // for now counting rests as notes. + while (ttrack == track) { + hum::HTp xtok = tok; + if (xtok->isNull()) { + xtok = xtok->resolveNull(); + } + if (!xtok) { + tok = tok->getNextFieldToken(); + if (!tok) { + break; + } + ttrack = tok->getTrack(); + continue; + } + notes.push_back(xtok); + hum::HumNum timestamp = xtok->getDurationFromStart(); + timestamps.push_back(timestamp); + tok = tok->getNextFieldToken(); + if (!tok) { + break; + } + ttrack = tok->getTrack(); + } + + if (notes.empty()) { + return ""; + } + + int bestindex = 0; + for (int i = 1; i < (int)notes.size(); ++i) { + if (timestamps[i] < timestamps[bestindex]) { + bestindex = i; + } + } + + hum::HTp target = notes[bestindex]; + if (!target) { + return ""; + } + + std::string prefix = "note"; + if (target->isRest()) { + if (target->find("yy") != std::string::npos) { + prefix = "space"; + } + else { + prefix = "rest"; + } + } + else if (target->isChord()) { + prefix = "chord"; + } + + return getLocationId(prefix, target); +} + ////////////////////////////// // // HumdrumInput::handleCustos -- From 0beffacbae817fdca87fa057c9b05a26abbcccc4 Mon Sep 17 00:00:00 2001 From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:58:10 -0700 Subject: [PATCH 275/383] In getStartIdForOttava, don't use xtok->resolveNull (like we do in getEndIdForOttava) because you will end up starting the ottava with a note that starts before the ottava does. --- src/iohumdrum.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 11ba40cc81a..843f6ddc775 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -24617,9 +24617,6 @@ std::string HumdrumInput::getStartIdForOttava(hum::HTp token) while (ttrack == track) { hum::HTp xtok = tok; if (xtok->isNull()) { - xtok = xtok->resolveNull(); - } - if (!xtok) { tok = tok->getNextFieldToken(); if (!tok) { break; From 48e4c581212a9b9577ad5500330aaaa71e31b025 Mon Sep 17 00:00:00 2001 From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:47:24 -0700 Subject: [PATCH 276/383] Fix comment. --- src/iohumdrum.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 843f6ddc775..9f284cd7403 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -24594,7 +24594,7 @@ std::string HumdrumInput::getEndIdForOttava(hum::HTp token) // durations of the notes in different layers, choose the note from // a lower layer. Do this by searching forward in the token's strand // to find the first data token, then search accross all subspines -// for the latest note. It is assumed that the ottava marking is +// for the earliest note. It is assumed that the ottava marking is // placed in the position of the first layer (this is similar to // the assumption for clef changes). // From 69eb5ad42cfa3b3b090fb66d94ebf021073cc406 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sun, 30 Jun 2024 20:35:16 +0100 Subject: [PATCH 277/383] Humlib update. --- include/hum/humlib.h | 3 ++- src/hum/humlib.cpp | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 9e2cd80ca7d..6dd3ce528cb 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Mon Jun 17 07:10:09 PDT 2024 +// Last Modified: Sun Jun 30 19:51:54 WEST 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -1678,6 +1678,7 @@ class HumdrumToken : public std::string, public HumHash { bool isStaffLike (void) const { return isKernLike() || isMensLike(); } std::string getSpineInfo (void) const; int getTrack (void) const; + int getSpineIndex (void) const; int getSubtrack (void) const; bool noteInLowerSubtrack (void); std::string getTrackString (void) const; diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 407db92b986..227c5637403 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Mon Jun 17 07:10:09 PDT 2024 +// Last Modified: Sun Jun 30 19:51:54 WEST 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -27948,7 +27948,7 @@ void HumdrumFileStructure::analyzeStropheMarkers(void) { // Store the new strophe. laststrophe[spineinfo] = token; } - } else if (*token == "*Xstrophe") { + } else if ((*token == "*Xstrophe") || (*token == "*S-")) { string spineinfo = token->getSpineInfo(); HTp lastone = laststrophe[spineinfo]; if (lastone) { @@ -33556,6 +33556,19 @@ int HumdrumToken::getTrack(void) const { +////////////////////////////// +// +// HumdrumToken::getSpineIndex -- Similar to getTrack() but indexed from 0 +// rather than 1. Non-spined tokens should return -1 since they +// are not in the spine structure. +// + +int HumdrumToken::getSpineIndex(void) const { + return m_address.getTrack() - 1; +} + + + ////////////////////////////// // // HumdrumToken::setSubtrack -- Sets the subtrack (similar to a layer @@ -130142,6 +130155,10 @@ int Tool_tspos::getToolCounter(HumdrumFile& infile) { // around 40%. // +#ifndef M_E +#define M_E 2.71828182845904523536 +#endif + int Tool_tspos::logisticColorMap(double input, double max) { double center = max * 0.40; double k = 0.04; From c631ed210ffeea27a94250c51f48f5dc876b9312 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 18 Jun 2024 08:35:05 +0200 Subject: [PATCH 278/383] Apply tuplet ratio including with single elements. Fixes #3639 --- src/layerelement.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 25dd5d9ad77..603220d73c6 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -688,7 +688,7 @@ double LayerElement::GetAlignmentDuration( ListOfConstObjects objects; ClassIdsComparison ids({ CHORD, NOTE, REST, SPACE }); tuplet->FindAllDescendantsByComparison(&objects, &ids); - if (objects.size() > 1) { + if (objects.size() > 0) { num = tuplet->GetNum(); numbase = tuplet->GetNumbase(); // 0 is not valid in MEI anyway - just correct it silently From e0a436e565d44631f9bbbc25daf15ad3341068c4 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 28 May 2024 19:53:21 +0200 Subject: [PATCH 279/383] Adjust C clef offset for neume notation --- src/clef.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/clef.cpp b/src/clef.cpp index 3e73f0cc14d..8e85723c860 100644 --- a/src/clef.cpp +++ b/src/clef.cpp @@ -101,6 +101,12 @@ int Clef::GetClefLocOffset() const defaultOct = 3; offset = 4; } + else if (this->GetShape() == CLEFSHAPE_C) { + const Staff *staff = this->GetAncestorStaff(); + if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { + offset = 7; + } + } if (this->HasOct()) { int oct = this->GetOct(); From 20ac7dcca22a7d98668f3a5e9a2e8c93dc04d9a0 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 28 May 2024 19:54:36 +0200 Subject: [PATCH 280/383] Set yRel for Nc even when --neume-as-note is not set --- src/calcalignmentpitchposfunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calcalignmentpitchposfunctor.cpp b/src/calcalignmentpitchposfunctor.cpp index 697ea7bf548..13e212d1ea8 100644 --- a/src/calcalignmentpitchposfunctor.cpp +++ b/src/calcalignmentpitchposfunctor.cpp @@ -311,7 +311,7 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE } layerElement->SetDrawingYRel(yRel); } - else if (layerElement->Is(NC) && m_doc->GetOptions()->m_neumeAsNote.GetValue()) { + else if (layerElement->Is(NC)) { Nc *nc = vrv_cast(layerElement); assert(nc); int loc = 0; From c6c4c2b40b2a30cfc58e4c61abed023478638066 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 28 May 2024 19:54:59 +0200 Subject: [PATCH 281/383] Adjust drawing code for Nc, Liquescent and Custos --- include/vrv/view.h | 2 +- src/view_element.cpp | 39 ++++--------------- src/view_neume.cpp | 89 ++++++-------------------------------------- 3 files changed, 20 insertions(+), 110 deletions(-) diff --git a/include/vrv/view.h b/include/vrv/view.h index 5beb16884b1..49288554532 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -423,7 +423,7 @@ class View { /** * @name Methods for drawing parts of neume LayerElement child classes. - * Defined in view_neumes.cpp + * Defined in view_neume.cpp */ ///@{ void DrawNcAsNotehead(DeviceContext *dc, Nc *nc, Layer *layer, Staff *staff, Measure *measure); diff --git a/src/view_element.cpp b/src/view_element.cpp index 0f4f90636f2..a577500ac4c 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -728,48 +728,23 @@ void View::DrawCustos(DeviceContext *dc, LayerElement *element, Layer *layer, St // Select glyph to use for this custos const int sym = custos->GetCustosGlyph(staff->m_drawingNotationType); - int x, y; - // For neume notation we ignore the value set in CalcAlignmentPitchPosFunctor - if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { - x = custos->GetDrawingX(); - // Recalculate y from pitch to prevent visual/meaning mismatch - Clef *clef = layer->GetClef(element); - y = staff->GetDrawingY(); - PitchInterface pi; - // Neume notation uses C3 for C clef rather than C4. - // Take this into account when determining location. - // However this doesn't affect the value for F clef. - pi.SetPname(PITCHNAME_c); - if (clef->GetShape() == CLEFSHAPE_C) { - pi.SetOct(3); - } - else { - pi.SetOct(4); - } - // Convert from lines & spaces from bottom to from top - int CClefOffsetFromTop = ((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset(); - int custosOffsetFromTop = CClefOffsetFromTop + pi.PitchDifferenceTo(custos); - y -= custosOffsetFromTop * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - } - else { - x = element->GetDrawingX(); - y = element->GetDrawingY(); - // Because SMuFL does not have the origin correpsonding to the pitch as for notes, we need to correct it. - // This will remain approximate + int x = element->GetDrawingX(); + int y = element->GetDrawingY(); + + // Because SMuFL does not have the origin correpsonding to the pitch as for notes, we need to correct it. + // This will remain approximate + if (staff->m_drawingNotationType != NOTATIONTYPE_neume) { y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize); } if (staff->HasDrawingRotation()) { y -= staff->GetDrawingRotationOffsetFor(x); } - else if (staff->HasDrawingRotation()) { - y -= staff->GetDrawingRotationOffsetFor(x); - } this->DrawSmuflCode(dc, x, y, sym, staff->m_drawingStaffSize, false, true); /************ Draw children (accidentals, etc) ************/ - // Drawing the children should be done before ending the graphic. Otherwise the SVG tree will not match the MEI one + this->DrawLayerChildren(dc, custos, layer, staff, measure); dc->EndGraphic(element, this); diff --git a/src/view_neume.cpp b/src/view_neume.cpp index d50c6f0b4ea..92c162235f0 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -74,11 +74,6 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer dc->StartGraphic(element, "", element->GetID()); - Clef *clef = layer->GetClef(element); - int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int staffLineNumber = staff->m_drawingLines; - int clefLine = clef->GetLine(); - Nc *nc = dynamic_cast(element->GetParent()); if (nc->GetCurve() == curvatureDirection_CURVE_c) { @@ -106,44 +101,17 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - int noteY, noteX; - int yValue; - if (nc->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); - noteX = nc->GetDrawingX(); - } - else { - noteX = element->GetDrawingX(); - noteY = element->GetDrawingY(); - } - // Calculating proper y offset based on pname, clef, staff, and staff rotate - int clefYPosition = noteY - (staffSize * (staffLineNumber - clefLine)); - int pitchOffset = 0; + int noteX = nc->GetDrawingX(); + int noteY = nc->GetDrawingY(); - // The default octave = 3, but the actual octave is calculated by - // taking into account the displacement of the clef - int clefOctave = 3; - if (clef->GetDis() && clef->GetDisPlace()) { - clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); - } - int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotateOffset = 0; if (staff->HasDrawingRotation()) { - rotateOffset = staff->GetDrawingRotationOffsetFor(noteX); + noteY -= staff->GetDrawingRotationOffsetFor(noteX); } - if (clef->GetShape() == CLEFSHAPE_C) { - pitchOffset = (nc->GetPname() - 1) * (staffSize / 2); - } - else if (clef->GetShape() == CLEFSHAPE_F) { - pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); - } - yValue = clefYPosition + pitchOffset + octaveOffset - rotateOffset; - for (auto it = params.begin(); it != params.end(); it++) { for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { - DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, yValue + it->yOffsetLiq[i] * noteHeight, + DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, noteY + it->yOffsetLiq[i] * noteHeight, it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); } } @@ -179,12 +147,6 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff /******************************************************************/ - // Intializing necessary variables - Clef *clef = layer->GetClef(element); - int staffSize = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - int staffLineNumber = staff->m_drawingLines; - int clefLine = clef->GetLine(); - Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); assert(neume); int position = neume->GetChildIndex(element); @@ -249,53 +211,26 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); const int noteWidth = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - int noteY, noteX; - int yValue; + + int noteX = nc->GetDrawingX(); + int noteY = nc->GetDrawingY(); + if (nc->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); - noteX = nc->GetDrawingX(); params.at(0).xOffset = 0; } + // Not sure about this if - the nc pname and oct are going to be ignored else if (neume->HasFacs() && m_doc->IsNeumeLines()) { noteY = staff->GetDrawingY(); noteX = neume->GetDrawingX() + position * noteWidth; } - else { - noteX = element->GetDrawingX(); - noteY = element->GetDrawingY(); - } - // Calculating proper y offset based on pname, clef, staff, and staff rotate - int clefYPosition = noteY - (staffSize * (staffLineNumber - clefLine)); - - // The default octave = 3, but the actual octave is calculated by - // taking into account the displacement of the clef - int clefOctave = 3; - if (clef->GetDis() && clef->GetDisPlace()) { - clefOctave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); - } - int octaveOffset = (nc->GetOct() - clefOctave) * ((staffSize / 2) * 7); - int rotationOffset = 0; - if (staff->HasDrawingRotation()) { - rotationOffset = staff->GetDrawingRotationOffsetFor(noteX); - } - if (nc->HasLoc()) { - yValue = noteY + (nc->GetLoc() - 2 * (staffLineNumber - 1)) * (staffSize / 2); - } - else { - int pitchOffset = 0; - if (clef->GetShape() == CLEFSHAPE_C) { - pitchOffset = (nc->GetPname() - 1) * (staffSize / 2); - } - else if (clef->GetShape() == CLEFSHAPE_F) { - pitchOffset = (nc->GetPname() - 4) * (staffSize / 2); - } - yValue = clefYPosition + pitchOffset + octaveOffset - rotationOffset; + if (staff->HasDrawingRotation()) { + noteY -= staff->GetDrawingRotationOffsetFor(noteX); } if (!nc->HasCurve()) { for (auto it = params.begin(); it != params.end(); it++) { - DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, yValue + it->yOffset * noteHeight, it->fontNo, + DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, noteY + it->yOffset * noteHeight, it->fontNo, staff->m_drawingStaffSize, false, true); } } From 22d8ec0201e424fb43b6f0045cc27a8b8868b7d1 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 29 May 2024 17:50:24 +0200 Subject: [PATCH 282/383] Pass notationType to Clef::GetClefLocOffset --- include/vrv/clef.h | 2 +- src/clef.cpp | 7 +++---- src/editortoolkit_neume.cpp | 4 ++-- src/layer.cpp | 6 ++++-- src/view_page.cpp | 3 ++- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/vrv/clef.h b/include/vrv/clef.h index bc16c6fdc42..15704c7b23d 100644 --- a/include/vrv/clef.h +++ b/include/vrv/clef.h @@ -59,7 +59,7 @@ class Clef : public LayerElement, /** * Return the offset of the clef */ - int GetClefLocOffset() const; + int GetClefLocOffset(data_NOTATIONTYPE notationType) const; //----------------// // Static methods // diff --git a/src/clef.cpp b/src/clef.cpp index 8e85723c860..74afebb9785 100644 --- a/src/clef.cpp +++ b/src/clef.cpp @@ -79,12 +79,12 @@ void Clef::Reset() this->ResetVisibility(); } -int Clef::GetClefLocOffset() const +int Clef::GetClefLocOffset(data_NOTATIONTYPE notationType) const { // Only resolve simple sameas links to avoid infinite recursion const Clef *sameas = dynamic_cast(this->GetSameasLink()); if (sameas && !sameas->HasSameasLink()) { - return sameas->GetClefLocOffset(); + return sameas->GetClefLocOffset(notationType); } int offset = 0; @@ -102,8 +102,7 @@ int Clef::GetClefLocOffset() const offset = 4; } else if (this->GetShape() == CLEFSHAPE_C) { - const Staff *staff = this->GetAncestorStaff(); - if (staff->m_drawingNotationType == NOTATIONTYPE_neume) { + if (notationType == NOTATIONTYPE_neume) { offset = 7; } } diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 39a192f97ec..063b13e89b1 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -4225,7 +4225,7 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) - m_view->ToLogicalY(fi->GetZone()->GetUly())) / staffSize - - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); + - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset(staff->m_drawingNotationType))); pi->AdjustPitchByOffset(-pitchDifference); return true; } @@ -4292,7 +4292,7 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) - m_view->ToLogicalY(fi->GetZone()->GetUly())) / staffSize - - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset())); + - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset(staff->m_drawingNotationType))); pi->AdjustPitchByOffset(-pitchDifference); } diff --git a/src/layer.cpp b/src/layer.cpp index a05d48903f1..560e5642bb2 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -288,7 +288,9 @@ int Layer::GetClefLocOffset(const LayerElement *test) const { const Clef *clef = this->GetClef(test); if (!clef) return 0; - return clef->GetClefLocOffset(); + const Staff *staff = vrv_cast(this->GetFirstAncestor(STAFF)); + assert(staff); + return clef->GetClefLocOffset(staff->m_drawingNotationType); } int Layer::GetCrossStaffClefLocOffset(const LayerElement *element, int currentOffset) const @@ -298,7 +300,7 @@ int Layer::GetCrossStaffClefLocOffset(const LayerElement *element, int currentOf if (!element->Is(CLEF)) { const Clef *clef = vrv_cast(GetListFirstBackward(element, CLEF)); if (clef && clef->m_crossStaff) { - return clef->GetClefLocOffset(); + return clef->GetClefLocOffset(element->m_crossStaff->m_drawingNotationType); } } } diff --git a/src/view_page.cpp b/src/view_page.cpp index d150f693ed4..797dd0d4746 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1476,7 +1476,8 @@ int View::CalculatePitchCode(Layer *layer, int y_n, int x_pos, int *octave) Clef *clef = layer->GetClef(pelement); if (clef) { - yb += (clef->GetClefLocOffset()) * m_doc->GetDrawingUnit(staffSize); // UT1 reel + yb += (clef->GetClefLocOffset(parentStaff->m_drawingNotationType)) + * m_doc->GetDrawingUnit(staffSize); // UT1 reel } yb -= 4 * m_doc->GetDrawingOctaveSize(staffSize); // UT, note la plus grave From 94479ff389363082330c494b04a6cb39a3d5b011 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 29 May 2024 18:40:26 +0200 Subject: [PATCH 283/383] Add AdjustNeumeXFunctor --- Verovio.xcodeproj/project.pbxproj | 16 +++++++++ include/vrv/adjustneumexfunctor.h | 56 +++++++++++++++++++++++++++++++ src/adjustneumexfunctor.cpp | 30 +++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 include/vrv/adjustneumexfunctor.h create mode 100644 src/adjustneumexfunctor.cpp diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index d443aa757d3..861f6d922dc 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -284,6 +284,12 @@ 4D4C26EE1EF7E75400681770 /* label.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4C26EC1EF7E75400681770 /* label.cpp */; }; 4D4C26EF1EF7E75400681770 /* label.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4C26EC1EF7E75400681770 /* label.cpp */; }; 4D4C26F01EF7E75E00681770 /* score.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D95D4FB1D74551100B2B856 /* score.cpp */; }; + 4D4CDEA52C078FF9005621E9 /* adjustneumexfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4CDEA42C078FF9005621E9 /* adjustneumexfunctor.cpp */; }; + 4D4CDEA72C079026005621E9 /* adjustneumexfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D4CDEA62C079008005621E9 /* adjustneumexfunctor.h */; }; + 4D4CDEA82C079026005621E9 /* adjustneumexfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D4CDEA62C079008005621E9 /* adjustneumexfunctor.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4D4CDEA92C07902C005621E9 /* adjustneumexfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4CDEA42C078FF9005621E9 /* adjustneumexfunctor.cpp */; }; + 4D4CDEAA2C07902E005621E9 /* adjustneumexfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4CDEA42C078FF9005621E9 /* adjustneumexfunctor.cpp */; }; + 4D4CDEAB2C07902F005621E9 /* adjustneumexfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4CDEA42C078FF9005621E9 /* adjustneumexfunctor.cpp */; }; 4D4FCD0A1F5455F10009C455 /* staffgrp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D4FCD091F5455F10009C455 /* staffgrp.h */; }; 4D4FCD0C1F5455FF0009C455 /* staffgrp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4FCD0B1F5455FF0009C455 /* staffgrp.cpp */; }; 4D4FCD0D1F5455FF0009C455 /* staffgrp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D4FCD0B1F5455FF0009C455 /* staffgrp.cpp */; }; @@ -1835,6 +1841,8 @@ 4D49924E2926B4DD007E3431 /* toolkitdef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = toolkitdef.h; path = include/vrv/toolkitdef.h; sourceTree = ""; }; 4D4C26EB1EF7E73000681770 /* label.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = label.h; path = include/vrv/label.h; sourceTree = ""; }; 4D4C26EC1EF7E75400681770 /* label.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = label.cpp; path = src/label.cpp; sourceTree = ""; }; + 4D4CDEA42C078FF9005621E9 /* adjustneumexfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = adjustneumexfunctor.cpp; path = src/adjustneumexfunctor.cpp; sourceTree = ""; }; + 4D4CDEA62C079008005621E9 /* adjustneumexfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = adjustneumexfunctor.h; path = include/vrv/adjustneumexfunctor.h; sourceTree = ""; }; 4D4FCD091F5455F10009C455 /* staffgrp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = staffgrp.h; path = include/vrv/staffgrp.h; sourceTree = ""; }; 4D4FCD0B1F5455FF0009C455 /* staffgrp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = staffgrp.cpp; path = src/staffgrp.cpp; sourceTree = ""; }; 4D4FCD0F1F5457020009C455 /* staffdef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = staffdef.h; path = include/vrv/staffdef.h; sourceTree = ""; }; @@ -3104,6 +3112,8 @@ E7A1640829AF343F0099BD6A /* adjustharmgrpsspacingfunctor.h */, E7E1698529A898C700FFF482 /* adjustlayersfunctor.cpp */, E7E1698229A8987200FFF482 /* adjustlayersfunctor.h */, + 4D4CDEA42C078FF9005621E9 /* adjustneumexfunctor.cpp */, + 4D4CDEA62C079008005621E9 /* adjustneumexfunctor.h */, E7876F1729C07F16002147DC /* adjustsylspacingfunctor.cpp */, E7876F1429C07EE1002147DC /* adjustsylspacingfunctor.h */, E7E9C11C29B0EFB400CFCE2F /* adjusttempofunctor.cpp */, @@ -3334,6 +3344,7 @@ 4D89F90C201771A700A4D336 /* num.h in Headers */, 4DA0EAE322BB77AF00A7EBEB /* surface.h in Headers */, 4DACC9E62990F29A00B55913 /* atttypes.h in Headers */, + 4D4CDEA72C079026005621E9 /* adjustneumexfunctor.h in Headers */, 4DB3D8FE1F83D20100B5FC2B /* horizontalaligner.h in Headers */, 4DB3D8F21F83D1B100B5FC2B /* svg.h in Headers */, 4DACC9DC2990F29A00B55913 /* atts_header.h in Headers */, @@ -3536,6 +3547,7 @@ BB4C4B3822A932CF001F6AF0 /* trill.h in Headers */, BB4C4B6422A932D7001F6AF0 /* mrpt2.h in Headers */, E797C462298EC2E700CAD67E /* calcalignmentpitchposfunctor.h in Headers */, + 4D4CDEA82C079026005621E9 /* adjustneumexfunctor.h in Headers */, BB4C4B5222A932D7001F6AF0 /* ftrem.h in Headers */, E7E9C11329B0A1E200CFCE2F /* adjustaccidxfunctor.h in Headers */, 4D88AD0B289673F50006D7DA /* symbol.h in Headers */, @@ -3983,6 +3995,7 @@ 4DD7C10127A5650600B9C017 /* timemap.cpp in Sources */, 4D1694081E3A44F300569BF4 /* iopae.cpp in Sources */, E78833612994EC7C00D44B01 /* calcchordnoteheadsfunctor.cpp in Sources */, + 4D4CDEA92C07902C005621E9 /* adjustneumexfunctor.cpp in Sources */, 4D16940A1E3A44F300569BF4 /* fermata.cpp in Sources */, 4DB3D8981F7C325C00B5FC2B /* lb.cpp in Sources */, E7F39C6229A62B430055DBE0 /* adjustclefchangesfunctor.cpp in Sources */, @@ -4329,6 +4342,7 @@ 4D5FA9111E16A93F00F3B919 /* boundingbox.cpp in Sources */, 8F086EF9188539540037FD8E /* object.cpp in Sources */, 4DD7C10227A5650600B9C017 /* timemap.cpp in Sources */, + 4D4CDEA52C078FF9005621E9 /* adjustneumexfunctor.cpp in Sources */, E797C460298EC2C600CAD67E /* calcalignmentpitchposfunctor.cpp in Sources */, 8F086EFA188539540037FD8E /* page.cpp in Sources */, 8F086EFB188539540037FD8E /* pitchinterface.cpp in Sources */, @@ -4556,6 +4570,7 @@ 403BEFF9206C00FF00D022D5 /* beatrpt.cpp in Sources */, E74A806928BC9843005274E7 /* functorinterface.cpp in Sources */, 4DD7C0FC27A55CEA00B9C017 /* timemap.cpp in Sources */, + 4D4CDEAA2C07902E005621E9 /* adjustneumexfunctor.cpp in Sources */, E78833622994EC7D00D44B01 /* calcchordnoteheadsfunctor.cpp in Sources */, 8F3DD33C18854B2E0051330C /* barline.cpp in Sources */, 4DB3D8D51F83D12B00B5FC2B /* tempo.cpp in Sources */, @@ -4843,6 +4858,7 @@ E78833632994EC7E00D44B01 /* calcchordnoteheadsfunctor.cpp in Sources */, BB4C4B2322A932CF001F6AF0 /* dynam.cpp in Sources */, BB4C4B6522A932D7001F6AF0 /* multirest.cpp in Sources */, + 4D4CDEAB2C07902F005621E9 /* adjustneumexfunctor.cpp in Sources */, BB4C4AB322A932A6001F6AF0 /* iohumdrum.cpp in Sources */, E7F39C6429A62B440055DBE0 /* adjustclefchangesfunctor.cpp in Sources */, E793206A2991454100D80975 /* calcstemfunctor.cpp in Sources */, diff --git a/include/vrv/adjustneumexfunctor.h b/include/vrv/adjustneumexfunctor.h new file mode 100644 index 00000000000..bc55c0fda41 --- /dev/null +++ b/include/vrv/adjustneumexfunctor.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: adjustneumexfunctor.h +// Author: Laurent Pugin +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_ADJUSTNEUMEXFUNCTOR_H__ +#define __VRV_ADJUSTNEUMEXFUNCTOR_H__ + +#include "functor.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// AdjustNeumeXFunctor +//---------------------------------------------------------------------------- + +/** + * This class adjusts the X position of accidentals. + */ +class AdjustNeumeXFunctor : public DocFunctor { +public: + /** + * @name Constructors, destructors + */ + ///@{ + AdjustNeumeXFunctor(Doc *doc); + virtual ~AdjustNeumeXFunctor() = default; + ///@} + + /* + * Abstract base implementation + */ + bool ImplementsEndInterface() const override { return false; } + + /* + * Functor interface + */ + ///@{ + FunctorCode VisitNeume(Neume *neume) override; + ///@} + +protected: + // +private: + // +public: + // +private: + // +}; + +} // namespace vrv + +#endif // __VRV_ADJUSTNEUMEXFUNCTOR_H__ diff --git a/src/adjustneumexfunctor.cpp b/src/adjustneumexfunctor.cpp new file mode 100644 index 00000000000..11b2d59fb0d --- /dev/null +++ b/src/adjustneumexfunctor.cpp @@ -0,0 +1,30 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: adjustneumexfunctor.cpp +// Author: Laurent Pugin +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "adjustneumexfunctor.h" + +//---------------------------------------------------------------------------- + +#include "doc.h" +#include "score.h" + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// AdjustNeumeXFunctor +//---------------------------------------------------------------------------- + +AdjustNeumeXFunctor::AdjustNeumeXFunctor(Doc *doc) : DocFunctor(doc) {} + +FunctorCode AdjustNeumeXFunctor::VisitNeume(Neume *neume) +{ + return FUNCTOR_CONTINUE; +} + +} // namespace vrv From eecbdcc100e33d9b46e204c4113cdc97453cf720 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 30 May 2024 08:32:19 +0200 Subject: [PATCH 284/383] Adjustment to the spacing for non measured music --- include/vrv/adjustxposfunctor.h | 2 ++ src/adjustxposfunctor.cpp | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/vrv/adjustxposfunctor.h b/include/vrv/adjustxposfunctor.h index 0b7d07b7ad8..641bda82629 100644 --- a/include/vrv/adjustxposfunctor.h +++ b/include/vrv/adjustxposfunctor.h @@ -125,6 +125,8 @@ class AdjustXPosFunctor : public DocFunctor { AdjustXPosAlignmentOffset m_currentAlignment; // The preceeding alignment AdjustXPosAlignmentOffset m_previousAlignment; + // The current measure + Measure *m_measure; }; } // namespace vrv diff --git a/src/adjustxposfunctor.cpp b/src/adjustxposfunctor.cpp index 5436c605497..88b0cf1a031 100644 --- a/src/adjustxposfunctor.cpp +++ b/src/adjustxposfunctor.cpp @@ -33,6 +33,7 @@ AdjustXPosFunctor::AdjustXPosFunctor(Doc *doc, const std::vector &staffNs) m_staffNs = staffNs; m_staffSize = 100; m_rightBarLinesOnly = false; + m_measure = NULL; } FunctorCode AdjustXPosFunctor::VisitAlignment(Alignment *alignment) @@ -145,8 +146,9 @@ FunctorCode AdjustXPosFunctor::VisitLayerElement(LayerElement *layerElement) int selfRight = layerElement->GetAlignment()->GetXRel(); if (!layerElement->HasSelfBB() || layerElement->HasEmptyBB()) { selfRight = layerElement->GetAlignment()->GetXRel(); - // Still add the right margin for the barlines - if (layerElement->Is(BARLINE)) selfRight += m_doc->GetRightMargin(layerElement) * drawingUnit; + // Still add the right margin for the barlines but not with non measure music + if (layerElement->Is(BARLINE) && m_measure->IsMeasuredMusic()) + selfRight += m_doc->GetRightMargin(layerElement) * drawingUnit; } else { selfRight = layerElement->GetSelfRight() + m_doc->GetRightMargin(layerElement) * drawingUnit; @@ -210,6 +212,8 @@ FunctorCode AdjustXPosFunctor::VisitMeasure(Measure *measure) m_upcomingMinPos = VRV_UNSET; m_cumulatedXShift = 0; + m_measure = measure; + System *system = vrv_cast(measure->GetFirstAncestor(SYSTEM)); assert(system); @@ -250,6 +254,9 @@ FunctorCode AdjustXPosFunctor::VisitMeasure(Measure *measure) this->SetFilters(previousFilters); + // There is no reason to adjust a minimum width with mensural music + if (!measure->IsMeasuredMusic()) return FUNCTOR_SIBLINGS; + int minMeasureWidth = m_doc->GetOptions()->m_unit.GetValue() * m_doc->GetOptions()->m_measureMinWidth.GetValue(); // First try to see if we have a double measure length element MeasureAlignerTypeComparison alignmentComparison(ALIGNMENT_FULLMEASURE2); From c60f2991faf3f43007149dbd4c3d32762c87ac37 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 31 May 2024 13:13:13 +0200 Subject: [PATCH 285/383] Add method for logging the tree structure --- include/vrv/object.h | 8 ++++++++ src/object.cpp | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/vrv/object.h b/include/vrv/object.h index 31bbf863f95..18b0b59b47b 100644 --- a/include/vrv/object.h +++ b/include/vrv/object.h @@ -659,6 +659,14 @@ class Object : public BoundingBox { virtual FunctorCode AcceptEnd(ConstFunctor &functor) const; ///@} + /** + * Output the class name of the object (or a custom msg) and of its children recursively + */ + ///@{ + void LogDebugTree(int maxDepth = UNLIMITED_DEPTH, int level = 0); + virtual std::string LogDebugTreeMsg() { return this->GetClassName(); } + ///@} + //----------------// // Static methods // //----------------// diff --git a/src/object.cpp b/src/object.cpp index d23e54181bf..91b852b7613 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -1201,6 +1201,18 @@ Object *Object::FindPreviousChild(Comparison *comp, Object *start) return const_cast(findPreviousChildByComparison.GetElement()); } +void Object::LogDebugTree(int maxDepth, int level) +{ + std::string indent(level, '\t'); + LogDebug("%s%s", indent.c_str(), this->LogDebugTreeMsg().c_str()); + + if (maxDepth == level) return; + + for (auto &child : this->GetChildren()) { + child->LogDebugTree(maxDepth, level + 1); + } +} + //---------------------------------------------------------------------------- // Static methods for Object //---------------------------------------------------------------------------- From 959242cc17c9920de8cf5a730a709655ee1f36ce Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 2 Jun 2024 15:38:04 +0200 Subject: [PATCH 286/383] Fix aligners with neumes --- src/alignfunctor.cpp | 34 ++++++++++++++++++++++++++++---- src/calcalignmentxposfunctor.cpp | 11 +++++++---- src/layerelement.cpp | 17 +++++++++++++--- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index 8a1e1b35e51..476bf07f10e 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -15,6 +15,8 @@ #include "fig.h" #include "layer.h" #include "ligature.h" +#include "nc.h" +#include "neume.h" #include "page.h" #include "rend.h" #include "rest.h" @@ -22,6 +24,7 @@ #include "section.h" #include "staff.h" #include "svg.h" +#include "syllable.h" #include "system.h" #include "tabgrp.h" #include "verse.h" @@ -140,6 +143,7 @@ FunctorCode AlignHorizontallyFunctor::VisitLayerElement(LayerElement *layerEleme Rest *restParent = vrv_cast(layerElement->GetFirstAncestor(REST, MAX_NOTE_DEPTH)); TabGrp *tabGrpParent = vrv_cast(layerElement->GetFirstAncestor(TABGRP, MAX_TABGRP_DEPTH)); const bool ligatureAsBracket = m_doc->GetOptions()->m_ligatureAsBracket.GetValue(); + const bool neumeAsNote = m_doc->GetOptions()->m_neumeAsNote.GetValue(); if (chordParent) { layerElement->SetAlignment(chordParent->GetAlignment()); @@ -263,14 +267,15 @@ FunctorCode AlignHorizontallyFunctor::VisitLayerElement(LayerElement *layerEleme layerElement->SetAlignment(note->GetAlignment()); } else if (layerElement->Is(SYL)) { - Staff *staff = layerElement->GetAncestorStaff(); Note *note = vrv_cast(layerElement->GetFirstAncestor(NOTE)); - if (!note || (staff->m_drawingNotationType == NOTATIONTYPE_neume)) { - type = ALIGNMENT_DEFAULT; + if (note) { + layerElement->SetAlignment(note->GetAlignment()); } else { - layerElement->SetAlignment(note->GetAlignment()); + Syllable *syllable = vrv_cast(layerElement->GetFirstAncestor(SYLLABLE)); + if (syllable) layerElement->SetAlignment(syllable->GetAlignment()); } + // Else add a default } else if (layerElement->Is(VERSE)) { // Idem @@ -278,6 +283,25 @@ FunctorCode AlignHorizontallyFunctor::VisitLayerElement(LayerElement *layerEleme assert(note); layerElement->SetAlignment(note->GetAlignment()); } + else if (layerElement->Is(NC)) { + // Align with the neume + if (!neumeAsNote) { + Neume *neume = vrv_cast(layerElement->GetFirstAncestor(NEUME)); + assert(neume); + layerElement->SetAlignment(neume->GetAlignment()); + } + // Otherwise each nc has its own aligner + } + else if (layerElement->Is(NEUME)) { + // Align with the syllable + if (neumeAsNote) { + Syllable *syllable = vrv_cast(layerElement->GetFirstAncestor(SYLLABLE)); + assert(syllable); + layerElement->SetAlignment(syllable->GetAlignment()); + return FUNCTOR_CONTINUE; + } + // Otherwise each neume has its own aligner + } else if (layerElement->Is(GRACEGRP)) { return FUNCTOR_CONTINUE; } @@ -370,6 +394,8 @@ FunctorCode AlignHorizontallyFunctor::VisitMeasureEnd(Measure *measure) if (m_hasMultipleLayer) measure->HasAlignmentRefWithMultipleLayers(true); + measure->m_measureAligner.LogDebugTree(3); + return FUNCTOR_CONTINUE; } diff --git a/src/calcalignmentxposfunctor.cpp b/src/calcalignmentxposfunctor.cpp index e8d44e18a3d..780c076f5b9 100644 --- a/src/calcalignmentxposfunctor.cpp +++ b/src/calcalignmentxposfunctor.cpp @@ -96,6 +96,12 @@ FunctorCode CalcAlignmentXPosFunctor::VisitAlignment(Alignment *alignment) FunctorCode CalcAlignmentXPosFunctor::VisitMeasure(Measure *measure) { + // We start a new Measure + // Reset the previous time position and x_rel to 0; + m_previousTime = 0.0; + // We un-measured music we never have a left barline, so do not add a default space + m_previousXRel = (measure->IsMeasuredMusic()) ? m_doc->GetDrawingUnit(100) : 0; + measure->m_measureAligner.Process(*this); return FUNCTOR_SIBLINGS; @@ -103,10 +109,7 @@ FunctorCode CalcAlignmentXPosFunctor::VisitMeasure(Measure *measure) FunctorCode CalcAlignmentXPosFunctor::VisitMeasureAligner(MeasureAligner *measureAligner) { - // We start a new MeasureAligner - // Reset the previous time position and x_rel to 0; - m_previousTime = 0.0; - m_previousXRel = m_doc->GetDrawingUnit(100); + m_lastNonTimestamp = measureAligner->GetLeftBarLineAlignment(); m_measureAligner = measureAligner; diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 603220d73c6..93766929238 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -74,6 +74,9 @@ namespace vrv { // Large spacing between syllables is a quarter note space // MAX_DURATION / pow(2.0, (DUR_4 - 2.0)) #define NEUME_LARGE_SPACE 256 +// Medium spacing between neume is a 8th note space +// MAX_DURATION / pow(2.0, (DUR_5 - 2.0)) +#define NEUME_MEDIUM_SPACE 128 // Small spacing between neume components is a 16th note space // MAX_DURATION / pow(2.0, (DUR_6 - 2.0)) #define NEUME_SMALL_SPACE 64 @@ -702,13 +705,14 @@ double LayerElement::GetAlignmentDuration( return duration->GetInterfaceAlignmentMensuralDuration(num, numbase, mensur); } if (this->Is(NC)) { + // This is called only with --neume-as-note const Object *neume = this->GetFirstAncestor(NEUME); assert(neume); const Object *syllable = neume->GetFirstAncestor(SYLLABLE); assert(syllable); - // Add a gap after the last nc of the last neume in the syllable - if ((neume->GetLast() == this) && (syllable->GetLast() == neume)) { - return NEUME_LARGE_SPACE; + // Add a larger gap after the last nc of the last neume in the syllable + if (neume->GetLast() == this) { + return (syllable->GetLast() == neume) ? NEUME_LARGE_SPACE : NEUME_MEDIUM_SPACE; } else { return NEUME_SMALL_SPACE; @@ -750,6 +754,13 @@ double LayerElement::GetAlignmentDuration( return DUR_MAX / meterUnit * meterCount; } } + // This is not called with --neume-as-note since otherwise each nc has an aligner + else if (this->Is(NEUME)) { + const Object *syllable = this->GetFirstAncestor(SYLLABLE); + assert(syllable); + // Add a larger gap after the last neume of the syllable + return (syllable->GetLast() == this) ? NEUME_MEDIUM_SPACE : NEUME_SMALL_SPACE; + } else { return 0.0; } From 53e4949a5a50984e1cccab7b4bfcdc02ebe27ae8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 2 Jun 2024 15:44:12 +0200 Subject: [PATCH 287/383] Debug code --- include/vrv/horizontalaligner.h | 6 ++++++ src/view_page.cpp | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/vrv/horizontalaligner.h b/include/vrv/horizontalaligner.h index 9c8a8da8160..73f8e09c03d 100644 --- a/include/vrv/horizontalaligner.h +++ b/include/vrv/horizontalaligner.h @@ -10,6 +10,7 @@ #include "atts_shared.h" #include "object.h" +#include "vrv.h" namespace vrv { @@ -184,6 +185,11 @@ class Alignment : public Object { */ bool HasTimestampOnly() const; + /** + * Debug message + */ + std::string LogDebugTreeMsg() override { return StringFormat("%d %f", this->GetXRel(), this->GetTime()); } + //----------------// // Static methods // //----------------// diff --git a/src/view_page.cpp b/src/view_page.cpp index 797dd0d4746..626aa7f91e7 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1107,6 +1107,16 @@ void View::DrawMeasure(DeviceContext *dc, Measure *measure, System *system) if (measure->GetDrawingEnding()) { system->AddToDrawingList(measure->GetDrawingEnding()); } + + /* + //Debug code for displaying aligner positions + for (auto &child : measure->m_measureAligner.GetChildren()) { + Alignment *alignment = vrv_cast(child); + int x = alignment->GetXRel() + measure->GetDrawingX(); + int y = system->GetDrawingY() - m_doc->GetDrawingStaffSize(100); + this->DrawVerticalLine(dc, y, y + m_doc->GetDrawingUnit(100), x, 20); + } + */ } void View::DrawMeterSigGrp(DeviceContext *dc, Layer *layer, Staff *staff) From ab2bdcaf70259c8d2532c602c41be2981d26f6c1 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 2 Jun 2024 16:29:52 +0200 Subject: [PATCH 288/383] Rename functor filename --- Verovio.xcodeproj/project.pbxproj | 32 +++++++++---------- bindings/iOS/all.h | 2 +- ...ctor.h => calcligatureorneumeposfunctor.h} | 16 +++++----- ....cpp => calcligatureorneumeposfunctor.cpp} | 10 +++--- src/page.cpp | 6 ++-- 5 files changed, 33 insertions(+), 33 deletions(-) rename include/vrv/{calcligaturenoteposfunctor.h => calcligatureorneumeposfunctor.h} (71%) rename src/{calcligaturenoteposfunctor.cpp => calcligatureorneumeposfunctor.cpp} (96%) diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 861f6d922dc..17426c1ea5e 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -1448,12 +1448,12 @@ E7265E7229DC700800D11F41 /* castofffunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7265E7029DC700800D11F41 /* castofffunctor.cpp */; }; E7265E7329DC701000D11F41 /* castofffunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7265E7029DC700800D11F41 /* castofffunctor.cpp */; }; E7265E7429DC701100D11F41 /* castofffunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7265E7029DC700800D11F41 /* castofffunctor.cpp */; }; - E738715C29CAFA7700982DE5 /* calcligaturenoteposfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E738715B29CAFA6700982DE5 /* calcligaturenoteposfunctor.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E738715D29CAFA7800982DE5 /* calcligaturenoteposfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E738715B29CAFA6700982DE5 /* calcligaturenoteposfunctor.h */; }; - E738715F29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp */; }; - E738716029CAFA9D00982DE5 /* calcligaturenoteposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp */; }; - E738716129CAFA9D00982DE5 /* calcligaturenoteposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp */; }; - E738716229CAFA9E00982DE5 /* calcligaturenoteposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp */; }; + E738715C29CAFA7700982DE5 /* calcligatureorneumeposfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E738715B29CAFA6700982DE5 /* calcligatureorneumeposfunctor.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E738715D29CAFA7800982DE5 /* calcligatureorneumeposfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E738715B29CAFA6700982DE5 /* calcligatureorneumeposfunctor.h */; }; + E738715F29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp */; }; + E738716029CAFA9D00982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp */; }; + E738716129CAFA9D00982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp */; }; + E738716229CAFA9E00982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E738715E29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp */; }; E73E86252A069C640089DF74 /* transposefunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E73E86242A069C640089DF74 /* transposefunctor.h */; }; E73E86262A069C640089DF74 /* transposefunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E73E86242A069C640089DF74 /* transposefunctor.h */; settings = {ATTRIBUTES = (Public, ); }; }; E73E86282A069C920089DF74 /* transposefunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E73E86272A069C920089DF74 /* transposefunctor.cpp */; }; @@ -2223,8 +2223,8 @@ E7231E0429B64B2D000A2BF3 /* adjustxoverflowfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = adjustxoverflowfunctor.cpp; path = src/adjustxoverflowfunctor.cpp; sourceTree = ""; }; E7265E6D29DC6FD200D11F41 /* castofffunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = castofffunctor.h; path = include/vrv/castofffunctor.h; sourceTree = ""; }; E7265E7029DC700800D11F41 /* castofffunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = castofffunctor.cpp; path = src/castofffunctor.cpp; sourceTree = ""; }; - E738715B29CAFA6700982DE5 /* calcligaturenoteposfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = calcligaturenoteposfunctor.h; path = include/vrv/calcligaturenoteposfunctor.h; sourceTree = ""; }; - E738715E29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = calcligaturenoteposfunctor.cpp; path = src/calcligaturenoteposfunctor.cpp; sourceTree = ""; }; + E738715B29CAFA6700982DE5 /* calcligatureorneumeposfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = calcligatureorneumeposfunctor.h; path = include/vrv/calcligatureorneumeposfunctor.h; sourceTree = ""; }; + E738715E29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = calcligatureorneumeposfunctor.cpp; path = src/calcligatureorneumeposfunctor.cpp; sourceTree = ""; }; E73E86242A069C640089DF74 /* transposefunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = transposefunctor.h; path = include/vrv/transposefunctor.h; sourceTree = ""; }; E73E86272A069C920089DF74 /* transposefunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = transposefunctor.cpp; path = src/transposefunctor.cpp; sourceTree = ""; }; E741ACFE299A3D1D00854426 /* calcslurdirectionfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = calcslurdirectionfunctor.h; path = include/vrv/calcslurdirectionfunctor.h; sourceTree = ""; }; @@ -3140,8 +3140,8 @@ E788335C2994EC5100D44B01 /* calcchordnoteheadsfunctor.h */, E77C197F28CD318B00F5BADA /* calcdotsfunctor.cpp */, E77C197C28CD317B00F5BADA /* calcdotsfunctor.h */, - E738715E29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp */, - E738715B29CAFA6700982DE5 /* calcligaturenoteposfunctor.h */, + E738715E29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp */, + E738715B29CAFA6700982DE5 /* calcligatureorneumeposfunctor.h */, E741AD01299A3D5B00854426 /* calcslurdirectionfunctor.cpp */, E741ACFE299A3D1D00854426 /* calcslurdirectionfunctor.h */, E7883367299500D600D44B01 /* calcspanningbeamspansfunctor.cpp */, @@ -3298,7 +3298,7 @@ 8F59294818854BF800FE51AD /* mensur.h in Headers */, 4D79641D26C152400026288B /* pageelement.h in Headers */, 4DB3D8B81F83D0B100B5FC2B /* score.h in Headers */, - E738715D29CAFA7800982DE5 /* calcligaturenoteposfunctor.h in Headers */, + E738715D29CAFA7800982DE5 /* calcligatureorneumeposfunctor.h in Headers */, 4DA0EAD922BB77AF00A7EBEB /* facsimile.h in Headers */, 4DEC4DD721C8295700D1D273 /* reg.h in Headers */, 4DA1448D1C2AB29400CB7CEE /* textelement.h in Headers */, @@ -3675,7 +3675,7 @@ BB4C4B4622A932D7001F6AF0 /* btrem.h in Headers */, BB4C4A8922A93225001F6AF0 /* c_wrapper.h in Headers */, 4DACC9BD2990F29A00B55913 /* atts_harmony.h in Headers */, - E738715C29CAFA7700982DE5 /* calcligaturenoteposfunctor.h in Headers */, + E738715C29CAFA7700982DE5 /* calcligatureorneumeposfunctor.h in Headers */, 4DACC9F32990F29A00B55913 /* atts_frettab.h in Headers */, E74A806728BC97D5005274E7 /* findfunctor.h in Headers */, 402492BE232E6FCB0017BB75 /* gracegrp.h in Headers */, @@ -4081,7 +4081,7 @@ E7A3790E29BB420300E3BA98 /* adjustxposfunctor.cpp in Sources */, E78F205329D9B04700CD5910 /* calcbboxoverflowsfunctor.cpp in Sources */, BDEF9EC826725234008A3A47 /* caesura.cpp in Sources */, - E738716029CAFA9D00982DE5 /* calcligaturenoteposfunctor.cpp in Sources */, + E738716029CAFA9D00982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */, 4DACC9D92990F29A00B55913 /* atts_gestural.cpp in Sources */, 4D16942A1E3A44F300569BF4 /* octave.cpp in Sources */, 4D16942B1E3A44F300569BF4 /* slur.cpp in Sources */, @@ -4262,7 +4262,7 @@ 40D0D5E21E3BD7FE00E6BF5C /* turn.cpp in Sources */, 40C2E41E2052A6E00003625F /* sb.cpp in Sources */, E79320672991453C00D80975 /* calcstemfunctor.cpp in Sources */, - E738715F29CAFA9500982DE5 /* calcligaturenoteposfunctor.cpp in Sources */, + E738715F29CAFA9500982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */, 4D1BE76D1C688F5A0086DC0E /* MidiEvent.cpp in Sources */, E75EA9FD29CC3A88003A97A7 /* calcarticfunctor.cpp in Sources */, 35FDEBD124B6DC5B00AC1696 /* fing.cpp in Sources */, @@ -4657,7 +4657,7 @@ E7A3790F29BB420400E3BA98 /* adjustxposfunctor.cpp in Sources */, E78F205429D9B04800CD5910 /* calcbboxoverflowsfunctor.cpp in Sources */, 8F3DD35A18854B2E0051330C /* tuplet.cpp in Sources */, - E738716129CAFA9D00982DE5 /* calcligaturenoteposfunctor.cpp in Sources */, + E738716129CAFA9D00982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */, 4DACC9DA2990F29A00B55913 /* atts_gestural.cpp in Sources */, 4DBDD6742939E1BD009EC466 /* symboldef.cpp in Sources */, BDEF9EC926725234008A3A47 /* caesura.cpp in Sources */, @@ -4945,7 +4945,7 @@ E7A3791029BB420400E3BA98 /* adjustxposfunctor.cpp in Sources */, E78F205529D9B04A00CD5910 /* calcbboxoverflowsfunctor.cpp in Sources */, 4DACC9DB2990F29A00B55913 /* atts_gestural.cpp in Sources */, - E738716229CAFA9E00982DE5 /* calcligaturenoteposfunctor.cpp in Sources */, + E738716229CAFA9E00982DE5 /* calcligatureorneumeposfunctor.cpp in Sources */, BB4C4AC722A932B6001F6AF0 /* page.cpp in Sources */, BB4C4AE322A932BC001F6AF0 /* choice.cpp in Sources */, BB4C4B7722A932D7001F6AF0 /* syllable.cpp in Sources */, diff --git a/bindings/iOS/all.h b/bindings/iOS/all.h index d99cbd442f9..acb38d8b1e8 100644 --- a/bindings/iOS/all.h +++ b/bindings/iOS/all.h @@ -54,7 +54,7 @@ #import #import #import -#import +#import #import #import #import diff --git a/include/vrv/calcligaturenoteposfunctor.h b/include/vrv/calcligatureorneumeposfunctor.h similarity index 71% rename from include/vrv/calcligaturenoteposfunctor.h rename to include/vrv/calcligatureorneumeposfunctor.h index bcf41a2f1ea..46482134313 100644 --- a/include/vrv/calcligaturenoteposfunctor.h +++ b/include/vrv/calcligatureorneumeposfunctor.h @@ -1,32 +1,32 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: calcligaturenoteposfunctor.h +// Name: calcligatureorneumeposfunctor.h // Author: David Bauer // Created: 2023 // Copyright (c) Authors and others. All rights reserved. ///////////////////////////////////////////////////////////////////////////// -#ifndef __VRV_CALCLIGATURENOTEPOSFUNCTOR_H__ -#define __VRV_CALCLIGATURENOTEPOSFUNCTOR_H__ +#ifndef __VRV_CALCLIGATUREORNEUMEPOSFUNCTOR_H__ +#define __VRV_CALCLIGATUREORNEUMEPOSFUNCTOR_H__ #include "functor.h" namespace vrv { //---------------------------------------------------------------------------- -// CalcLigatureNotePosFunctor +// CalcLigatureOrNeumePosFunctor //---------------------------------------------------------------------------- /** * This class sets the note position for each note in ligature. */ -class CalcLigatureNotePosFunctor : public DocFunctor { +class CalcLigatureOrNeumePosFunctor : public DocFunctor { public: /** * @name Constructors, destructors */ ///@{ - CalcLigatureNotePosFunctor(Doc *doc); - virtual ~CalcLigatureNotePosFunctor() = default; + CalcLigatureOrNeumePosFunctor(Doc *doc); + virtual ~CalcLigatureOrNeumePosFunctor() = default; ///@} /* @@ -53,4 +53,4 @@ class CalcLigatureNotePosFunctor : public DocFunctor { } // namespace vrv -#endif // __VRV_CALCLIGATURENOTEPOSFUNCTOR_H__ +#endif // __VRV_CALCLIGATUREORNEUMEPOSFUNCTOR_H__ diff --git a/src/calcligaturenoteposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp similarity index 96% rename from src/calcligaturenoteposfunctor.cpp rename to src/calcligatureorneumeposfunctor.cpp index 52cd1b7d497..8f8077329e8 100644 --- a/src/calcligaturenoteposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -1,11 +1,11 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: calcligaturenoteposfunctor.cpp +// Name: calcligaturorneumeposfunctor.cpp // Author: David Bauer // Created: 2023 // Copyright (c) Authors and others. All rights reserved. ///////////////////////////////////////////////////////////////////////////// -#include "calcligaturenoteposfunctor.h" +#include "calcligatureorneumeposfunctor.h" //---------------------------------------------------------------------------- @@ -18,12 +18,12 @@ namespace vrv { //---------------------------------------------------------------------------- -// CalcLigatureNotePosFunctor +// CalcLigatureOrNeumePosFunctor //---------------------------------------------------------------------------- -CalcLigatureNotePosFunctor::CalcLigatureNotePosFunctor(Doc *doc) : DocFunctor(doc) {} +CalcLigatureOrNeumePosFunctor::CalcLigatureOrNeumePosFunctor(Doc *doc) : DocFunctor(doc) {} -FunctorCode CalcLigatureNotePosFunctor::VisitLigature(Ligature *ligature) +FunctorCode CalcLigatureOrNeumePosFunctor::VisitLigature(Ligature *ligature) { if (m_doc->GetOptions()->m_ligatureAsBracket.GetValue()) return FUNCTOR_CONTINUE; diff --git a/src/page.cpp b/src/page.cpp index 6e4d3f30109..b14fa603d70 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -44,7 +44,7 @@ #include "calcchordnoteheadsfunctor.h" #include "calcdotsfunctor.h" #include "calcledgerlinesfunctor.h" -#include "calcligaturenoteposfunctor.h" +#include "calcligatureorneumeposfunctor.h" #include "calcslurdirectionfunctor.h" #include "calcspanningbeamspansfunctor.h" #include "calcstemfunctor.h" @@ -336,8 +336,8 @@ void Page::ResetAligners() CalcAlignmentPitchPosFunctor calcAlignmentPitchPos(doc); this->Process(calcAlignmentPitchPos); - CalcLigatureNotePosFunctor calcLigatureNotePos(doc); - this->Process(calcLigatureNotePos); + CalcLigatureOrNeumePosFunctor calcLigatureOrNeumePos(doc); + this->Process(calcLigatureOrNeumePos); CalcStemFunctor calcStem(doc); this->Process(calcStem); From 8fa3faa3f473f27d7cbc97410f13920bfe4238b1 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 20 Jun 2024 15:21:30 +0200 Subject: [PATCH 289/383] Simplifying drawing code for Nc and Liquescent --- src/view_neume.cpp | 100 +++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 62 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 92c162235f0..4eebb5cc3c1 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -33,6 +33,12 @@ namespace vrv { +struct NcDrawingParams { + wchar_t fontNo = SMUFL_E990_chantPunctum; + float xOffset = 0; + float yOffset = 0; +}; + void View::DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) { assert(dc); @@ -63,38 +69,27 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer assert(staff); assert(measure); - struct drawingParams { - wchar_t fontNo = SMUFL_E990_chantPunctum; - wchar_t fontNoLiq[5] = {}; - float xOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - float yOffsetLiq[5] = { 0, 0, 0, 0, 0 }; - }; - std::vector params; - params.push_back(drawingParams()); + NcDrawingParams params[3]; dc->StartGraphic(element, "", element->GetID()); Nc *nc = dynamic_cast(element->GetParent()); if (nc->GetCurve() == curvatureDirection_CURVE_c) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB92_staffPosRaise3; - params.at(0).fontNoLiq[2] = SMUFL_E995_chantAuctumDesc; - params.at(0).fontNoLiq[3] = SMUFL_EB91_staffPosRaise2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = -1.5; - params.at(0).yOffsetLiq[4] = -1.75; + params[0].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + params[1].fontNo = SMUFL_E995_chantAuctumDesc; + params[2].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + params[2].xOffset = 0.8; + params[0].yOffset = -1.5; + params[2].yOffset = -1.75; } else if (nc->GetCurve() == curvatureDirection_CURVE_a) { - params.at(0).fontNoLiq[0] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).fontNoLiq[1] = SMUFL_EB98_staffPosLower1; - params.at(0).fontNoLiq[2] = SMUFL_E994_chantAuctumAsc; - params.at(0).fontNoLiq[3] = SMUFL_EB99_staffPosLower2; - params.at(0).fontNoLiq[4] = SMUFL_E9BE_chantConnectingLineAsc3rd; - params.at(0).xOffsetLiq[4] = 0.8; - params.at(0).yOffsetLiq[0] = 0.5; - params.at(0).yOffsetLiq[4] = 0.75; + params[0].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + params[1].fontNo = SMUFL_E994_chantAuctumAsc; + params[2].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + params[2].xOffset = 0.8; + params[0].yOffset = 0.5; + params[2].yOffset = 0.75; } const int noteHeight @@ -109,11 +104,9 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer noteY -= staff->GetDrawingRotationOffsetFor(noteX); } - for (auto it = params.begin(); it != params.end(); it++) { - for (int i = 0; i < static_cast(sizeof(params.at(0).fontNoLiq)); i++) { - DrawSmuflCode(dc, noteX + it->xOffsetLiq[i] * noteWidth, noteY + it->yOffsetLiq[i] * noteHeight, - it->fontNoLiq[i], staff->m_drawingStaffSize, false, true); - } + for (int i = 0; i < static_cast(sizeof(params)); ++i) { + DrawSmuflCode(dc, noteX + params[i].xOffset * noteWidth, noteY + params[i].yOffset * noteHeight, + params[i].fontNo, staff->m_drawingStaffSize, false, true); } dc->EndGraphic(element, this); @@ -134,14 +127,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff return; } - struct drawingParams { - wchar_t fontNo = SMUFL_E990_chantPunctum; - wchar_t fontNoLiq[5] = {}; - float xOffset = 0; - float yOffset = 0; - }; - std::vector params; - params.push_back(drawingParams()); + NcDrawingParams params; dc->StartGraphic(element, "", element->GetID()); @@ -153,7 +139,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff // Check if nc is part of a ligature or is an inclinatum if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { - params.at(0).fontNo = SMUFL_E991_chantPunctumInclinatum; + params.fontNo = SMUFL_E991_chantPunctumInclinatum; } else if (nc->GetLigated() == BOOLEAN_true) { int pitchDifference = 0; @@ -165,8 +151,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); assert(lastNc); pitchDifference = nc->PitchDifferenceTo(lastNc); - params.at(0).xOffset = -1; - params.at(0).yOffset = -pitchDifference; + params.xOffset = -1; + params.yOffset = -pitchDifference; } else { isFirst = true; @@ -175,36 +161,28 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff Nc *nextNc = dynamic_cast(nextSibling); assert(nextNc); pitchDifference = nextNc->PitchDifferenceTo(nc); - params.at(0).yOffset = pitchDifference; + params.yOffset = pitchDifference; } } // set the glyph switch (pitchDifference) { - case -1: - params.at(0).fontNo = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; - break; - case -2: - params.at(0).fontNo = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; - break; - case -3: - params.at(0).fontNo = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; - break; - case -4: - params.at(0).fontNo = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; - break; + case -1: params.fontNo = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; break; + case -2: params.fontNo = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; break; + case -3: params.fontNo = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; break; + case -4: params.fontNo = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; break; default: break; } } // If the nc is supposed to be a virga and currently is being rendered as a punctum // change it to a virga - if (nc->GetTilt() == COMPASSDIRECTION_s && params.at(0).fontNo == SMUFL_E990_chantPunctum) { - params.at(0).fontNo = SMUFL_E996_chantPunctumVirga; + if (nc->GetTilt() == COMPASSDIRECTION_s && params.fontNo == SMUFL_E990_chantPunctum) { + params.fontNo = SMUFL_E996_chantPunctumVirga; } - else if (nc->GetTilt() == COMPASSDIRECTION_n && params.at(0).fontNo == SMUFL_E990_chantPunctum) { - params.at(0).fontNo = SMUFL_E997_chantPunctumVirgaReversed; + else if (nc->GetTilt() == COMPASSDIRECTION_n && params.fontNo == SMUFL_E990_chantPunctum) { + params.fontNo = SMUFL_E997_chantPunctumVirgaReversed; } const int noteHeight @@ -216,7 +194,7 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff int noteY = nc->GetDrawingY(); if (nc->HasFacs() && m_doc->IsNeumeLines()) { - params.at(0).xOffset = 0; + params.xOffset = 0; } // Not sure about this if - the nc pname and oct are going to be ignored else if (neume->HasFacs() && m_doc->IsNeumeLines()) { @@ -229,10 +207,8 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } if (!nc->HasCurve()) { - for (auto it = params.begin(); it != params.end(); it++) { - DrawSmuflCode(dc, noteX + it->xOffset * noteWidth, noteY + it->yOffset * noteHeight, it->fontNo, - staff->m_drawingStaffSize, false, true); - } + DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, + staff->m_drawingStaffSize, false, true); } // Draw the children From 196e40d4073b1fd39e58ee723d892c63a14d91e1 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 21 Jun 2024 10:38:42 -0400 Subject: [PATCH 290/383] Skip `CalcAlignmentPitchPosFunctor` for accid in neume lines --- src/calcalignmentpitchposfunctor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calcalignmentpitchposfunctor.cpp b/src/calcalignmentpitchposfunctor.cpp index 13e212d1ea8..5c667f5b6de 100644 --- a/src/calcalignmentpitchposfunctor.cpp +++ b/src/calcalignmentpitchposfunctor.cpp @@ -60,8 +60,9 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE if (layerElement->Is(ACCID)) { Accid *accid = vrv_cast(layerElement); assert(accid); - if (!accid->GetFirstAncestor(NOTE) && !accid->GetFirstAncestor(CUSTOS)) { + if (!accid->GetFirstAncestor(NOTE) && !accid->GetFirstAncestor(CUSTOS) && !m_doc->IsNeumeLines()) { // do something for accid that are not children of a note - e.g., mensural? + // skip for neume-lines mode as accid doesn't have a pitch in this case accid->SetDrawingYRel(staffY->CalcPitchPosYRel(m_doc, accid->CalcDrawingLoc(layerY, layerElementY))); } // override if staff position is set explicitly From f981c080dd50d048b7981dc7bb6073d05e970f40 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Fri, 21 Jun 2024 11:00:27 -0400 Subject: [PATCH 291/383] Reset drawing position after dragging for neume lines - Call `Page::LayOutPitchPos()` after dragging - Fix pitch difference calculation in `EditorToolkitNeume::AdjustPitchFromPosition` Refs: https://github.com/DDMAL/Neon/issues/1223, https://github.com/DDMAL/Neon/issues/1227 --- src/editortoolkit_neume.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 063b13e89b1..73decbabee9 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -739,6 +739,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) } Layer *layer = vrv_cast(element->GetFirstAncestor(LAYER)); layer->ReorderByXPos(); // Reflect position order of elements internally (and in the resulting output file) + m_doc->GetDrawingPage()->LayOutPitchPos(); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", status); m_editInfo.import("message", message); @@ -4221,11 +4222,11 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - const int pitchDifference = round( - (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) - - m_view->ToLogicalY(fi->GetZone()->GetUly())) - / staffSize - - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset(staff->m_drawingNotationType))); + const int pitchDifference + = round((double)((staff->GetDrawingY() + - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) + - m_view->ToLogicalY(fi->GetZone()->GetUly()))) + / (double)(staffSize)); pi->AdjustPitchByOffset(-pitchDifference); return true; } @@ -4288,11 +4289,11 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) } pi->SetOct(octave); - const int pitchDifference = round( - (staff->GetDrawingY() - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) - - m_view->ToLogicalY(fi->GetZone()->GetUly())) - / staffSize - - (((staff->m_drawingLines - 1) * 2) - clef->GetClefLocOffset(staff->m_drawingNotationType))); + const int pitchDifference + = round((double)((staff->GetDrawingY() + - staff->GetDrawingRotationOffsetFor(m_view->ToLogicalX(fi->GetZone()->GetUlx())) + - m_view->ToLogicalY(fi->GetZone()->GetUly()))) + / (double)(staffSize)); pi->AdjustPitchByOffset(-pitchDifference); } From 0c7b7f084c76a4cf4f4ab99b75d8e7f54114f8be Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 25 Jun 2024 16:53:27 +0200 Subject: [PATCH 292/383] Add a functor to align neumes --- include/vrv/adjustneumexfunctor.h | 11 +++++-- src/adjustneumexfunctor.cpp | 52 +++++++++++++++++++++++++++++++ src/page.cpp | 5 +++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/include/vrv/adjustneumexfunctor.h b/include/vrv/adjustneumexfunctor.h index bc55c0fda41..c20509d2ea7 100644 --- a/include/vrv/adjustneumexfunctor.h +++ b/include/vrv/adjustneumexfunctor.h @@ -32,13 +32,19 @@ class AdjustNeumeXFunctor : public DocFunctor { /* * Abstract base implementation */ - bool ImplementsEndInterface() const override { return false; } + bool ImplementsEndInterface() const override { return true; } /* * Functor interface */ ///@{ + /// + FunctorCode VisitLayer(Layer *layer) override; + FunctorCode VisitLayerEnd(Layer *layer) override; + FunctorCode VisitStaff(Staff *staff) override; FunctorCode VisitNeume(Neume *neume) override; + FunctorCode VisitSyl(Syl *syl) override; + ///@} protected: @@ -48,7 +54,8 @@ class AdjustNeumeXFunctor : public DocFunctor { public: // private: - // + /** The minimu position of the next syl */ + int m_minPos; }; } // namespace vrv diff --git a/src/adjustneumexfunctor.cpp b/src/adjustneumexfunctor.cpp index 11b2d59fb0d..38983447df7 100644 --- a/src/adjustneumexfunctor.cpp +++ b/src/adjustneumexfunctor.cpp @@ -10,7 +10,11 @@ //---------------------------------------------------------------------------- #include "doc.h" +#include "layer.h" #include "score.h" +#include "staff.h" +#include "syl.h" +#include "syllable.h" //---------------------------------------------------------------------------- @@ -22,9 +26,57 @@ namespace vrv { AdjustNeumeXFunctor::AdjustNeumeXFunctor(Doc *doc) : DocFunctor(doc) {} +FunctorCode AdjustNeumeXFunctor::VisitLayer(Layer *layer) +{ + m_minPos = VRV_UNSET; + + return FUNCTOR_CONTINUE; +} + +FunctorCode AdjustNeumeXFunctor::VisitLayerEnd(Layer *layer) +{ + // Alignment *alignment = m_rightBarline->GetAlignment(); + Measure *measure = vrv_cast(layer->GetFirstAncestor(MEASURE)); + assert(measure); + Alignment *alignment = measure->m_measureAligner.GetRightAlignment(); + assert(alignment); + + int selfLeft = alignment->GetXRel(); + if (selfLeft < m_minPos) { + const int adjust = m_minPos - selfLeft; + alignment->SetXRel(alignment->GetXRel() + adjust); + } + + m_minPos = VRV_UNSET; + + return FUNCTOR_CONTINUE; +} + FunctorCode AdjustNeumeXFunctor::VisitNeume(Neume *neume) { return FUNCTOR_CONTINUE; } +FunctorCode AdjustNeumeXFunctor::VisitStaff(Staff *staff) +{ + if (!staff->IsNeume()) return FUNCTOR_SIBLINGS; + + return FUNCTOR_CONTINUE; +} + +FunctorCode AdjustNeumeXFunctor::VisitSyl(Syl *syl) +{ + Alignment *alignment = syl->GetAlignment(); + + int selfLeft = syl->GetContentLeft(); + if (selfLeft < m_minPos) { + const int adjust = m_minPos - selfLeft; + alignment->SetXRel(alignment->GetXRel() + adjust); + } + + m_minPos = syl->GetContentRight(); + + return FUNCTOR_CONTINUE; +} + } // namespace vrv diff --git a/src/page.cpp b/src/page.cpp index b14fa603d70..f336123d746 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -23,6 +23,7 @@ #include "adjustgracexposfunctor.h" #include "adjustharmgrpsspacingfunctor.h" #include "adjustlayersfunctor.h" +#include "adjustneumexfunctor.h" #include "adjustslursfunctor.h" #include "adjuststaffoverlapfunctor.h" #include "adjustsylspacingfunctor.h" @@ -397,6 +398,10 @@ void Page::LayOutHorizontally() AdjustDotsFunctor adjustDots(doc, scoreDef->GetStaffNs()); this->Process(adjustDots); + // Adjust the X position of the neume and syllables + AdjustNeumeXFunctor adjustNeumeX(doc); + this->Process(adjustNeumeX); + // Adjust layers again, this time including dots positioning AdjustLayersFunctor adjustLayersWithDots(doc, scoreDef->GetStaffNs()); adjustLayersWithDots.IgnoreDots(false); From c7ecae4452451f58c24cf52c11c3c57665046559 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 25 Jun 2024 16:54:52 +0200 Subject: [PATCH 293/383] Fix class name capitalization --- include/vrv/oriscus.h | 2 +- include/vrv/quilisma.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h index 5cc62f5d5d4..391784a6e27 100644 --- a/include/vrv/oriscus.h +++ b/include/vrv/oriscus.h @@ -31,7 +31,7 @@ class Oriscus : public LayerElement, public PitchInterface, public PositionInter virtual ~Oriscus(); virtual Object *Clone() const { return new Oriscus(*this); } virtual void Reset(); - virtual std::string GetClassName() const { return "oriscus"; } + virtual std::string GetClassName() const { return "Oriscus"; } ///@} /** diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h index 4bafb203068..2ffbf576f94 100644 --- a/include/vrv/quilisma.h +++ b/include/vrv/quilisma.h @@ -31,7 +31,7 @@ class Quilisma : public LayerElement, public PitchInterface, public PositionInte virtual ~Quilisma(); virtual Object *Clone() const { return new Quilisma(*this); } virtual void Reset(); - virtual std::string GetClassName() const { return "quilisma"; } + virtual std::string GetClassName() const { return "Quilisma"; } ///@} /** From 43c1887b805c0edfd282406ba2fc79b2ac58f30f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 25 Jun 2024 16:56:31 +0200 Subject: [PATCH 294/383] Scaffold drawing oriscus and quilisma methods --- include/vrv/view.h | 2 ++ src/view_element.cpp | 6 ++++++ src/view_neume.cpp | 29 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/include/vrv/view.h b/include/vrv/view.h index 49288554532..d72d1a39703 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -419,6 +419,8 @@ class View { void DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); void DrawNeume(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); + void DrawOriscus(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); + void DrawQuilisma(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure); ///@} /** diff --git a/src/view_element.cpp b/src/view_element.cpp index a577500ac4c..eb253fc26fd 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -175,12 +175,18 @@ void View::DrawLayerElement(DeviceContext *dc, LayerElement *element, Layer *lay else if (element->Is(NEUME)) { this->DrawNeume(dc, element, layer, staff, measure); } + else if (element->Is(ORISCUS)) { + this->DrawOriscus(dc, element, layer, staff, measure); + } else if (element->Is(PLICA)) { this->DrawPlica(dc, element, layer, staff, measure); } else if (element->Is(PROPORT)) { this->DrawProport(dc, element, layer, staff, measure); } + else if (element->Is(QUILISMA)) { + this->DrawQuilisma(dc, element, layer, staff, measure); + } else if (element->Is(REST)) { this->DrawDurationElement(dc, element, layer, staff, measure); } diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 4eebb5cc3c1..c811f0dec62 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -328,4 +328,33 @@ void View::DrawDivLine(DeviceContext *dc, LayerElement *element, Layer *layer, S dc->EndGraphic(element, this); } + +void View::DrawOriscus(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) +{ + assert(dc); + assert(layer); + assert(staff); + assert(measure); + + NcDrawingParams params[3]; + + dc->StartGraphic(element, "", element->GetID()); + + dc->EndGraphic(element, this); +} + +void View::DrawQuilisma(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) +{ + assert(dc); + assert(layer); + assert(staff); + assert(measure); + + NcDrawingParams params[3]; + + dc->StartGraphic(element, "", element->GetID()); + + dc->EndGraphic(element, this); +} + } // namespace vrv From fbe17362bc23375d01aed27e6b7de10c669fdf3f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 30 Jun 2024 17:44:12 +0200 Subject: [PATCH 295/383] Update Leipzig to 5.2.91 and add corresponding glyphs --- data/Bravura.css | 2 +- data/Bravura.xml | 2 + data/Bravura/E9A1.xml | 1 + data/Bravura/EA2A.xml | 1 + data/Leipzig.css | 2 +- data/Leipzig.xml | 11 +---- data/Leipzig/E9A1.xml | 1 + data/Leipzig/EA2A.xml | 1 + fonts/Leipzig/Leipzig.svg | 65 +++++++++++++++------------- fonts/Leipzig/Leipzig.ttf | Bin 127396 -> 128628 bytes fonts/Leipzig/Leipzig.woff2 | Bin 45060 -> 45848 bytes fonts/Leipzig/leipzig_metadata.json | 58 +++++++++++++++---------- fonts/supported.xml | 4 +- include/vrv/smufl.h | 13 ++---- 14 files changed, 83 insertions(+), 78 deletions(-) create mode 100644 data/Bravura/E9A1.xml create mode 100644 data/Bravura/EA2A.xml create mode 100644 data/Leipzig/E9A1.xml create mode 100644 data/Leipzig/EA2A.xml diff --git a/data/Bravura.css b/data/Bravura.css index f3bcd5d1a3f..f99bbbb41c6 100644 --- a/data/Bravura.css +++ b/data/Bravura.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Bravura'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Bravura.xml b/data/Bravura.xml index 2b184736543..381aab1252b 100644 --- a/data/Bravura.xml +++ b/data/Bravura.xml @@ -716,6 +716,7 @@ + @@ -749,6 +750,7 @@ + diff --git a/data/Bravura/E9A1.xml b/data/Bravura/E9A1.xml new file mode 100644 index 00000000000..4c6e9ae0776 --- /dev/null +++ b/data/Bravura/E9A1.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Bravura/EA2A.xml b/data/Bravura/EA2A.xml new file mode 100644 index 00000000000..a91a6b22a15 --- /dev/null +++ b/data/Bravura/EA2A.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig.css b/data/Leipzig.css index 171e8eba364..716af530606 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index a62291dca4f..83f458ce741 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -709,6 +709,7 @@ + @@ -805,13 +806,5 @@ - - - - - - - - - + \ No newline at end of file diff --git a/data/Leipzig/E9A1.xml b/data/Leipzig/E9A1.xml new file mode 100644 index 00000000000..4c6e9ae0776 --- /dev/null +++ b/data/Leipzig/E9A1.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/EA2A.xml b/data/Leipzig/EA2A.xml new file mode 100644 index 00000000000..6bae39ea1d9 --- /dev/null +++ b/data/Leipzig/EA2A.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fonts/Leipzig/Leipzig.svg b/fonts/Leipzig/Leipzig.svg index 26422411775..f056ef31e1d 100644 --- a/fonts/Leipzig/Leipzig.svg +++ b/fonts/Leipzig/Leipzig.svg @@ -2,11 +2,11 @@ -Created by FontForge 20220308 at Wed Jun 5 15:10:21 2024 +Created by FontForge 20220308 at Sun Jun 30 17:35:16 2024 By Laurent Pugin Created by Etienne Darbellay, Jean-Francois Marti, Laurent Pugin, and Klaus Rettinghaus. This font is licensed under the SIL Open Font License \(http://scripts.sil.org/OFL\). -Version 5.2.89 +Version 5.2.91 @@ -2166,8 +2166,8 @@ d="M6 -401c-4 0 -6 5 -6 8v448c0 43 44 47 63 47h9c27 0 72 -3 72 -45v-140c0 -4 -4 - + @@ -2437,33 +2437,36 @@ d="M120.146 498.265c0.224774 0 17.1897 -1.82497 17.1897 -14.4827c0 -13.3186 -15. /> - - + - - - - - - - + + + + + + + + diff --git a/fonts/Leipzig/Leipzig.ttf b/fonts/Leipzig/Leipzig.ttf index 8eafdf58bb892464610ba28c3e88d11d445bbb33..135e2672dd89ff6b5f35570a3bda2ffac91f0af7 100644 GIT binary patch delta 5028 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 delta 3742 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 diff --git a/fonts/Leipzig/Leipzig.woff2 b/fonts/Leipzig/Leipzig.woff2 index 174d3b074450ed34b0a1f3d3c6a9ad5414227368..85ef4546089cecf943cf76d8c1c3762e61c03ada 100644 GIT binary patch literal 45848 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

?0wt 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 diff --git a/fonts/Leipzig/leipzig_metadata.json b/fonts/Leipzig/leipzig_metadata.json index 113fe16ddb9..2ad4f419d41 100644 --- a/fonts/Leipzig/leipzig_metadata.json +++ b/fonts/Leipzig/leipzig_metadata.json @@ -30,7 +30,7 @@ "tupletBracketThickness": 0.16 }, "fontName": "Leipzig", - "fontVersion": "5.2.89", + "fontVersion": "5.2.91", "glyphBBoxes": { "4stringTabClef": { "bBoxNE": [ @@ -1414,12 +1414,12 @@ }, "chantPunctumDeminutum": { "bBoxNE": [ - 0.5, - 0.352 + 0.364, + 0.232 ], "bBoxSW": [ - 0.012, - -0.348 + 0.0, + -0.228 ] }, "chantPunctumInclinatum": { @@ -3632,6 +3632,16 @@ -1.016 ] }, + "medRenOriscusCMN": { + "bBoxNE": [ + 1.276, + 0.52 + ], + "bBoxSW": [ + 0.0, + -0.524 + ] + }, "medRenSharpCroix": { "bBoxNE": [ 1.168, @@ -4464,91 +4474,91 @@ }, "mensuralProportion1": { "bBoxNE": [ - 0.72, + 0.8, 0.76 ], "bBoxSW": [ - -0.0006, + 0.0794, -0.8 ] }, "mensuralProportion2": { "bBoxNE": [ - 1.0161, + 1.0961, 0.7601 ], "bBoxSW": [ - 0.0, + 0.08, -0.8 ] }, "mensuralProportion3": { "bBoxNE": [ - 0.864, + 0.952, 0.76 ], "bBoxSW": [ - 0.004, + 0.092, -0.804 ] }, "mensuralProportion4": { "bBoxNE": [ - 1.016, + 1.096, 0.72 ], "bBoxSW": [ - 0.0, + 0.08, -0.808 ] }, "mensuralProportion5": { "bBoxNE": [ - 1.016, + 1.04, 0.768 ], "bBoxSW": [ - -0.008, + 0.052, -0.8001 ] }, "mensuralProportion6": { "bBoxNE": [ - 0.912, + 0.988, 0.76 ], "bBoxSW": [ - -0.0213, + 0.0587, -0.8 ] }, "mensuralProportion7": { "bBoxNE": [ - 0.932, + 1.012, 0.76 ], "bBoxSW": [ - 0.0, - -0.8 + 0.08, + -0.804 ] }, "mensuralProportion8": { "bBoxNE": [ - 0.932, + 1.012, 0.76 ], "bBoxSW": [ - 0.0, + 0.08, -0.8 ] }, "mensuralProportion9": { "bBoxNE": [ - 0.912, + 0.992, 0.76 ], "bBoxSW": [ - -0.0213, + 0.0627, -0.8 ] }, diff --git a/fonts/supported.xml b/fonts/supported.xml index e899a6c2ff1..d72c32a4d66 100644 --- a/fonts/supported.xml +++ b/fonts/supported.xml @@ -2363,7 +2363,7 @@ - + U+E9AF U+E990 @@ -2468,7 +2468,7 @@ - + U+EA2F U+EA20 diff --git a/include/vrv/smufl.h b/include/vrv/smufl.h index 7ba69a44518..ac712b5bc85 100644 --- a/include/vrv/smufl.h +++ b/include/vrv/smufl.h @@ -432,10 +432,6 @@ enum { SMUFL_E923_mensuralProlationCombiningThreeDotsTri = 0xE923, SMUFL_E924_mensuralProlationCombiningDotVoid = 0xE924, SMUFL_E925_mensuralProlationCombiningStroke = 0xE925, - SMUFL_E926_mensuralProportion1 = 0xE926, - SMUFL_E927_mensuralProportion2 = 0xE927, - SMUFL_E928_mensuralProportion3 = 0xE928, - SMUFL_E929_mensuralProportion4 = 0xE929, SMUFL_E938_mensuralNoteheadSemibrevisBlack = 0xE938, SMUFL_E939_mensuralNoteheadSemibrevisVoid = 0xE939, SMUFL_E93C_mensuralNoteheadMinimaWhite = 0xE93C, @@ -466,6 +462,7 @@ enum { SMUFL_E99B_chantQuilisma = 0xE99B, SMUFL_E99E_chantOriscusLiquescens = 0xE99E, SMUFL_E99F_chantStrophicus = 0xE99F, + SMUFL_E9A1_chantPunctumDeminutum = 0xE9A1, SMUFL_E9B0_chantPodatusLower = 0xE9B0, SMUFL_E9B1_chantPodatusUpper = 0xE9B1, SMUFL_E9B2_chantDeminutumUpper = 0xE9B2, @@ -499,6 +496,7 @@ enum { SMUFL_E9F8_mensuralRestSemifusa = 0xE9F8, SMUFL_EA02_mensuralCustosUp = 0xEA02, SMUFL_EA06_chantCustosStemUpPosMiddle = 0xEA06, + SMUFL_EA2A_medRenOriscusCMN = 0xEA2A, SMUFL_EA50_figbass0 = 0xEA50, SMUFL_EA51_figbass1 = 0xEA51, SMUFL_EA52_figbass2 = 0xEA52, @@ -646,15 +644,10 @@ enum { SMUFL_ECB7_metAugmentationDot = 0xECB7, SMUFL_ED40_articSoftAccentAbove = 0xED40, SMUFL_ED41_articSoftAccentBelow = 0xED41, - SMUFL_EE90_mensuralProportion5 = 0xEE90, - SMUFL_EE91_mensuralProportion6 = 0xEE91, - SMUFL_EE92_mensuralProportion7 = 0xEE92, - SMUFL_EE93_mensuralProportion8 = 0xEE93, - SMUFL_EE94_mensuralProportion9 = 0xEE94, }; /** The number of glyphs for verification **/ -#define SMUFL_COUNT 629 +#define SMUFL_COUNT 622 } // namespace vrv From d0dd8de597a7af1bfd19df1ea4ab18bad01f199f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 1 Jul 2024 07:51:32 +0200 Subject: [PATCH 296/383] Add vu spacing between syl --- src/adjustneumexfunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adjustneumexfunctor.cpp b/src/adjustneumexfunctor.cpp index 38983447df7..eb104e606ee 100644 --- a/src/adjustneumexfunctor.cpp +++ b/src/adjustneumexfunctor.cpp @@ -74,7 +74,7 @@ FunctorCode AdjustNeumeXFunctor::VisitSyl(Syl *syl) alignment->SetXRel(alignment->GetXRel() + adjust); } - m_minPos = syl->GetContentRight(); + m_minPos = syl->GetContentRight() + m_doc->GetDrawingUnit(100); return FUNCTOR_CONTINUE; } From 18c3389b79c08fb785202e40d88df96aff9684d5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 1 Jul 2024 07:52:36 +0200 Subject: [PATCH 297/383] Draw Oriscus and Quilisma and Liquescent with no curve --- src/alignfunctor.cpp | 2 +- src/view_neume.cpp | 189 ++++++++++++++++++++++++++++--------------- 2 files changed, 125 insertions(+), 66 deletions(-) diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index 476bf07f10e..517f3ccf135 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -394,7 +394,7 @@ FunctorCode AlignHorizontallyFunctor::VisitMeasureEnd(Measure *measure) if (m_hasMultipleLayer) measure->HasAlignmentRefWithMultipleLayers(true); - measure->m_measureAligner.LogDebugTree(3); + // measure->m_measureAligner.LogDebugTree(3); return FUNCTOR_CONTINUE; } diff --git a/src/view_neume.cpp b/src/view_neume.cpp index c811f0dec62..c6055ce75cb 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -34,7 +34,7 @@ namespace vrv { struct NcDrawingParams { - wchar_t fontNo = SMUFL_E990_chantPunctum; + wchar_t fontNo = 0; float xOffset = 0; float yOffset = 0; }; @@ -91,6 +91,9 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer params[0].yOffset = 0.5; params[2].yOffset = 0.75; } + else { + params[0].fontNo = SMUFL_E9A1_chantPunctumDeminutum; + } const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); @@ -131,86 +134,103 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff dc->StartGraphic(element, "", element->GetID()); + const bool hasLiquescent = (nc->FindDescendantByType(LIQUESCENT)); + const bool hasOriscus = (nc->FindDescendantByType(ORISCUS)); + const bool hasQuilisma = (nc->FindDescendantByType(QUILISMA)); + /******************************************************************/ - Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); - assert(neume); - int position = neume->GetChildIndex(element); + if (!hasLiquescent && !hasOriscus && !hasQuilisma) { - // Check if nc is part of a ligature or is an inclinatum - if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { - params.fontNo = SMUFL_E991_chantPunctumInclinatum; - } - else if (nc->GetLigated() == BOOLEAN_true) { - int pitchDifference = 0; - bool isFirst; - int ligCount = neume->GetLigatureCount(position); - - if (ligCount % 2 == 0) { - isFirst = false; - Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); - assert(lastNc); - pitchDifference = nc->PitchDifferenceTo(lastNc); - params.xOffset = -1; - params.yOffset = -pitchDifference; + params.fontNo = SMUFL_E990_chantPunctum; + + Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); + assert(neume); + int position = neume->GetChildIndex(element); + + // Check if nc is part of a ligature or is an inclinatum + if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { + params.fontNo = SMUFL_E991_chantPunctumInclinatum; } - else { - isFirst = true; - Object *nextSibling = neume->GetChild(position + 1); - if (nextSibling != NULL) { - Nc *nextNc = dynamic_cast(nextSibling); - assert(nextNc); - pitchDifference = nextNc->PitchDifferenceTo(nc); - params.yOffset = pitchDifference; + else if (nc->GetLigated() == BOOLEAN_true) { + int pitchDifference = 0; + bool isFirst; + int ligCount = neume->GetLigatureCount(position); + + if (ligCount % 2 == 0) { + isFirst = false; + Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); + assert(lastNc); + pitchDifference = nc->PitchDifferenceTo(lastNc); + params.xOffset = -1; + params.yOffset = -pitchDifference; + } + else { + isFirst = true; + Object *nextSibling = neume->GetChild(position + 1); + if (nextSibling != NULL) { + Nc *nextNc = dynamic_cast(nextSibling); + assert(nextNc); + pitchDifference = nextNc->PitchDifferenceTo(nc); + params.yOffset = pitchDifference; + } } - } - // set the glyph - switch (pitchDifference) { - case -1: params.fontNo = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; break; - case -2: params.fontNo = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; break; - case -3: params.fontNo = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; break; - case -4: params.fontNo = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; break; - default: break; + // set the glyph + switch (pitchDifference) { + case -1: + params.fontNo = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; + break; + case -2: + params.fontNo = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; + break; + case -3: + params.fontNo = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; + break; + case -4: + params.fontNo = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; + break; + default: break; + } } - } - // If the nc is supposed to be a virga and currently is being rendered as a punctum - // change it to a virga - if (nc->GetTilt() == COMPASSDIRECTION_s && params.fontNo == SMUFL_E990_chantPunctum) { - params.fontNo = SMUFL_E996_chantPunctumVirga; - } + // If the nc is supposed to be a virga and currently is being rendered as a punctum + // change it to a virga + if (nc->GetTilt() == COMPASSDIRECTION_s && params.fontNo == SMUFL_E990_chantPunctum) { + params.fontNo = SMUFL_E996_chantPunctumVirga; + } - else if (nc->GetTilt() == COMPASSDIRECTION_n && params.fontNo == SMUFL_E990_chantPunctum) { - params.fontNo = SMUFL_E997_chantPunctumVirgaReversed; - } + else if (nc->GetTilt() == COMPASSDIRECTION_n && params.fontNo == SMUFL_E990_chantPunctum) { + params.fontNo = SMUFL_E997_chantPunctumVirgaReversed; + } - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - int noteX = nc->GetDrawingX(); - int noteY = nc->GetDrawingY(); + int noteX = nc->GetDrawingX(); + int noteY = nc->GetDrawingY(); - if (nc->HasFacs() && m_doc->IsNeumeLines()) { - params.xOffset = 0; - } - // Not sure about this if - the nc pname and oct are going to be ignored - else if (neume->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); - noteX = neume->GetDrawingX() + position * noteWidth; - } + if (nc->HasFacs() && m_doc->IsNeumeLines()) { + params.xOffset = 0; + } + // Not sure about this if - the nc pname and oct are going to be ignored + else if (neume->HasFacs() && m_doc->IsNeumeLines()) { + noteY = staff->GetDrawingY(); + noteX = neume->GetDrawingX() + position * noteWidth; + } - if (staff->HasDrawingRotation()) { - noteY -= staff->GetDrawingRotationOffsetFor(noteX); - } + if (staff->HasDrawingRotation()) { + noteY -= staff->GetDrawingRotationOffsetFor(noteX); + } - if (!nc->HasCurve()) { DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, staff->m_drawingStaffSize, false, true); } + /******************************************************************/ + // Draw the children this->DrawLayerChildren(dc, nc, layer, staff, measure); @@ -336,10 +356,30 @@ void View::DrawOriscus(DeviceContext *dc, LayerElement *element, Layer *layer, S assert(staff); assert(measure); - NcDrawingParams params[3]; + NcDrawingParams params; dc->StartGraphic(element, "", element->GetID()); + Nc *nc = dynamic_cast(element->GetParent()); + assert(nc); + + params.fontNo = SMUFL_EA2A_medRenOriscusCMN; + + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + + int noteX = nc->GetDrawingX(); + int noteY = nc->GetDrawingY(); + + if (staff->HasDrawingRotation()) { + noteY -= staff->GetDrawingRotationOffsetFor(noteX); + } + + DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, + staff->m_drawingStaffSize, false, true); + dc->EndGraphic(element, this); } @@ -350,10 +390,29 @@ void View::DrawQuilisma(DeviceContext *dc, LayerElement *element, Layer *layer, assert(staff); assert(measure); - NcDrawingParams params[3]; + NcDrawingParams params; dc->StartGraphic(element, "", element->GetID()); + Nc *nc = dynamic_cast(element->GetParent()); + + params.fontNo = SMUFL_E99B_chantQuilisma; + + const int noteHeight + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); + const int noteWidth + = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); + + int noteX = nc->GetDrawingX(); + int noteY = nc->GetDrawingY(); + + if (staff->HasDrawingRotation()) { + noteY -= staff->GetDrawingRotationOffsetFor(noteX); + } + + DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, + staff->m_drawingStaffSize, false, true); + dc->EndGraphic(element, this); } From 5516cab23fab4750ace1bad2e240ae4c9d207a88 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 1 Jul 2024 07:58:21 +0200 Subject: [PATCH 298/383] Regenerate fonts --- data/Bravura.css | 2 +- data/Leipzig.css | 2 +- data/Leipzig.xml | 9 +++++++++ data/Leipzig/E926.xml | 2 +- data/Leipzig/E927.xml | 2 +- data/Leipzig/E928.xml | 2 +- data/Leipzig/E929.xml | 2 +- data/Leipzig/EE90.xml | 2 +- data/Leipzig/EE91.xml | 2 +- data/Leipzig/EE92.xml | 2 +- data/Leipzig/EE93.xml | 2 +- data/Leipzig/EE94.xml | 2 +- include/vrv/smufl.h | 11 ++++++++++- 13 files changed, 30 insertions(+), 12 deletions(-) diff --git a/data/Bravura.css b/data/Bravura.css index f99bbbb41c6..1ddb156c98c 100644 --- a/data/Bravura.css +++ b/data/Bravura.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Bravura'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAOioAA0AAAACnQAAAOhOAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4GVgCVQBEIComlOIesMQuaXAABNgIkA5pQBCAFgwYHwl5bNhJyAW86qS23gwKbu/D7Khi7ZqB2gnSr4qe2qAq2qw29WaKC7751OrP//09LOmRooJoAtarVbfsOKrJ69e6obyN3HCOL0UGcmTRRGbmZqqqq0dF7eqHLbBhWU3NCyNXdkL+z6jC1mvTrBK3ki/QIegSdh6LzklyCJojz3e2DM2bGdQVqdccd3j73wBhCLiZft6/mmk1Mq6bp0MQyny/+5PyCDv2QRPuRMyC1EE80/btv7mG+RLRwaS74W9syMHYZlIg21jjx4jWJrqqke/YA4AVih2g5G7EVuz1N/C6ikMtFIFjuLorGCVKKhKAStFiAoFqjQPFSirTwVCg1oVSo0i8tVapQKsbw/N7tz80XdV/33tte1CLf63WzeItkbM2AkaNLBIkKE7FQEbFBUbH4ShjFR/7nfn33l/gA/jNgryqmAxdkbTH7eJPu0Fy7xyUkWqRBLEzcfvNXDYlQSYXr77V9MwFswg0oYBqh32qhpN9Zte1FISSGEik143svyvMfR/vz2nYKsPFA03QFabINKPE4+8vO9vlS/3Gwfc0fT7TAAwpcEkvwdDgWKzYk2u/7LndCsZ+fSoB2aIRBm+3+5ZKmacrc0KqJLHCXTyZG1lhTVSXLBohzvHKY3nEnD0QIYzhwRj0Odfo/mASn00mGOKkdVJM2BcCHRPsfPw8rLcv7X52mvm7S+vWivrrATgFgKh/KzXw3TTcnq1xUCfwqFdE+2CggnctAZBuvUvj//7XfefeL1TeINxYlEQlFrRTiHurUN98cSGol2v/ltKqyB6tmb5Ld2cxwGtM0QNwgSNpDcLjuctXbW+n665dkxY0GOXaI7XbTYNIsy0twOe7/fVP76sYD51TtcleXwBAQWHYSzQvBeJDT3WVJIVsDjxcoE/9Iuyyd/VN3CzJx5kWWBZbskG2hacjADUvwv/v5aClnJmXiBckpJz4kiDObddsc8mvhE/V+Uu/n2Astx5syfA/ot5ADJMgTE45X2jBN5vHnqvZ/ZCcw8sLMSmZN4USLZmDFOdPX6+u/BPL/D4Ef3SRYEgZ8936yFgIrBLFhEj74OjMr4j/J8PaH7HAhM+wMnDIrYsZw5xVvi2qvEy9Kl+redldVovVV1ZVXeueVtd2ZNU1cKS+NheGPLI/SLp2wEOK9sy3rm9IK4QEc3xvZ+XsztdLfMxgRpBwhntn12luf3QYZ1xgXnQuiy/q//3t+9/v9Z3p+94Az3RiYGUACB5BEDCA3wGpnegiwpwGRAEjpQHINpTXOEpDWUGu5e9YmsTfpZQmlc9JGWxdEG4QXpBee979savuerJ38L8lVCG0OUNERdem67J1hV/fH2owuRFARtekaxK4I/8+YvNrS3FP1n4xwpvxAGQRmMldn5fzYz6KmoB5BG8V27EhSQHcvpM/9u3nZ9v93ow24Pg8WLSAba9hQXsVPOMbUykfU7F8jafNNtgh3wDE10Z/w/WTsv/vbmSIiiEgsqTEk/12qedC1UcUBj4U3WQh9j9Bx7CkS6SFb1qr07r0KK0AyUaxBukUSEr6DbOv/wW16UT/qohWQGirRS1j+/gAoDffX55vhpA9PtY1oJa4rQHOco6fHoswNyMgAEK3YKucQvORxGcBYa63QQ8eCwXsyGfJCbeGRyGF/8G+TqhJVd7ffyfowXySOEbYFP63dQuhyUa3n54Xpflyw84XJcTEV6N0vylGHXqzHARzDOXyJdym//tOQHFRAQzSPI9zIb/Gct3wT/d1HimWqLJTn5U3R0AlJIRSqdUJvqsRp/NvWn16Way/YK1bZI5n779SWdqdjrvHtfthvuOVBnszn8svb8SUHHuzEEgE32sk2nwmw5KwHIF2hELVowgB2/OwYZ/AZriX/+kddMpOXGmg2e7mBd/B7DD4Jq2ZSKK0yT7bJcfkuNIL9py2F2qcfaBUn8TfjPz0t0VbZcbtrhyR+wELnB1zmq3yvv+zIxXjPO36aHl0AlkjDRTB2+F8/bytWf/udFe8vb4eHl9ZmAMQWf5Z+U4AXb/ZJMNpGE5kKU9e4v8X5gk/OM8y+1HnXu+HN385d383d3TdALjxXqvcPL1SCrO1JaAIMheVKrbMnXL/ups57yee9QiDLgRx8K27Lnb+rd+9K/IZ1p1UAYDPpbPomfZc9wK6wj9h33C8DANAAAKAJCTIUqNCgw4CJjYOLh09ASERMQkpGTkFJBQBwovFG2oWOnoFRDBMAwFusONau5cfKxs7BycXNI16CREmSpUiVJl0GLwQKgyOQKDTYilk4PIFIIlOoNDqFOy9nsTlcHl8g/AEbgA3BRmBjsAnedAWbgc3BFo231Gh1eiNjPF9IO5iamVtYWlnb2NrZOzg6Obu4gsAQKAyOQKLQGCwOTyACACaRKVQancFksTlcHl8gFIklUpm+XKFUqTVanYGhkbGJqZm5haWVtY2tnb2DIwwOR+wIEoXGYHF4ApHkbjvnKVQancFksTlcHl8gFIklUoG/lXokVyhVao1WpzcYTWaL1evzAxIw/JBkRdV0w9oioCfJiqrphmnZjuv5/AAIwQiK4QRJ0QzLCYSiAKgPiVQmVyhVao1WpzcYTWaL1WZ3OF1uj9fn5yFQGByBRKEx2ADqE08gkikERF/nb0W+6Qwmi83h8vgCoUgskcrkCqVKrdHq9EbGJqZm5haWVtY2tnb2Do5Ozi6uIDAECoMjkCg0BovDE4gkMoVKozOYLDaHy+MLhKIAKpZIZfpyhVKl1mh1BoZGxiamZuYWllbWQgk3ccDWbtffOzjC4AgkCo3B4vAEIolModLoDCaLzeHy+AKhSIzvP8/8yeQKpUqt0er0BqPJbLF6fX5AgigpaqD+dcOMFYAbTGaL1WZ3OF1uj9fnB0AIRlAMJ0iKZlhOIBSJJVKZXKFUqTVand5gNJktVpvd4XS5PV6fn4dAYXAEEoXGYHF4ApFEplBpdAaTxeZweXyBUCSWSGVyhTKqqKOJNrroYxTjmMTU3MLSytrG1s7ewdHJ2cUVBIZAYXAEEoXGYHF4ApFEplBpdAaTxeZweXyBUCSWSGXxUq5QqtQarc7A0MjYJL6bmVtYWlnb2NrZOzgG5SccgUShMVgcnkAkkSlUGp3BZLE5XB5fIBSJJVKZXKFUqTVand5gNJktbvTV6/MDEkRJVlRNN0wJKQVV0w3Tsh3XE8ITnx8AIRgJAADPMZwgKZphOYFQJJZIZXKFUqXWaHV6g9FktlhtdofT5fZ4fX4eAoXBEUgUGoPF4QlEEplCozOoAbDz8Rz94q8U9KkewO/WvMfATKEleCssh71w+IHzLvW++Sr4RCE6eIOc8SFRYtWl5L34Q0JXRNdA0OCC5VpSj88lJsFWFra5erge0heCgpXbocekuq65yg8PnjkF2V1uhTBGkNqSR971wcR1fNhip0OFYCBCnbh5gbs4WU1kIcHd/BwK8cRikS4MOaaqsFno8vh6nFHbMGBjMdfWjmhbyeEA7KBCielwk6KkpvrCdSiYJZA2OnRLYcKMpBxkBGqMiaFOMM0081KlfWhgAGloOlrAfv2ujd7jDFXjGTyNyTFId9gCki0qT8J19gioptlc0HXL6xnnsAdb4lwTOkewxx574mGH8x7jrHdzDTFNnlF5Lv19oKDr9XBEW51jms153XQpmGEgLhcYzBzBYPzqGefS8okg3klmQTduCYodObhn5+NYgKjMIUCl+ovdVfpNHkk+GSqju2ntb4NMaqc4MazVP+DtaCXve0jR5srw89GO1oUCHcUpROo+LEY1JUcZO7misQOsVnLXw0bbICcGqEKk4qHHShZ57RKlUEAEG5jcPNsk8RLnQDA+sv/m0r5xdjgzZqPOHsOuS8AHnYltJc2hqS5KIoRWTTIeMkMrFUXEoliN9igh3/paS/KhXKrGjCJG7C3kG5it6Rnc0ahDiRb95blUK9X5hHBSNMyHsyGAZWBgIu/ly5jNULpqH6lLpNJtkGoNEIgtRNlgS2cA+hDSgH2QaeFIqgJIA4fgJhkq+TPu3yZzb7+9RmRcpoH7zloITDZNk6RJHdOBhXPXGhem3U700R+2C4tNygYV5jC7+egXTdqb9b2yZAxRyjwHKDzGAJYgckp8DqMNKVWg8RAfEo2SlwW0ohAVUmUVTm9l0xhrASoVAECi4/21yx9//f/KWyVDnJMDaxhUVmgsWpRrViIZ+qVblHJZ5ym6wkKFeM8B2SOQdLsPATPMYmNFBsus3bRBAxKSIukp796MRd3CXb6LuOxrtAGDUooY6994pu70frZwpSBWVhYlaTypghVLTHuJIykDh7HBuCihOJJAN4wW7rj8vHyn05dVH48OTrdVNs5smBycs21WVV3fsqmd05ZV1o0MVc+b3SFDgwiUHcbyPE0FoHa4kAgirxILJsCADX4y+PKn//YQMDwUM4HcJ+OQOBkziuM0jaMghCg4vXDOIhI9YepIW6cBuortUBxd3dhdcWJjQIETUgL4vl9Lr+hqK6ubZGEIfd+BEK1FBRJcQC9Q2mIGIg/DXIgu6/amxFg4EfZ4gzFtMsu2aZ4zwS4h4szgNjZamFh/mVopNJzX6+Xcs0re6aRxX0uVJkWrTGyulG8nAFJyydYmPw1O+wD23Hnu9tpCsN7mweOzmzuHTi4yjGZgD2MruydjkONyH4tGAVOswjl7snCuXi8sEZXl6GgFsubo7Fofu/FIB128OYMzrroEBTu62Me+1YgDY08lRHfr7NzmDKymoKZhUDvbK6iGo1PySAWWeXtpOJVd2YGJ/mBpJhqwsG+Zj7G5OFurqrU+0b6pxgFer4kX51GuuXks2U3Bpv5zdheTRcr2WWNkdAVPG4Wj9kSs4Ek9k6FUSgvxsdK7tz0/v/MJxGEeiWiasUOcLxsiDD+BeORV6m30BYaHI1MdpCc1QFgNfd8OhRMPxjaMHjbGQkFC38QUhQHllKGKr+ED8Zxu7k06HBDzjca3Gk0L6+CJLUtjriY/4e0hwGWkwO2A8WpDerVE8pjzWGZZAMuC92tYX+P2hVvgFqiDOojDd/gODA+6DMb231renuIHM8QZLqUE2yXBDOAm3r22vbnn+BmH4i3tEdBqMUY070xBNFAM4K0FJE6L3HT1MT0s5jQ4hGVrYV4pWCO2AzMAAvdppYtUDRIPgSa4AQ5gEKd7g4iEHSDvSpgAvaXTI4YTnzRk58kSmWEFHcVomMLxj3pCuNdPWEyu711gh/hw5t87B4+sGda6m2kw2oC0JqEF3stHQQJwLiZ9u/1a6NeCQIj+sUsLB8Zhb0Y4CfNH0bkUABY+CaEGBtw/OFxjbIPiY+3YwU3LnQwX/0Xx9EliCC6oJuZvkeO7CA4bHsDKAW3EiLENG6SGtYVyMWFDgtgc28kNiQwxZGBSQMEqAdaIMu7i6xnhLtOyBjMPfpekk4PGnUivNudlwAiko4CqACg2roa12DMgSRhhVsSPMGgWfki33jqLQUTRQd8JEJHZrFYWW9MgFsgVQ4wGCJhFWKdGGE/NNOLQAEiZsYo3QGwQwj/YnqywZryjDNObDPl/+ZaiRCxb0tVrTCHRcrxCrBQrhkMMAcOpKOZwsUaaOt9H8C1MyMYMOIbE9GVELprW+QKID31KVBq+K7NA7Na0Ue7SkmpbQo9W1Qm6vW8iDNb3vkmkOFUlbDY957h2QBaItRVPP71guFCc9BKJpAmOF3wTvAIZjg2yjzyeYlWyUXR1SFNB0d9KpJHjlEWbzogAoeBVfosI72BNr06ykiE2gYA/C4Co03XT7IkQ5o0qVguEG2JVJAJiOulNg+xZa6JKCdYYYM2CsL8xAyKiZY0QK3ozZPyXOOG+zakkkWRbZW4xTAuGRGJpwCVLYZ/Lz6xabe1MjuTe9ps8/YK47uNi8d4d447M2l+aD6p1b0nBcAZyQhgF0S3DSqIiq5hHkMnyQAis0gDJiXuXDRyNtAk7wjVV0RkunVVHJGcUOyuEKzQjV9Imk/jrVe0O1hPlNazfIbzttdJmFbHsM6XSAsSofT7AiiP8nwcWhl2k+lnLCBEzadsxXS3ViS24pXUBtxpbD7maR8z3vZqYewJ9KhGVc6g+mKvSx8ozkJwvxugyZ8vLvUA3r5NsyuEqdLSWBFkILck+0S5VETKdYhA/T3iRIVZqNeMF9NJ0ap4aszVzQuSGSENSUBM0QqTthFUx1vY+rYlVp6EPOUGFPJ+BKyA+WSgoFXE+jqC1ECdgLYYCiAUC4hcUodAj2o5EqB7BqBPVkGbVxIJC4YuyFIILQV7HjS7SDuhJSk7ya4G6+iT8wQzUgqB1ltZ3j7Fayw9pje+qtniqiHtic+s2iIKaUP1UmMT8RhePRfNZ3EFE3vBA/HwLpEQOcZJK6ZgyEGUR29i3r2Xp3MIUHddqoBycVoukr+jbOdOJxYQOVhGFbaCykIvIChu4SxDGgUbiUZQF+12AMJ+mCZ8l0HqKCIjuUO4M2AjX+6opCFwbIvaRVTX5o/pMrMY1tkKL7XkN0Q8plUaI374+ftHomnhzQQaz9iGG8pgTIaNbomSDZ9cGUEi4KiKSmsVdwpvP4vdtglik6uK+j3Ss5elaFZfgiBPrNLfFaUYaF5YfvAHCKarWeoOdZkyVrTfngLTMLB/2xc8cpW6T/VgqO8luXkw4QLc/14Ylvv0iCsRbfPikYegwwUH5cHoZIKV4Kn9w+RPrWbjWKRS4TdaUkc2tt9qXNFC51B8hcpqn+YYGR/nr3xTyVcAZ1/G9MxP8p8sX37alajg81PBTXkTLOPIOqT+h3cgaOOk7RigmzEmkNNwAdZXKodv3LGYDHhNzVpZ6LsSa/QpRt9QDYkLyPtMX8tUFmIpVqfY3nhes5bXdVcSvU5cudodYXZmCApjahUbsfITN8kJvsNho7CRSOeJ0x8j+iyHLqqexFq8pRxi/JhqeJScNJbPN0iJvBfWhBD/vtFJW//TlVl89a3PJ9rNVySErryaGPBYjLj6FtfxrgaTx6XYpJqRaLzVi8x0Qv3E+SbgVFhdN/xjFbAOQeJr4ifHlZFDsY1vD+vPrK3O5pk+bWb5c/HrHaAzPoyJFzrT/SaG41TMnNG3BbX4LpUHRScVUlCeFQ9XGbrmLaOoQiFbJUKslp/23Qi1KuEkNmqgAM3I0Dnc+Tiel7+l42CGxaW4i+n9qqdloVc+ynkcq8XKBwDByM1CEBS4vrkBeZpMq7ZsGAAesoyCEwOytPqHFTRBfvQOy36bS2WItnF3rnDnHaGJmyUbWHLJFyRRMCZdhpfsGTHMaKyB7Z4rzPxGmrN0ntOW1bgXbndc6KxlNuGRE4pvPZI9h0cL6HDFz6YHXG3vn9DdAGlgff2cgPK2ykGAJGUqQmni8o3TZJQyLkbNYrQjEvMGV/SuXfQgS0J+CMovQwkKjW6It9l32loXVHcO4LcxXdQSOVg1uRazk8Wn6bAPkG8UTll7pi2Rv15c1uLBDv/SvCIhJw8eXD7r2WxDfcvQqG3zB37W3xsx0vCaip/qDWCnjm5meK87whk66RW9zz5VTgszexvmI/OSyVLickkFq3BxtWy/kL8VRU/9zXs7Wbiwm9ofuLT+bFwO15AT/oNSnhrTjIUNW9ZdfPM7zlEphXavdBSGzC8ToEhJT4FN4TV+cHCgY6fVUSju/tytLq18TY16K1NBUx7Auuxr5TTjrrbZ2ZtBejApVpVdX+gwIPmaEKqZzZRpwy1bkHqthtclpiO1TIshBb92LUU/0dYi7RE0aU7SXsTpECgV0MTvaUZUpKIzTYcZbzgPugnjRyCaKDh3VawUxd79Vgrp1g0fb88yCR4SZZoUNQEQp0tPLWJ8LGya4izW004uPhw9afPe6rM0H93a+9ln3jA9kFI36F/7fCStuJ83YqDCTdtxKGCTyY1INSEFsjhTIQltZpemgnqyAKw3neiJyGewFsek3qobDDMGb+vHvYM5dlED3jNpnxfk81rUG1vkiViA1/d5EEPU2Qra4veEqy3Kw5Tfk+2FBIaV1XLdNVh6BZGtrDYb+H2xAvPKsmskLRPj4QiNg9wGdyM0tKim+Xl3StfSAWKGPb1/9Zk+I0kuym4dFCXA1iX2d/Nb+KgIFBOd4rPSRhh4QZLVK0EMYC7MibCcCgrhQzNpLl8DTljr6WWLDcEplecrQeAYbJMNpxc3uwSF+yRfs1yO/R3uqLohjI9KkdpSOyvZKcx3MOzGvDBKGIeTJqNCiVZIPmrcQV+y2jCrj7B6ynkHCgeiJJtbHWM3dDLbsBRg/k8lPMD9C4YTlSdGV3K2v98RcQsy1Cbn34TauX1JrFBri4mJEbSBWShNPlla6CdKZlV0W329b5XtzMi3ks8YryupNj+EE1QkLi9zhVBM6FoFcuZkdJlbrD2Itz4jaOrYdRJvFOt8yOOtfSj0OoBx7oP3AJ9/eGejDFo4KvUIxiT0bbucbmig1F9AgbM4L4KnRCV60ZMPnEBMFutFDdB3AhicZ55/2YVI25ghzrL12uAdAUQeZ+DYj42618Ssf0Hvw8Ezl7XCostrq3FUBn53yRg2HriaP7XCyyejYqeO+WndBQsxE9Cq4GNijjosIIj/Xh3Zmoe9qkZAB+fWBxvOt44XxrOvrcAocGDvGS3dhQbV/YIMEFfAxpfB5vki6zH89bI+Ky+dj+Jb5kfzu5KdHJO/oH1gju5FPKniyHSoFYSEcItLgHQkrWDqFnXyiYoSrXG6uap+tNBZ2g2zY0qOVGtlsqkVDEfERIhuWk4qMdwJVF2yhOOF7yRJdiQ1VORkHvYAkU2OAsBNmMOjnQqv6qen2Yx96KQFke2089U/NPE9+v4NVdnVmQI6LwmlZ5c6xW4skqL8/1V3Lxf3ePh8fZfyxmwfgcLvMcfpDFrtjiwN8zhGhIzsTummYN3K4CihGGaRt5dFzxFdGvCe2X6YGXxPZN9CAAgqTT20rODh2/5TvyPylWPLlPBD7fbtzN7+4FLCys9J6vhjE377QqrhPAJVERgEZ/3aj/B2aDN+pjC5/Z9nn9A4nlXdCj+s1gBi5bknJT4rkaz+Sv8WSCb2OLKWUedOarPb01wjcygcqVKJ6hV9dUI0yVuPVfBkY6Ls03SVAoOnYFvoKplq0AHJi3rpwS57uOAEQb608KG3S/tJweLh1R0jrTfSARKiFs8iJK5XMei3njYm+lqpJuwFNNHTbF2a9aHEp43ZU+d42vU60ah9d1ikUGwbnEP1fBm2p6rAVYWaduaSf4cNNfVcn2LCUKtVdak9dnw0WmyRQkKa46r8TdyZiwRr5DngdGdMo8gIP8ZgrkwixzGkghfrr6Rv6doqc0YYMeymSgL0hRuptk/c6YzfvX/9WOgyJ2Xlg93mFiqNbYaFJ5qpEZsA4G/3M9n+kerJsdNsB9iA6bHIvirUraWf4LNA0FndlAy2iicikQ6wi2fSMb1MFMdi8oVBDw64djrrBeaqYU7gyJc2ZF/sUBcqr9PqB4WaxavxJTWp3q1Vn1BveVhRJtXufMsmCHo5G3D98H0Ap1Uk/iXTcUxIUzF+VKZW2l6/rm8dB+TN42x+PeiOKbkoeYFWQdkpy3sLpAnTwPUlxlLFx7Xfb+E1wCNzlPPHpcxKr/X8zqRQmJb4/eQq8dxdPS3Qtcl6SIynx8VBmnZl90RnVLoP8l6AKsG/z2AY5Uc4pSe+zPxuMGaM7DDEn8e1TnV5ufFPgKoWqL3WlAse9yW+bK8GBUvb/mIj6kuibn9cXXyJgwjB2jG7ggrDZwZlsL8We6/ilcGdx7y0JsUDiIidY9/qdTC+vzfLy1CpFp3BH8IRBb259P7hQv3tXlMqgySOj+mlWM6S5L+L04gdgLILinmjAYzjvtBheglZxRhGaqgLycdiZOmB6xdrlT9b5Po17vtRpfmtDSpDQM2Jwg2NOlacUqciw8yu7n1ceTMLBplPd0r2JEBuUyL5i60JkLebpUrmc+H4Ur6na36wujkmZ6JZaXZUVL637lI1Vb0jPyHiUnTkFJP9MNWYLPuZris4K4VJE765NdXUCq9e8pmC4NznXXMEBiqGA6rAtHUzTPhbGb2Luw2uxk4YEG8WZGZRzpJcWEiWcmnIIVWEpi9ZwODQWuwf7t7oS6Ycc/oLa1Pmg9FEVYpOSdGa1E0IRRqfhis7i0lbKdfYUQrlwZ0oQS/3MpO2knoRL9mcHqa0zX6i9wRfmRufUB45aaCwefOz3tLHJaGH7RaqywksoqqvzyiUrARkutpCabM23rr/yMPjAClntJhg6BEcw2Yr55im6YrCVm4HUyryqEJglb4DGlx0HxpV91/LsqT9XAOkBVaXhh8kSaozOzZl/ClypeTri7C0nSzKEToa3rUmftMLIx4LDBEUPb5L8o+TPohCFQuSxJf+Cir2wvdioRqkmFqxdNSzP0/ApxMDPNUdW05LVsB6FOY06sfHfEPcmmTCx3ntsx1BS1wVmrKl3qbTfgR34ivLWv/8uxLohADrve6wjCHicSgGE9NHowua0FjhGMoEW6XQDx76f/C+KuzyVAFEwlbQtlq+frMBGbw2nfFxzBcmafySxGvw54TWrK1PdVqj7WXpUE8mbrbw3eN79nF+fDXj0UDPe5WGs0LW50fHtZQ5ccOzx0vOY5/84zvoSKsAOx42d44ILwr6Mp4wBKE7hbovd9Po4ETfy9TZ1PeMvBiQ/YWFeSA5yJzlYB2NbBWJdE2E+xovnXAyY5B0GXYf2KGG2Cn5Gpd4gLAGsp/7hZkeGPQeLmbK3YU5wmLMLwipPX1lPzD+zfzYI1P5SlAYwRG//Xc3HtfqWIsy/1sOd/IwO1qnYXtg2bs34TZXZcZVLIf1lEPzChV1xbaOFW87OPGCJQsrSuu9KP0UpvgeP9WZurZ6XkiyyzTIkK7LaqbYp+JUyOzmS4k2oJ1V2pkH0JJM5yuKVDWyliRGf9Cb4WBGJhHbFbtZ/z08bkBJ8nJymZIAb8/EvoohkkjFgSwrzyN0rNjdSD0Ay38Z6jxOD1GEF4XzwnwxLfMod4cpeBmEpfYz+OOKz34asZD9mWqHCnh2so0vSmNf7XDgp09YBf8MWihvCOCliUtJK2hyPymcEk3PM5nM+cMBAnYJ0kZ7U2YlyYE4a3jv7VwXHioIA1hO6PG4IHyDJNXu/EuydpBCfPE3VVkZ9kJSdCgEqJNtbCMspl2nQilODA2qIgf6TUJ63YMVIzlMlEwjG2j3km50MTH3riFehqLisEf/Qw5rA9R41MWMwh/q0kUEMA8bBWP2MoZKfZTxLBJ6IDWtGF1SyyU+Z/5SIVqRu0kJV/41vMEn/vCu0GOpHwEIZ43T8YFJ3WO3QupFEApbs2WObyyT6tU6BKRODidRWgamejfUAL321/hnJOvhE37ktOLqLfVHyG0TvzOvqoRJGmboRa5XC5vUm+rhIYoWZF82aQzfk/jWhiGs0VC1Yv+STs9fcFTN9w7cvIwlj4CZA7ME+p632gOHLk4VMYAa61L1/TIQBskEJ+xc9vYY6Utmt6FNOmlxVZXYgXHyU8v1m0g5yTVAQ0WEJO4a9101GEHn/sVRlR671jL2y3fx2ssc9bxizjQshg5mstWV9UTkohFhtV6nHpgbp7NUMnZoSf8ttO+eWTLLYUGYb4zHWwjeNrWVLj0tON+4iIUS2OAIBhJ6hrYWHtRE9hgnmkVjs8lLOCMpnoD1lVNe0ZuKctlmRa5KJRMatASQ2iPgjfNG8MlAtyKEBYsA8NYpN45Y3oRI1hGQAPBF1OJ2s6szoBukYbE6Sik2s+RfAhUh4qgxQ3gE38UW+fzDM4/DTGlQIKU7X214OHnTCyW9sFLwApUWj4ii0soNPbPY0HmslQqcsPdXSb34zsV2tiD9TqGjTE604yquhli/AIsLKg9LSds3hBlaxIEGYbgarJOxyU4nXE78UJ9QHCWMhVmW6J4qg1wNTGKaSNrrbKiLpLH3yls8O6FMB20xPEvc+vKZyr+qMIeCw9P2S8QsUkvlBNHRXvJFqx2valUdslvl08kln3xRxcuAIK3NugjzswDufm1phKlVMp4Vm/wedlwJdkFOk8JnBUA2qM6Q8qOvJSRDqAqTkuMKss8wT+Ge6I5i1FQLU9xyPjmS1dddeIPCN7emt4kJs2BYFtG5IMm4BzCHBc/kUCjZlmsxfhWJdhJXELxNsrsOWoTQqDzLkxfQvAFJ5gLIs/iv2wgGSjMmAUQQTkBeqNot8sdV9giNXHT+oQUq+54hyYEHaHbNo2MC/SYDqU1WikFRHByZDt05HK33mvue+VIsoFgiUPLqNDIW3wJpfcUR5v6te2YDfW7MU9u+WRLZj32LKpuKIHQBtT14j/1PoM+O2mxrgzF4pr8rpAUI32JAIhdrtdL+niNjdAwzAFTASK0L6jyqSDSNHqNWXq09Bbiz16XDJUmDbkhiYzCRWEvI7OYiQMr+5p/cJDty89mRJZOTODEf7cgyidp+ix08quA81nNbWpwr4qjr11beK+Eb3MMUmO180SUrl/WWfZ1OvVslJMYzcAfg9nWDBPoypqNZcLOVEyi8oiEuWkYcejY+rO1Ba8/aYabRt/OIPyC7mxKgt4ztWrCneRK1UIrxrN9G6h+FjsIJ6F1SzIWZNndfWwRYxr1bnnmTzPyixvKzVxGkXlSQCsC0jQlNT9zyamuNsuYxBmHOZ4BsThf8t2N1P/9000bl6MlH4IO7G/6MHzmnyLC36Ng2HTvvs2siY6qAr3IYvwL9pcjC37UmN5o3iN68PcwNeRdApIz/SBs0XfK9Os3SLi+p5nWS9726HHUj9ge2DdKp3DicdLfXgpDGgpB3oeoRqCFZ71JiflzhwQFszm3VJc0ROw0BmfYfn4OYWoiZmNfwHKuRhU3U//MnkaVae/p9kRriq4Kb4nCyZWkMDCwc8Opxyc9uk89PYw0ipKc7FEZJc+W8bJ1FULrAKurBq3yO4BLTsEBPzLhE7a3QYp5YNny8cxF5kD0MoGd7ik1rwELIkvIR8LdmeIHaLhrESvUofrzGYFh5RwKM4uoneFyZAT43dT066W8+QzJRlEEjDLAGeHRdTLYcDSbKJgbeZ+L32HGAdORe55O9tzH8neXsCeiV5A+8KImDKHTnETy9FwSynTKsGiZI7mspvLEc4Gs5cj50X9tj9Zsj7kDR4r5XCfUhWbIBw2spV2OoMmfYKlsI6A2MCZ+t3772CqyyFfhR+c3M0RquXTbGuY1T5BaRMVmoFtY7l8vi9iLUzmsQdVdeRUwTHanS1Qvx/1ylYd0xReiS34o12sFwp9zGwLmVKZJPFOGrrjGPZriCPgKMOD7aVMIIwmIrnjrTqvkxkNmVU5Kd3GASL8duiT0zeyLBcC7IZXuFCA7JRl0xeU1L7zRPnyRadiRidXgplmAozoXAHRybqblPL1UB4rjR4gN8jgQjIJaRyywuTNZBtYvWwimGvF2OlgNSkB4FsDT7DgvQ6bC5Ld9Y429DOtuSO2kkWDnIPA+IpamotIQDJm9yiRIVyrRj+OqYg4EO/VYstfBFOK25c/M0kx/d7A34ZayMimM8j5lcEMTdhRqf8EPNrpmYqpfY7jZ7m9BuAH9VngKSNMmDIRv4HGGsp6xQ9bb4IHLix43MtbMb3BxRECXd8bn9q8k9RUHlxaKkHOEX2pqnzD/BiXFloooDStZVszU/aRCMMuaTeQX7aP9Ci6KyoahOn1rlA52mG3IU9wkvbpnLJtm8qRorKIHvPpamGMCvT0uYTNUlWWEkq+x6F/CHqfwj7H9/88XeSg/heo1rlzj/I6LpzRh8Hj+nKpWHJaUvlDtdrRGd5SdB+/R3rlrGdRnqHXv6hoDkQg2/rc76xjmpP2FwOPuT0EzP/E5/hoF4+x/oAPYebDT8ozioqskhOOrg4/8HoG7G8rN4jPA6ZV4QYQHSA0mFUlXwlJPGfSMIPJBwnuGtx8r+MaYuvjDo35IIhBteL3MreNN00t5N/Ey9l/Y7V70Iimb2sg8MejTxXRTTaMB71rCxbkxb/DKuQOqwwPtqWe3n0berEAH3cRRBYz4+9PRYQw4zW3fCMiRPrjol4zz7ycJg8MVeWKDkoYsjleeYsXcA3w6fdTSh5oSiyNA3vXBPPbgGTLnAIGJpLKShACPt9mR0rQg8N8AJRlpzyVSd8YICTylkLEAgxyjJE5HXNWTOo4syYzC4pnK5mvSutIW9cdxXMwNqSEwgGhnBcd+F5A2Ou/aoPQeA5viGdgOZ3foByGN0iWWqwlvSU3+ksRwhzEWoPbRNCITnSocL6TmY6Tlcn5xWnNu3xmX8SFClvwdGKV5w4T6FBP5BkmfczPr1ATDbGlf1sYTmXIthVgkPqglSA3QXiQc4c33xNuvFPc+HsypUNJkdK5dWLtn/SszsDKorCopH0iGbcAvzAvFeAYqePVsi30Z8cVy6+KuzikGTCvxpaRYEHJ3GM8FT7SLrhUr3s58CKFBMDprTgaaOWn2dtvkJM4/J3nfD9jFVKdnaEDsa5hl0kLAcle55nqpqhf+lvldiBnnPc9fdHRpM1XIp8IVELnJwd3BVmMqXBFMXh6O/Q3qXFVyshjnes9oPwHIyVEN0fNhG2sB5/aJTDBQO+AKIYYJs94J5icyPxsZnVeQvFigLXatd+jUjPOlY8fXObSTJZZfMumvJob8Xet7f96cuoQkfjGYK1tyNNapYlXjMkW1qWpf16+WWcKyFDI1mlg5zyN/MRlguzWF9IEefVErX7Mj8r96oicKjpIrjT2s0Ao975Dze9tyJSUELWTxbEEQ6s9nq/Ith7CX2/hVXd/x50FBiWK5QQmEtfmJLNYR9X25UU5No8X4biVE+xxGazt/BVYAB3CszyTmyGS4U0D6fU82K5WxnjhM1+f8BfvXLqgu8/kO6Mxc0cHQzdPILzRf6FxI2h/JRb9mBr0OhMXcTHeVpKfqwALbS3UiyCeA46fOSQeZevklg7NvJZb7Cj8Wo6MUTOzrLhgMOvhZBlxWwZ68bMPcSK3dW1rMduBoE7n98AlOFzjEfTyutLmEufvuIK34Se+9YqZqugBtsKbByBezMIC9OYXLLOdKx/ByDGzUMa4UQGbq4O95guHMQ5Zc0yDbNFuTtuV4JBUd+Ird7SPQqrHyfskoKEmezDzhYlYFv0u0lSXW23V4ldGWWbbZa32LRIpbtQSCVlWcsocwI+L9nYjeR5qytJh7um/Q/wGaQ44m6CL+tdKRaf11Ktok1aHgNd4uyz+Ola9SyjVyoXcK7E234PWAIvh12obhIxe7l4cslpWCGz8a31EixXXNU6l4XnFEuVT/baMGiIBQw48fwEul7/+onXQB5PmOUbor3knC63AoWakcmrAnbG7BERLedP7EWOWGXXBqB7wYisw76Z0HHEDMyNSDv5jQbV4Xvj8tHkmsAP0C0Dg1DiNi8ECIsc9TW9ILJvjJO5Qp9HSHdPquyeph7jsZ14BRblxL+Os8usq5jVnhhWNZJAsoUKnCyKGbcIUQonAwBx3Ear2kFisEZw2TAu8KQjvKeiZom9b2dqwdjP6Qw1vrzsMxGeImx3gZdyaJxu2e2llbNZ31KQeT7CXHfq8XPxZg8nrL2B9UJL080ozwyk6mJ+J6/GOZm7P/5oYX3E7qZz6KwlfUlj1oGLiBa+mV3rPHTI7ft3OouQ4R1zOTvLkLfugzM8N905RZg7sXeSU6XFidu2I7Hputdn0DgGMToITYtfMg4GkQcWT3zakth289Wby+Tb+Aib2FHsoTWJY3dK5vDRzYKfy/MGXT/guP2eM1BSy869oSR2zTHNicXsBfk3cLKn+9sufXVJfeKjHtFKWAt02wMiwJl6IKlHYbHfjZMXVd31h0wOULj7fIUchJpeK/WIR+l35mjmnoKucQ3rCfVHVu2ap2+9rTNaM7gEhxYLGGBil5fJbBZEV2/IkEjQZwZpls0vrV+jUeF06gyXjxxL6qJTmmNxiJgMNykMNAnLKH5WCg1zVEXHGncHWjlggAyigKxhhVuXOnc312pNLQyOF24PCBM8pNWkp2pIqXCTLSkwKIdKa64V/qCaMyrm9id2LBmPXvGuzAjZbsfV7gF1vqY1FZoslzGyVjApmB1k+Z6tluQIWFPaDjmQcmI+RvWEbW3JV2821eSIfTnWXum/bEYUdDRdNYSJHG8WRXjGNXW0SX/6qQV7GcLtPcsY7KszTGW77Ym3GRFNbCud6MJkw1j0/4mprGRPo3z8P5awOPqR3BP9e+T5AlJMOrIcgHv4p3JjXC/+N7ET203ux2VU0dm8fLzFl6vS8vT3EthV6edL2GJkDG742/ii+qvI/OFxwVXEY1W4PEIbDle5ASZbgVI8JuBFyp4qgoHb+RRUdPhZjaDyLnxqBKYywNqzgvwTEfN7q714Chgi7yPLxHUWOlCmPR3Rtu6ExZuyYR70W3+X3OTHvpINxvAHIZd9h8vSkQyNCL42RVMfxfSVv/QTzivYHSJdaaDhNh/HA8fRKlOaiKe8tQdoO/0jV2eZaqY8IPTbQzWKoOMVWVNcKcsB13wcRPJW+cjfSAJBPW25ua80rJImdg4dnA7I0qAvwMRt+LYtELGI0MqT9GNxmmhGxsjT62sorAbWup9KXB+PRCiDlmFtfpmL/KGb1RxbaOlaE+Siu1ZthOFW29JS6g089TH2p/sVbYGjO35JnI+j0ggHcD/1y+HLOX8EFkecW+fxFaPktEsmQqEtGEnuYBmr9t8X8ovkbMikgnBerHvxI4PG25scTA0lvHcvw9fw+P79rL3e8JPGxnX154zgbTCGdq/Ms/LtfhaQMi0UjozHM5sTR0MSmV2rl060mCFy2rS5viqrKtx/Tmhkl7hP6KdTZZVcswjbyAb0jHSMd5SvZo8SbjWnW8ViWbifWpc4o3X66Y3fVMn+IQM0r/t3BrEGivlnULgbVAYEo2JLW3qJTf8Y9Ik5IAUhlY5JC5cqg0pDCxa55gQJcZ/R4UyL7je0t54Yd7qXc/KmDTJ0vYXJCjOaWQL4XHNpOEoUaDgFqQ4RxhHRHl9tAAGrvvgCclEKaLYhyM/ytV8FQmXRucs6rGaNg6kRWTxxxkrfwJaJK4DJ+dFcCpZ1dRNIyhQ0A96omueEXQTsNVhgpGNfffIFw5/zECLzyD0gfiKnkMJFYRpRgjOp+0j7RRlfmOGalaoVBB6Oqp6jpvCcj4gyQ3Ly5HSbZKrqlmu6V789bE7ibLXjYBjdgIiII4Cpq/moFbJ3byiDpufOlOjwAGT5Fomd158QY725xL4DENjWtyjTPuHjBjlpwvoQ8oyT64bbq0SoU0yFpToAf6KkDIHv6T5WiRAVq5v6Z1C9oCdwzkzKvIeNWV9dHjvwsgrGGMvIkTGmVfpJZQQtntXBz7JinbIoMuk+kppibfBQuK145eDUxWIuqZsnQXfIcAWu3aKhThN6cKbbN8FXMvBvXfp+esSRefguFdNdDHB58RlXysM5N2s8DUJSPGX1Z7uO5bm9E8KpxwJdLobdHvGQZwmB1ksjSwDmmrMM8VE+nSYo6ErHdd3kXZSoI8GlHMAdSTo5fmddxy2/jazYB/Yr5r1duWqEZEzophIBUwrJ1Pmn12cmkxfvpWzZFMaSBt66YfJwQXmJrouMNtUZ8nrn9YSgBdd9okYJ8tOpG8xmlLla+nC+MdN8d+P92qKS1HlNLEVy/UP7v3o+ZDm/EMqsjU5gM1ch9W/h1Oi6mp6JX4yh8OJABqdvZIw6ao3Xa+cMKz3L/RksteVqcIChyhNcOMqsKl4c65xpvYQkgQUvPJa0q8+RQeq28Er2ypadXqTXa4CAq+QCySCjBZKgsdP5lHnWWUAJhMjJ760TeHUUUC4KfDm7F0J2jyLTl4+SjkxWh0a+M4dSCX69s9EfeW16feLbrdjZc03fTpqygY/pBSNhKVr/dqhWRo/+QvGMWd+U+ZGpZlZDFL/yuhCPiqkMLIVs42unDCFsHYQx7dRb11E3ZD7zoSQp3I0VmkhjAf3I7L9U1aNlV8buT+X89kZxANVSJ9VLbhC6iuKhSuOzr7w/3eXLtTIp0nQQ/+WNuqnE5EMlXq9m7+Yu3JCjtBj2gxqqZPYoIsxEZEW7BowWvPONCtYHGWeFUp8JKpDmjbPGgm1PZ2bzxWJaQAvn77AE4QFLBiso2VQm7ItJt9ayjcAg+xPT82EJ74PZE4QjU8Aps7xpME+eTSAnaQDVgrmmAUBr6nGKZqKHpCQMZeDDjNttDDT5TNZavJGUTT00xULzfS5XkEHLk2goJf4Onz6KUFQZ3PVC1tUycAAVCPBaiDaaSt/5N3L+zA7rcyWGUEb25Uw417ePSGaGc07TW+YAJXCs4eoJ5kF2C8bV3YidHAiDB6tcZzSwcj4cfFeURr6RF56/u4KcNdb34521b3xh4XhqeNC7uf52fSG+t+ZDETEMgAm6+tzWv8QP0ZVqng7l72rKn+NpEHdsdjVMnP7pQFjAUBxPB0eMW85uMXswmLnVcQ1h1w06gWkkWA/QTyJoaDhD5IXCaj+u6dhgV8Wy5va3aIXLW6Z6k+ST4591L+HXlCU8Qqx4/PwwXt32PCUvCYVX0EwASr6b5ybhhHKMGj+w6STl/zIOIW/h5jB1H4cU0XCux1kfU56Ds+FmYjyiOslPvSCNnuvkOnh8ArM1rpZEHW4eE2djbUmJkm32mSNU9lRzYbJfuTyYZPhPJlRUF/ejlm8yaZRMJrhbPSMgxGxjgDwokcDW5kVTlZq40CbPUoiOKmE8xySlLTqoyoDTxPYx130lcvvKFHyJAXpzo0IX5T6xPiJeF/+3zKiN+mvr06BrvvmKYoZcw1wyA33zXU3syFXPyJqQj0lHASkjhvjKM0XZOH6+WUrn1bPWAKo0di3xVzYYKcb8KSuRH34xF/JiA9hQtAZxxRaS7Na5o08d5Tyx2+uoL8VdXqeoCO7x3p7v2j1aMqSYrBtFTLrPRCkQz0+7AFrtki9QrO47H8Kx7vwJPvNxoyC1vuRYL1NwB6XBeSfi7MDFQeZFCqLK0IhBBmUyn/hPV5z9opKGkTEGfQysn5qXyrwsmCujOMXxctzY3SRnKARHmHAqC8PEdcCFDxM8WZYRUhP05aopdOKpJUsV1ns3/GcuGrmado0beURQJbCs1A7nsJag/S2E7wRWBGwct5lAdZk3doOcon69/AiBTd5t9IOuR++XKEM61EDELVHbvUcLIN4bzexJyQzDPEqZ+QO9eKwOyR/m7IYgaQb0XLZjb3LnST3GkGS4UM8ONFa/ZAZqwSghzOOh4KR63aqAQmh4jA3u/2g3YDTLZQRgH4uAURowOAAGyh5lvrnLJAaX5EE1pv6+FxRw44dMiRSY6W+gOUGM0zLDuo5tuyNAH+ly4bqcF3xQOcD43+RyJFJIBt1f4lMoIDIsrFudCWfH4vR+jOtJ+nwwN5mmyaLneV0B01+JLJhcBpHTx8AiO7ZeUxdLyMK5CBq1e2vtvGjx7h3cx4vAH/CyGVwMma7Lgg7X65tTCS68HnhrzitFNYRh5ROEDUsR/6sJCqdBdZVeSYsPUWqyb1BW2pXx7Sf2CeST4PySemQhOFPSJBMHV4g5o9owe7qF8Z5JeFFjoNy7A2WIasLtubpl6YG46iRnLZwKhMMrETP3SArZVH2o9QjqqpfoDFE6LXygvezFToysPezNQR4LLc/BZGO44tELUsL+JuDPjsMXCNxTJDU54zC5t/GDMLJEsZU9MAMhM+Gl+kl5cT20lid1hLsWQK8FieSfpxkvF3wwxdV8pCe82nRHuNyeISx5RfnqLipzAwMGOJEJFJXJbwfhYVn0UYtBp6FqR0E+ndirac1ERfxJ70gl1UulPxQQlKXaPRbS2VxUsmeQq27XaF/2QvX9xwZz3ITzHf0B9V6erYoI+98DEZXwMckFuyrXrYK0/h3dcMfbxF6AoTl/w+Hs+MWifqJ4lzCWeNN0EouxngPw+hjtRZMtXDKpv66v5po/mZNEWCP+QhKYHFGbnXzsecwC7b6Erxjl3erLkcnK5p7KG0RgIyT7DIkqwQsvSIOyQGMAlt4E41gevXzXq7Zg7dYmYStTffRqcrfI+deeuwV3T1epO0Aj1pWmDtn1ExM6rzVM1TUYzqByLjL10g+anGSbtXqp5NaHrHc/s1s1Ec9EAWdPG+Glgc8/qxwxcB+KSmv9rhz5IlqUgNSY3w8nc+Wk8+t65tRA2k7BEGTaDieephZbsmmdj+McG7RZ6KBJEHAdleCAHIl/N7qEfVm12bW0ogq+IBfr8em9JA0xQWERwwiwjqJ5jGmKKoGhIvvJ1tCB9oppXymoi+Sz88AxDGTWxAD7KB4geVrWd/HeGnO79b4Wux+FZwGtxAAWl32+HRfeWsdeUq015J17MWKfWNYMl1o4p6NX+kxUfZ9NTlscSRcKk4a+3ZUa/SAvEOSXPewegbzshghoq0ZNDh1i2ZEGM7JKGY4bmbpbGUgzpewu9Cn4zJg+J6lqFJan5jGd2OulcvBE6AnSTew9XVv1txZZmOXZFbpp1BiBnkSe7EChuQXVQGFMuAaBmdzSzDEzSdCCbQJLY2DS9aHLHQatsxdNPEGNVVaQg30upt9ogBbOhkXSJDnDLNQxifpHaAYYwZqFN7MLBHayeNSjnqh9/cvqKrH9I1s1sAoyXfJ8NsL+/rEnj47fn1LWt+HiCeVBhcnXyLe6SyqwqRyn8VmvVeRNC04D08gcTkFL90/7QrGQtT7Ip6l7jc3QWZPO+3kEWZgNQYnSrHojxSXjtOR4aRRaOj4/OqmEEFvm5qHZUzWqSxm2TruG+bmJGoiPdWItnpRVy2HTCCjrHCUKjOkbp4upokAE7ad+TSHTbSaLy2/O+UQABcZ3LoyIraxBPBGpNtnA+PA4RJJAQxg2RmhJO6yJg3DoQWnVXsOuiLCaXq6hnaLfFwqZJpaSglB+oxTOYPk+OtyXDrOza/a9lScomqMU3cRsoI+If9cfTeEzK9oj2lV4TJ256JrUZBSmNDr2DquXzw7SFHHwzNta2kkrrePbPc6cv+IUbOw+vT3bt08V82oqWb5h56s1G2Ew23D7EdLRFKhu7SOQMtkeKky0xzOSIqBhdm1Vic1BeiPouf7D8sScInMEG87HNPxYuNllDuYfO7OCLGX3RtnE1vD6ZB70bKo6KxLOgEyRnMSesTGU7771i2RtJMxi2SyX83J5YiOGZGtXMZr2/zzqJG1fCfCSraGHdBq+ahUR6GyL2Au9a2bs6Ky0RT/gi9oCwfK5UMv8fB7r8bLrjminAgwyMr2hhoUFGBQ6FCaS56JEt+kh7IiOpbA+X0vao9M95lOFBpt8KfN5u1hE2nJgoVlsCQ7Df35e0aP5NDgpQwxz9PRuvUVrKO0nMlscdg6paGWxmVkiIA1w1jE9hxASyVIg7+8M2HqnVHyl9hTieXNzKTbTmj9nfW8suarndHvlW4h1UV/0+1TpMCCn7SJJXQYqx7eflTmeZYbR/QW4g0JxzoaNvU+sXvqrvi14PoNeglgnfClTdk2EMBoHdVbasD5qw+2U34zqDZhlhrkdVmzCV5/nol8NUoFroh5hMLQsZJHRIZEyIGJK+Txaa68Vpml3VVPr1szUTnFHUDM/M9xbscESJGZ6JkyFXZFgQDvnDa69ZYm4CHy62MJiF6SOTJ/b1g3YCdrUxKSh2rVBnkpFWuNHozmFKlu7dQGavH0/4feY4DLVJNroWmyidu6cs9DyUZPLAfqpRwOvWe2Lp98lOmkp+cblFn+BjrYI22VbU58/SPhW7g920YDWhuGiKnkZmLxdIx/KRDP1g56qIuWD+DbnDvDUkkQhrIX3lYXv9KvB9xAHUFaatp/3EWHL8FjKchVk/IphbN3qqnQ8HChtv9GXr8cMialWEJmzzbMOc+IoLeCAS6j2ug1AUVyto9Wyd0FW3n7WAHy9HYlqx8So/pcYCheAGopo0W3CD1PvcikWi8ZPUdxPZXoN3XiQPPcCL4VWinct64QMf+Ildi7JyB0I3zFB0z1PiBa4/uILwsCCYSA1BjTJm60CIK/FYY3rab7583Yf2xuMfGbjysQ52MB6EOYD2a0+ZPlmDiIcmEL/elScFBNpAWQmfM6BtDFkOwLrhQKkU1SEJnYowaF1eynjtAMdohZxabyNpsZoW/SNrGy7PpMgDQ/GT/23UCWANnSdVC1Hk1d3I/VdZzrEhTA72BxZMyKXVw2eCyLfE1Qq0SU5ZpoLLRunmt8jeTJvB01wvf110p5fRM2MniRl5O4QHquxquFZ3zrTJLC6L6/WTzBenZ+JUiRB7F8yj2ni45gKbUMQ1trhNRRp0bf7767xpKBVKpo7Hgej5P1tGq+k7oHY+/bbeJVWv1apSKfFkUIMzrQZrVAiVsozhDOuyLtX0tbAcF4ZEdmFtWVoX15aNdunDTKraxn++opzCDjZ1pTOyIAvHx6veZ9LrZaDHjwDBy3aTOw8ylTYk9RqwdmLhqFM/u32DvqbgDXU4LzKNevwuZUIuDZX9l5wHEi4ISylL9SUXLKZYcsJahbYcH8DvJgobIdAxMdTx+8DrRZkbTaFp8AvGpx+KcMkjRkdOoPm4zZhTUIg4/d7TU5nMHUyGSquPKdTO75Hihza77dIsXCOyXHiqHzjLFoQBcRzXha5YznnFLKmC4A+Ku7wqkb3ODrBsgzE03ztr7n2EUb+VfWh54UYI2K79qwh/eixAEU6o5bsBYEOFrgeWp7Xx37i7L330KfqkqXMByymhueINi0ez03rC4xr6ibX+SCak415Q7CUeMXClIblYObbmoOaJlOt+WJrXWMREPNCw2Vnj9rYzKBhmuyfmsv92UyLRNcFvyYqzzgkRsc1+3zVDEb1ntEdsGd5cNolHxT/9tJsLoD7FetadBcx2RBmy/ws8PaZ3SaerjQn/TtNk5B4EZrCFnPOUd9hzl0+soNEDRJj5a0yCNKDaSRUlzJtc5Sfku1MsuWf7Gc+40Jns9Q6BGnCCbQ2ngIifS0rg1QqdYxCOC9YU+OZYh6Tw0nCqF6nErmx/sfLX6QuW+xCpEay7wFr5YgUi2aoRdqml207dV7X6+VMmtnQYIkEjLAU2ZEJ+8zKXJBbSTJHt+tem1QZRRHR3a9H7YYyALmmyjAsk4SBFRk+xcg2gYoUMSE39wcCkh0oDWRxXZ1yfzt89XUuUhUGb5KmSZg8cxcd9JCe+3GVFN65NwQSnjB1dp2IBU1z2sPfGBzYxjrucYATLmc9bM7fTWpVaz7mNUhEC3UrPrCBa1pjdlt4nYx1BinMXTc+QP8YnQrSQuoQL/GnQXLWf6hcmrhvcT+hiC6z5TcL696Qt6scSAJtGay8MR/jViT0up0mXBUt9pxvXyLTmRu2H7/RluIiAOVolzYBVThgfYIAA7+lhx3XY+1kynR3WtbqBD+qbkqxu2TZgZBtGM59kOsi4ie7XYqHDoPh0ZZj2w0e+nA19hCNaXSo6+mSSu+LdTxb7/wMdbUiIR04x8ZwmrpKuzNnpBl8pf/iucypQxBJXtQi3lrXeZ8vcr16DLjnXje0fKqYxe4TmKBgbgqEQ90HyZDOwflfA5Vdupx5gnq7TsASxU8ld/bgquouPbyBz8Ur+4rjwAAyDGeLKVgqe9p2AGmCFPA/RHpue2V0Ic4wpmMaPKfrbwzIY4AnEZ7FMeQPbngHK7l4NX093U8QkJ3t6ZV/UEZ6MoXx9cNMpECdcJTiZNJnUyA/RxptkCeSJgxlWy+6c0Bnlls9HKV6gOb1owkqSk82zliz9S4FOjHrowbVU7y6/TOsnTVHtaXhTQIyj4qbcBTkUSqPzUig6yLypdb0wGG4wiztexNQTN+5CjUdgixytzxnkLajfhdl1csy29c65RkXprvynK6v1u476KwYUCFAWjhglYhhhfLtDnY9AGptFJHaoxIDVpy4FY+NG1kEuxM8cTTSQtBUzqaILWSXXIh8IUhQBqSYmrmuB62R7JMDcBcxEquBN2iNFc2iF7Ko7RJxZSypExQ2QPi3YGNd8YGTWK57RQ4y5Ko6JuhCa1nEhMhOhqkV15AD83mM/sddO8qzEaTs5UuSgzGSLOdT+J3VgzEr+iTxZbibZHv6EL4pf8at6rt4nAEYmS9OgaZp7p4ePzwQf0eqCNJqHVCa5HKzrJk6IDHnpShNrZXF/4umqX/6h9VYK8fWk/w9ldMbyLUcLAm0fZWFR/fHIZJWZ+2bWbGwah9skai6zX2loohHfr3kU/4jO1FzlEjGuJes/tmoWFnGcFZJ2LjcuuyhMl6WtYlWW52ouZWPaC9moKq8eXNCTfMRgeXwlPwJkvD7W7xuX5YCsn/FHh5tAbk+Rj4cnD1qEpwU+J4A8PqLbSrBEm9Oq4QBQs1zmetG20yQr8Q0k62VJ72RMfE1j0JjPT76c5ZlhkMJtpS/ERMV5Z6TVWT3OUz1u6yy6gLYjJKbNoX/1GbaMuGXkHP7+x5hd2fXV7e4phVLg4IIqKg9F17bkKbRsJsbHTKpxJvCuPh1XHa67mm9TXwK2gA5BgXDA+AtF556UbeXaF+F1VJl/QxnyB5+oGP6h3FV8q6VLHGrHCp8qgdMjPzcW6z31hU/k1FLNDLpFI2HPwjBtST92YfDkdftSexms7p13Rlo2pkoFqMOw7Khrq1m2vT55RAkJQL88yyJ9jIcZyXZhV2WwP6uv0RMLCuihxjW+inQ/KTKN2VAfNs6W32YBi0zRw7HXZTICMfyKszelhKUPukhPhxNTj8vW/dTZg40A0qIlcupcCrLH6+3vrRL4Q+i+0BGjM2nfIuz5gZDxAudjB//QIleHh7HaUoy9fquyBWrAye2Kpwp0LvpVyUbKCB7IaZL2GZxVpHDzMsDibytcdnBJGiC60VgIGVcE5/SbzdhFaGeY+VUl4bwRb7EcJlQ6niuF08xrsattT7g5rIgMro8wIN0cbbktrhliRwCMgz4oPWFB4pZyTL/2Uk5BNKH/XiwudPVv/QSfys9ivEai2Mcw2eFHPRLXyBKzm12WI4n2etygHN5O+aQ843EC2nq6CYVVGHFG2ybzKaxY4GqTvZSSUR0WnLwzjPf+ncWQWQIILWcv9Co9UlZ17VCW5D4xYVvkzecu5W13/k3lh5oC1bWAM741664c7KG/WtFvDm+wuC5DNe+y/+9Sf/ChPCaQ1/EmUPdVKyvjZeqzKZw2j1MIe30M5GsLhrmmLnv9I4F5MmRheP/eFgfnXAXRTt4hkLTXAsRan4jUm/7W8YnS9BgQcWM35B8cqu+l6fskBieEcC1syKu7WueO4emAoPRFR5aNH9P/E3yALMCdrknzrA3foTnKO7bwQMzDPlAYlEtGd/bPsiJS3KrCXPQ8Y3vNjM5K9OESxIXru/qHKvCkbM07WAlBrY+i46S809PfYiRhQojBNW4eGDvX32GcxhU34CzWILTmfGbp5Qq2PaR2QQgsmJVWmg5YWZYowt7JiE2zb2vWdLLM6FvApnhG+EG+fon9FcYulP15RpJyynC3FzE1o0kURphMrMOIji4zVov1ytVvclNjUdiksa+zoVbozDi5lCGYYF1KPDhsrciVHrzJqXJDBejiuFIGKxwOBkKIQeLwZTGMyCKZiqOwcntTMTNh1VMzqhA0KAAkVXyiMhCcMUy0ayHfLNMHBkFyO8R8mch80GTy0LhoCf3OIqPRltICCtZus2N88T8u1L+oLpYKlqmyqokwOIj5S6X0pFfAZ7gSunAEweEsdYpZIfVhi3gnGA454PE8D74c6fHt/4o0mpxnegBkaYMW2AlmtZkAqWUGp+ubwB+ldbAdRzTAtouU86sw5DX6CkGUQsbf53ZLH7tjLbvKhLTUSQXpgSU0lle3LySzKTt2sOcHu4Jku+M17L379oIvBRe5a21ndtdKH5YPHSDcldjssnBkMJtniRoZKcgqa8S3DyVrw5tlMuJYkqgNaXveQFH5cLaPeW6Wp9YBXducamBq3z1ZZe/K8oAsDKV3UX2JcYMeZrNVjoWHSMkv3pOfooX6vqsJ+VnC3Wn7I8LdtGmBIjmcu/94lW3sbYPO+bz84JVzRfl9d7BWPXjHEoN6XtJ6tLOrxKg5WCJIBv5+YFIdjLZAMSYlDogo15rUVTJPk5Ebv2Kd+Jl0F7ZPvnlUuYs4LtPRGZwJgiU4H7wEnafecSkzuL1QuNuGfaB18SRc4T+Amy9o3CKNzCBHLQFutGutQNhUrmgZR71LZxslp9Idw+sBHLrmhgtMfPXxt1W2hEifJsC2LKQ6e6Dsw7rjqmqIeTNazgYbKwXdajG5H5y81yvQ+iBtnh3VhuHo+FMuwvpkzMJBgltONUyYUUpJhxCo4YAKlIMsSwYk+eSJ0CGL+df1TpcM4UfUZ02R7bNrWRCgzxDTuwZW4+zBQb9J08KUqr7ssKsmVdLWWJVx6uwkY8QcQ0PKlnhR8lKc3IhBeaaVxHWwkbDnVOFgn3o19O54gYBF5aZ0Sh+ZDA+gCLKhTud1fcfPP2a4VptH1uLLV3mt5SfnC82f+M7cTvf6tjFKbeekcD7qkBcGJxoHGzah/Z6rPvpKdPDegcQrMk7eVtftg6Xt5/H29gwFg10FKMcDuwjSTnkW+Pz0xY+Ue/ZXAGrODMTfuURfESlnZyW6iPYOcvkdBSmW2OGE/aOhryX2lOiLWK2SLsrMkjK8wJfM8Ko1291u8WpH5ahk5+yE3lFAos1WCt+Yw8OdH9WXFJbPNjSWRo4u8/LO+bLT9yq9A0uO9b0sIwrMM+jC3DOZe3n2ta3IrgN3tItvCKs5OAQXKAMPXgebpPoflkMVnHzfxQsIMgpDV6PunzOEHfxQCsTzHbZJiInOmd+jdjffWCvIlDXkCeS+xZ6ckqD170c38h3nQXnRN1S111gnoZ3TxMLPu6lzGX9aw3tq/76/RoiaCoWilH9+UdPl6yyAn1KXfmCfkpI9T9bXuxpzilaXirSvB/bIYs4RYK2P7JlTDU1tnv26RlMwgjL/8CxhWUxiXrV/BKp/w+kUBoGDxyygPIctUXtUsDuf2+q8XOlsCzAmJyAw2pbcnaPUQNbQdRlXGyFc/VHPfaphidgfYu79oaG4eZPJnONq36iLaU8ryNUfxmL7u7+bEY8p9GNSvZVHYez1Wz7zdU768sBMhk4hwH//DH/apRLbAdW/wVHMtfV9+in6XNr+rryLkwNJ7lu4HxfIP4ueggQCufAnlPDwCFHY2Y7EWG1DgMSfiKvJPr0WgAt4lohO1nEkAzSblfVwJwvKDmZXltc35Tk1BjTY71Ss0lrUgwvjV1KyQsFwXytIts+s31HCyCX7afMuFoPtDqR9VsQ/UphFckVPMWMfdF0kSlfO5Wll/U0AmDZuJQMJ1l+XW8opaGx+ZCygOmBYVs6Lo/ffJG+I4wT47H/hqUzPnLBlCwMuZrU3BXCKTGGyRC/658ZdqhCsOmWFNZAx1bz1s1z9xgWr3KHmuAJZUhLbalkSSN7VWnD+1SfXcbphBgMdo3OySooRvjuvY+vUUMT8CnEx31mUiI5Jo7szz1SQCod7hYpveoal3ISJPuHE4qEEtfMhGbmEOgM+nNyuCE8KhOe6S6BF0+abaNmX3SGTIKquN7HKU1UdSb2sJqKlkqBFgOh+BlrrV0vAXT8+GOsMcU48pZsnKzdDR8UQxC1fL+Sfg5JmHe7SMuNQXn5aeHRSlA6JOe5WIhPkVfMpzlT+hW09FN4K0f+41/cfk8NR7GD27DY9psktbm2VS5cgTuSKqo+8i6zCWrVCNWQGdq2lncG636hTGkPXxx2KvSJsXvpafvsvFOqISI4JJPJydoIMMq+p9dqePgKMfe9fF5hOb8InUekkHcnGfiWG24xLrcdlxmn5IRlbV3uut15BrRg0xR7XdsMxhfUMdYN9Oor5sTbxkjqBNwG+tlBPyFlU8Y9GdDVrRtx8e3CEHXMA6PCADu+blmUAl7BxcD909SIn+pyC277Oczuqr1DFyoDUvG/NIBPukak0t1YSjbE8J7vu08VFwsoM/ADSJAJfJR1TWPE5oNE8Oln+9lGvOC5ZDwXohU+WlFHCQpROYj3t52Q749/4lrAillNUryVOaF/7/5mZPOB/ge/dv1010+OrdcSCrQzHD1JzoHaOUNS42n4mTjLJILVg6CCe8RvUsY/dsyNhTd2VEEKHuj+1BSVE3KvL4wHfPzrgq2OR+Fzdzfuhz8tguJoRSyOOcJxrz/mD9BPC6TbLgyUMWgLWTxuP1pZJ136E4bUW9Mz1QPEvqsyofODSZ1RA04i0TAa4mO5RTFo1qlZjiGRK9qrz4EhrHXX3MEjyYymOb48zO3gcRmuEGTesPXMamXPkVylKUKINWoJMBg+7xa/z+d1PoedRGyQjlvm9HHmO2SrLcGXhYz7UWUf6XlBvdxBPz/InMrwRN1GHkvFPClYZhKxFw9nDDAxAJ7ZGe4T6Ng3LwsEPWQm3pg05Q4S77bZUz8hzuYfY917vyCrR2QNjL0gx+O3jcxSVUQVPfR4m/tm5Y2XGxy5zsPG5GmXe/eP/J0IG5CvxSe4r67q1/hJ3JfX1GugsNeRnqzFLc6hkTqDyOHP7QLP6fW9Ptfvccatv+J8AeEdI4t/KbE+SIZd8EEHNsh5PADNqRpig9c+T56bPEjIA0RGYGSN4KxlSsbwJNhxkCat9YuUw5x/eHwbhX8wmrn7gbF2N3fPKHwxkK/uoJZEGkIPYZawVH2UbFzQAPLesfxdQm1hg1gk3Lcyw36LtxLezW7DwZn1o+k4UQ/PVTcchcfzwE/qjxYNzij65XJdxWzF1AgcHSLglwTx/42hTNHaInl577B0x/5qFgWJeHdYGsBd+PLdhlX/lKzLkBQ95ges009DYo4wEMSfSDRzpd106hXnGmRN4Svj5ZMGMX9PyNoIpGf8eLrSK5fxtlTXc+MgPysXdDHRNifSealbteU78066yPuBcAWfubi/8GzQ7iHiDJcI2wMm5yQ8mfED3Z/Zy8TO+6w9YMHDHrc0ebBdreXG4QLfAJnN5iMe2ysKZHhLUhffQQ8l8v2VvRP1B5cNXyZLxjTFeFmszqhMa0Kt05YKzl9QeO/1Za4YO6d0eb1XMQU65RIwbJmwDx7oMJ1UXL8HcwtxOSVIiamWtYWM3Jgml7DUrNh9cXLCD3SY0LIQdxdkUMxQ4eT7S+b/cEYL1t44LrhPnS1iKIWMnU7urTul/hczTXHZiiTMZm4nvAUOF6wUyJSGhwjt24fz/CznKMfiozLEjr9Lz1r4fgKLCPYj+F2G1VL4C2UhYs0+ayArKue72yPCcyC4FGAfEC1Ah95UEEmDrZpZhab7V9SwdLUIzhlbixCfFQ4SQLdAoefUlJMZZGdGvFdEDQSevVbEB89DMnuKpcwphWFVPsQo7Q0bY6/8lIHH5vkuAcNe762Y+vtFW/shB6NKcu5JWW6+DrZWSJbMF6XXlvYs0xZHFW9ldCaXJHD/U9kj4bZsh+QuVBdN4oXXOhxfGEZzO2l06bqJTQ8c3rYOEnlBUmT8qCcFEEF9TumUk/NfOs1MKQrGAmKnOtl7JdyUZCXBHHJXd6ksY+7Idb3aYCmlZXQHn76hL6cY6t6O7O7b7ACEMD9YD1aonRTbmo3vUvebCNU2xjtKTWUjJQtyKcmYxl8TiuBRhkLqCt3ZBkGAx34MLmJV1VT1IJXVV29j0ITXASLEb4mVjOltihUfkrMxMdfi+uTUMUx1vcYYQ8FcIVVqJ4uLz/AX0kxdmeOkey7s5c9NWjNP0ztMhItK9fQu3wDpLgdjV2C3smk3ZwXN0pSFdAEJ3ctbOEtB1vl0PEVgLDIRHnCrivawm/H5FAKT+kSOnke1q9McVluSnu7sSssGecu4taeXEpzxbpVUMxo/k9aEoB8RWvZYmkK92YAq2WnVPT2G6lgaSUvepNDwvF0hjDFcri1I1cwHQ1yMrm9Xhw1e+2TbNtZ0heHQ+5r4mQDM3MaE6xOEKdON+8dMeu1hQ7u6pIXMQTCNf918nc+7bb4dsvADyM7vDo//LuBXQHQQBPKJK0PPyhLHZxUnBbvvqCiZHe4YLKNgkYkyfqvyoFIUKMlxGf9Uao58dBY+iWphZTm9X3VCallWKP2rf6X47sT42XXamzL9Ct+c2dFvr+WxKqJJGZUF4WDby9MEdwQG0ZcCerSkOnpy3DFnixB4RB8w7yTvV+wgsDz4kWiRJHquiF/dbJmY05xU8FSCHb1pWN007cqro+5g8N1M9fPPtSf7Er0zf0W/hiz3VfeJZUFRd3JwB9zbvxky6/vi4/IVx8b4qmKli18mSKWz5kLpZ08W+/8tJlfuN85k0wyVF+0WRLgwkx5+MhQfnxviUnpU1l4N8y05QYs9TExDXbdlNHcFC5bCZdfl6uslhj0vwHUmsU0+i03/mc5TN8CTBuG0bnrEVl14RiNkC0UEBQbQfa8ICVEWhNgBcnceZz5pzem3plOWWHb2nIfTM6/ga9o4ds4Ivvrxt8coaz/YMtqTnXmVtLp9ls0uJVf3uK/nxNjtV0/JqmhDgLfGtiJm9bIvMl9uhMNs3cAVcenkquDtv78FrLtMPtlljgdY+ncbKecl5nKtuo91sOgC1h7PWNDA9rrn6zLpSXHPd0yrwejsohops3ZrWeq40JBzSNVNkXQxPmmIywBwc4o5UoWwVSUeZd2/Cyi8yb8yI6mxugyzu5WWi9ITraUbZxJik9q2vnjG0d8rr4g4U0peb38MpqttgRYXsYJywQGaJD7WcTCR4kuqD8ts5WV5GtE2oWYv1My5nVpDd1dFKvy/vjfri+ZugmG6R5bknD2Buc9lUr29SmsddTZQn3w9C+eTru6LI+aH1u8k2S9FaJ6c0gpLdRzhpPjA+s+XFzbQF5zbfRJEKJ/THK+c9c8ghc077ktqrC7H7OxoSJSRILp3Y2M3nf8eS9UvbNzBDJ3uZF4RWUSWM1kHNYwSdUa3518+FOLyiz9boQPzuGjbmMz9U9gBNetjwZU9Z2exSVLS3WGZzRoRezhM331CzQZOhMzttBpaesd5OiW6QTuol3b85YAbcDbdSdkc2oOXSNb3Gi02K4hy2iQfaB8vWbUllz49fIv4ADXGmQIr1P9NCn88RvRLyfyPUcFq+YwMP9V8/6yhkDnS9Kmhvdz5bQ45pgfYbiEiHIP0VxsGA5iLem05lDAHr+lpcl0OmdA6pS74us+JUQgm/F21PLG83jISyLQvqB+uKPV6ho0iRm3d243dL/HJdx7brF/yzpUTIxLRKnkzr2vB8uaSqvE8xeswonF7XXZZ9ubpPwjj85T9PKk9ye+IS46N6Tn3D0nZlCa9P0URdjslG0h10z7ys8bdP3eufPcJLSmYKSw+ebvOrJn+9DuSoiq3Hem1fm9+l49/SyfnTQgdQ3U5yb7adlGzzFm8vNdTrtpUwE6MyHONMWJkRslsj8guI9e273FmWX1ltdT/LqXq0urrv7GlelWRHWbqioiVenkxfynPVbSL43PGDsUqqE4T0bxu3p/9qOKO+12qlHHZrZgso4zkZqjG7YAg7PakEA0vJgt5jCzE4jCWmjkPFpgUjvWQZAlXhLN06N5QudUOaMoDJSprUeIsXt52aDrYK/C45N4w2/izWuMv1nHcj6RqT36DxPRKkQ4OPC3QHi8j9RmepITIaIgPvEJpLsrM5w4Tej53kEv7adlkyfGS+Ww/wf5hfaBPrlLTsM37csQhsbbbDhJ4SqD61qiVCWU6nua6nyXOv3ffvjHbqgWqdkZqZ8YaYIofs2dxHGe4GVzw2YLz/WTphFztt6vdd3/Vtk9Mq/xL38LFqdeUT3gsonk93D8HEc+aXCXj/9rCkXDzTWYqPfWOUFR6FWaMdtRavJL3GyMcsvcTKktCCtfwTXMdjVcwNoyDxFaQfAaT9zBjBA1iu6+jrLIPbGJqr7ZozpL6fPDmyAD3OKi8Iuv03Uiqj9AQpdzhH+DcdKxx1vxmIWxCSCXMpEZXme8+zhu+i6/13pK4H2m3lroR/WDP8w1Ao+sqG8LqKp2aMB7sI4o9laGoGjLeeeB/xjpT9tb0lXuZpdKR3G9WZV7I2aHLbfieztpf4xfGdy7/g2+CCmvs/PGFmT/Tz9AOyPIil4H/6Vv9itbno1j7INRMUOUDgvaRLVZ7ULx47SqfD7lcZ8lLStnCiUymNu/cN3abtqat1VKRyn/KoIcXulSlCs0hg7sqq7R/bF5FR+/sJtWb+g17F8oW7t2gf1PV1Du7o2JsXn9pVZbbcEijKFW5Zh7AtH/5qZaK1jZtjc3+zYXpDL3KM+FlzyE/3hR2QCdc5fPLPynBpT95bGuB7AavRieKr04mhRUpvzWG3OmiWHOIllsa672Fiu0HpHmaKn89L7R1Xc2tRePZi/PCNbk6DoFdMdJXWW0HbcnesgyDIifZ32xKn7N+2/zKlO21o0umzcsvMq6b8bL12buJFCLrgyUNkMkX2oibtn63pMnhg4ND6WMCyebky9fWFrM0URKf5herfCKaR4jbu+ufglV+gzpKzs+aX52O+3JXhkwdMhhzsgMk8KRQfLfEXR0LjZlm3rz7P8LATxxyS1ONmAAB7U3zUn/5gpI5VxZYOL54Gvpa9g3MJM81i97qpd+za9QwiSQt8lnV2/p3ZV975+aFYgMrQ9LM545pJDSG0OqaEOQN+vsv0i3L1fssvV03rnySXdK/oawRcHE23sG9vINVKWhZ0mk8Al4VMfYPfAEJ7yVflt+T6ZWXqYpScnIybqlJpQc/yt/9UcC+7Az2Tv00z5adqTu3og2f7Fwbc0WrO2xSvD24YbB+Q92T3I82AOLl2XG0lM575ifEqM0rBDWxfcnunmSLsn56WuJf2jZRjKgd/iLc3Sx91/6ulLcwvyU/w+lKL3GtbAHVoxYs7+IOyEWrJCNXTryzxFt/4hnZ9Uxj99vFjFqvcdjrqSitH7YvyAxYRsrrlydWGT86wYNxzHanWxGecl9qWE3jf57riYlNjnMk+e1SXn+eMl74w/TNsmy7S7aT7N+fp6ga/3EGKfDYbvncbXM7Jmrbk311OUMOIW9Crrvl4+d3ef3ptY4/aKrCv/x3usZcd/tksXD5X9n1s/KDm1Tlnt7i5U6JGDl1yurM2jO/iJwXJ/LMvlVWC7DreSL/3SM0u1fXn95Uwk/fanJDYRrJ2dtZY+RUi6+s9havC264I+2cvKQeLFB8n2PqS+XqY1PGrrwzdv76bXPSm03JfkWOIcNblmw7aLMuNrKuUNOhq8kN52UvHl9Uc2vruumhH6iqxrfWGuflL5lWO5qyfdNnXtAn7m/6R7lfFkxcU/wyjUNQ+f77NTFQmJabXBoonPx4LFdflbzwHtL+4IH306ri53pTmP19BM57NpQVUIu7lzYrLkwVhHJCJ5GNKK1yaBVbiPT4UMG6MsWBBT93vFICyx8aGnv2o1JaQnyUXsE+V7L78cIWa+k0UvW9SxWy79SXwoxihpbuW08X8X/1prDjUrKv0/o/uHN8wrD7LIEjbwK+Zm8ZKhWmeI3l3gtjrLbC9B81JG8u2NadpfM/8NgLpNkznTQx8vME5X1zQXVYAky0rm4oun872U4wfyiQ384IVdWlpuVVB7nfwNJXMBlOQTitGP5GQdftYF5Nekp1XTDjjpx/y6zg0q/dxfY5Yej/URquyo/9X/pPY01kJX/mMOlCHP47SddXaKyDzVs1VYYxj/doQN8sv9R+7dfPF7a9JpYiGLbe7UjcAiHdtBVyo0KvQBPuZMOsS2NXLhrFQ/bAg0nZgPWtd4Yz3cj2E1e4i7mcRVzOJg53c+py3Wb2DClGLhtE/zw5c/WNmWtOisA31L2VsePpquOxM+z/JgR4qVM+76eaCNYl4KN4nvs5eoXCWzExzY8EBMv/MAHrjAwqrEikwmn7AHfIcBJv8GTkD+dlJ4US775RFvPxH3z7OcfcU76duH5rEkAwU1b1Pvdsb5jLNnQms2J69JJ96aKasvg1Vf0N1cYKOclMkrD7uTLJi+cmAXeZuVh31U5KalMxm2Wq7iVJSKl8GE6VZkfaYsR0Y1ok6HST1AZlUQL1B5mw0TUNKnvRUEUeWcSmKrHNUxQxXSEvvBHnqYdHqNKvE2/YyRg6ppH7fLN0WQyp19ycAspBBNKn8thYKoBw4KKTs7hCZJOzETF0iKC7YWRkbfrCUvrqYf2jIvbHWaw0o7ob305zO/m8j7h8rjBGmwHsWXSaO0Gvuq2OKz9LYgdGEvSOKzxjli9RrwX+dp0fdRpIvmoKSin5jiiZ6C6ldZZXaPe8iWpxpJRwgk21oayhIRhuqquoKUsF+UFD1uqm+FXOUn0nwpP+VtH/zrwrIro5nWQjuQZ+yksh1nRXcm15cua218rTC9x/X4UarsF5yb5atc+u8dUC+x66LIqKn+GLTXC+4Z8PB5gpYfMFipL1NeTYNg1SSz1oZsnilJ0RPYGUCl07K7ShDpkh/4POrMg43Utx/WmBsiGbwyj5etZRaJQgAzGw5+XVxJbbSzM6syqJH338V7XBM0WmnLrR0vLCbm+i35bt8tjFKuZs+ykR88I82WvLIvrHcsbyYFrayIun8uNV/zwBBzszXBrhc7YeiucOvlur8Qm2VwblZ7z5dZ0zt7RSvwhf+iu7WqOR8gwSm4wjwD/NEQlnLxQVp/w+g/VAZ7XfQq4MY4ZbnbVzRa7a0QS2/hRkly/KyY0sfHIA3k2b2REmdrNqOjwv0h4J6dGMw3W7u5BWU+rE1mqbTjKV3xewx3RsKjGQhq19Zrbm0W7KAIuDH+b92AWwpzG6bDEHYWo1G0cQYLp+lt7Lzuh6J0M/42Ccuxie1c3HmUananIODxgNwLgYxqWhgnvFREMlPkxylyigAAFsYC+t6JtbhmOrjI3wBgeGRqUBgHQHw76Xwwhqjnu5nCNcRmgFYks9YQC8NkQP3YlSQ4smWknUv3QuAMI2l8xx4Ty6eLaLWnEnVS6axRF4mZNQW6zfc5bM0TArQNdfXRW7XuisQIjMrL9lrYVrUoo0iEbAbYLHD3xhV1cFTDTs71a0plDW+kQITn598lBa6rEDLggLl5zSNTv+5o5tCTcT8lzkGEbAF2QcffRAatrJQwBlMSJUnJdwc9uO+JtJ2VaQOqO9ORLvGQw3Zs/NdK0hy5os0rmsyXzbPQm7bZQvgjlhW1aZw7i57ytjDfmPZfJWKarMf6PwKUDSJLUE/vdrHtNU5UlWJn2vBVKuJtA2qmpJ/BM5zD8V8c4B23tBybwcAxUm6eiXvFgBQAWzw43Z8yKuiU87opGuGocRy43hvPZ5oj2rzOl/IxYMXxBbYxQENxTq3HHdBjZrQoNpRiF5yItSw4ZmJWnIabFxUx0w+wejRbG1hvTZ7SFzN+jl0dlNw81BkqaS4Pz2me0OhLYxEUmhEqQ0ukb8AhIsGlQDWpw+YgRTpyvZY3JspbqSYrjC9cJAABXOUsuVatYnHc3hjmrjp++Hizbl59r7hy1G9msNzRk5aQO+nPiIjfC3pEUqZWJyoI7WA1Zsj4G+9KGFmbKpl+q8PZlmB3Z9MKnrUJ8+z8LJu89Xfl5L6mNXpA4wk0Fq/PK8KebnTbY0V7DVvPLnXQWFD97/emspLS7v82HRV8XFrkP8adyCfJHA4fiEXb01iZOV/QG1M0hRocg/a4iNBqWOC2Hr1l4MYx/h++1ceuNTbH7AxqXPBB4nmr9/mI1S68BwE+7Dg452BNMOixtsPPLKS1JJk5XHjACdBrdmDrLJsD9uzRpmBQGE4uSHBvwoy987oIG/nH1DWuIH9gE2IyHJfhneTknebfZYBWyBUATPf2BalFq/p5tHkvYNXuWhCLuypHZO3dw6AHUPlQTiT0sjGan3R7rXhtv8wNdLlnkqvj3Ql+SpZzRt6V543yRogHn03UuKrQFH/owe+S0K3Ddu1087Vx/3kSFKMYD2jzcwb8OWj+o7sB40guTZeR1FKwoSF/9vWk/eyKxYRv4AUXxxnruw1pM5lxUViq6IKN+wK0OAbMgwqQIyd966PK36CgtLcP3ep8Kui/M6C1cWJCz5X39P3mBzfEWTpyTPXVjnqQZTNzq+sYhnPqszp7n//UORwtz1x3+lqnl1vP/zY0PW9Q853M+51mNdoHut7WZs9txpNa63/lakhlcPgTir7ffJUkwhd7hycf/ZHVp647iqFjXxLwfPWJTVMLS0ccFFRx+Wdy0edK96uMN/3YE74krfvCj9MtU3o1fNkV5ZpYMJovC17oKW9j66/IxM8aLMYqU/pqgk0bUfFwjlOR2Brf/3ZizJogaI4gLNTMs/EG7PI7PZaA8hjp9m1uD0E1p3+uCP73ZVTRdx7UnZTkZuRUV6LgQJhY/2K+rZTkXBFzgQRgvTc37R5eS7f1sVWj3MK8kIDkaL1o59duWlI6vFOv75lLl3Jg8HXyFjJrlsAkoUPSYNpNqXgDgiwYPHSDYlf2hFQ1KOwygosMHVrLm2m/zSTIDUbau8fzme/zKt8H5osOl1oin3g4MVL4rSINLhEi/hPx5XJc+h2zul/4zsfM+3q4fCQPnth6HBaOTbTsM8InolDlmtZhveCg62jH3gSzo0/fdWu45Pz8gD7BHyPKKlzeTal5Z6D3E+8JSK+R4+x3LchjRWC/lUjaAAoAjh2ZkoogPn9tFI8Dkuhhal5XypyyuI/20TZVfqhrfo2vTzvyy+YdNikhtWxJO6TRxN2vg4ONgyeSPoH+1Ga7KRLMWjXy4bLrxYp/7uETu7fd7/b13f9vvBuB+DA0Yyt/MngLewekLn86rI1ke/hsvY1E4J62pjoMar8zpAy1VeTI2hfMqFGbm/6MF15L6/zYIHr23ffxhRHWDSmcJunfzan7HkmzHjdDhTdX8TGopeLFBc34SHw4G1SDSCJPIOLuOdcuunlak4z1XNe3rz3WX86Sa686/gAFDKfZBkS8xH++9ZocUPea8sT2Z6jLBYj6XdORl1IMYlLwcuxeg5zKaWv104CIPnaac49vPi/fuhLLsaHGqhysi6xfae49O/uNyzRzCWTnwoail0QYj6yKU/ZDHAOV2qu8sGWqBl2kXe5rlBRdB8/XAI2JYQmzYTEdja4zwe+3dsOtvppezu0vmZpC7lZc+t+6iNHLPcXx+8eNt7blx1AH5vvPvcwgDEFW+ye0aC44sFtx3bLT5Z8Cl4k/8u708k8GAgb6e3vgvqS9d/4fg9ig3N8iZJZyrlHuh8OClKtoLYN+CGf+RwTc2V62ev3xrrYiGreXD1jiIysumThyzb8vDGKxYJFZzHi03yX5cBnpqsG4MVDa2qnC/FHTHZ+c1PGGrP2KmhdeOvHksqfazJLdQDsoa2y1awPgYkvBnLPOk5hc/G5T1ygGzJy+jitzNqfkxwtEg2JPjINXMl8rXOdogiClM2XpHybJ3OXG/SP86cDhl6XdZF1sCM83udDNYE3QoJ5BpufF88HwD0AM2jKyi/l0V6opXY5tZrseefcuzYzTD3BuJzXaveGXkIBRhHda3fZ/yR3GPfx7JqHrHBGDrfI1C3HdXScNiK1yIwFQWLUzr4PjtPNGPG8suRVOEs/Jh1FzbHRMaeCHQvQ79Of/fJlr3YY5pGu0+dnk5JhKbNp9KAnH7swjPw6je6zo6yFJJ/BWq9lRw308EeRqb0oWS56NsIQxjQNtP41rD48y43q5N0IR7OMs37XnHHAm/HDu0rBkt0KnmeuojBEk6oBivM562CMFzk2YVAacE65yrfbLXTorr2JQQ09i4Y1GJflupFS8FQbB+eEJWAlEePbFaiUwnLev4UbWN6cuI8VhvWSR+sbkDzD+JjSNK0Ops52ZBM1fltcDh6ccp1/RGIsL9RH+ILuGPGj7rZMXvNM+nHbd9bUKIw+z1eb+TT6mRa7tIzej5EbJc/H4Iwrk96LEXJzvqwU1iqaXF+b/jhxIP/Kknfi32X3v374MnCGfmLnkh0+2Go9227fLH96Dg6h4uu08ZTmbm8FxZnhS+pJ38JuE/unoH0bX8iEeZNOB5up1xeMY70kyZSzCC00D3zleM4gOwhgJpqAkznuadAFM4hCfcz41J5PZsehXJf/e5zKPN1CHbQGuvZGhiXygaiUDo/EuB+gJX/wl+UexxeIDEtidYLVRcnPCt5tJIrkEDfGlVJlr/+slg/jBGOuj/O7w1eLg7nD5K+F3wOWvbxeP4iekzKO2xf5FA4BRYPIQyW9GblPFfd5QtjR7FhC+0/lShLeq+0QpGCU7Tdz5qjxxKDSOHMjrt/SRQke7+DYzWjj4Kek9TdC54ggibk+ONrgO6hgL6mGXpyDwSBHvnP6Apaad9DylYjWPfw+3v6UVJOlhmQCW1wqHMi1vfQok6Jc50z1wJtlbS385EF/H6SpQPGtQF+7P+ANZk8fLaB8OuMELIJCBfNHVmpviDHNifO7w0Z5Cgn/w2lHBgvWHRfYj5Xz4qZ6y4Y3DED6d9+X4oBokYvG0Yfh+L3UBtGvF8u03lXNOT3tyVz//4UuWwHtffKGu4spO/3Z4Ukeu+xmlEQLDwJ/rT2kNMvOTispb57i8K/hcNRwrlg0D+sVpQPqdO8FLV/INCqUXyllLdekMBT0WcW6Vs5ubVdKYO6nAtoUr0UzWfBibMXJ17wA9rhAJYMNsD1Sq5imxRGFH2vidBY8IyNxy6C7V0gDbIZkGfsIDwrrTeVjZWW1IvUCCnwI8v+cKlXixAFP+OMMFkiTa6IUejicX49rofHJwG4ruLHePm3i1kO1B+BAF7re0noayNahBp6e39sOvrVBO6FaqjjlsAuS1wbBYXQUqkRSmGBVugy/4Gz4XzhAJm4Db7EQk6UVyXgF4Vk0VX9QeQsdLHPlkn5woeIC6w/JTssB193VLMFzQohNOgSlr7+xEnpi380VzkuIexY4bfd/J+CG5D/UjP6AULaCthlMaPjVv2+uGJHXmh+SAdhHVvbdO3Fpdr2PWzYQOArJoblKaH5eTsWVyBJW6yHte2lxbr29WzwP4SqETYxyu4bC2MDm0QEzhqVt0nM2N9+kDmj6vXK13102IYGcn2ziZBRK9Ur5b5ATNloSaBXbOEPi8egSTm5iAeNMSRSN9AZjhE4eZxHwPBxhDpT7aQ4rUl8l7bXRGnczHYAwcssPMpcOb00YCvdQ+TWvE0/jlp0hdR99NKz1K8Pshw3C4QguyZcfa3qWvZLmKy+dhsFiBC1pZS5+k+mAhS1o6lfo78SkOb3goaPFqv8DWcU/4w0SRPU00V1zzbX5mQwv+ad2nfyuqteJtyveBcHJXlOfXLtSbx1tBpIs/IHDi9eKVZhWNpamptXfZ36W6M3GFo2mDNlpKQipsQhX8Wg2NxPZ7sWs7m0TioumevGaqW+3bYb3NmeYlQxsjNEwvzHbF4APEnFgDqQ5NP3kkFzrvwDfCZjyp3K2Sei9rb3GCstjyn14ZcBBMfOVC9sRrG5l8l9Jpeeq5KrP3fYW5/0vNEZqeCaWUY7pgvXPqYbR67dcodHKq5rTsoTatjgcYEQ8I9wcHGqcA03is8jEMAvTPiFyUjF2k3suWy2H0oYlqFdVSpnHpx3VVTHZtoQmvAIw2enNYthuppv3QvZ/XnrAOUtLvrT6a+uZcyUGTIHU5joR6ff6KdvkhudZdTlEofAPDaPvmYz+yJJHvk36DfnF3qcIKDSbvtsmppXS5sl8vl9gl0rIaefspU9/wrBULD7GsZ6zTIhbvYIV/9o4wKJEzPvqi/jiEvLRtVN7phIko6NKCddpgZFWuqpkucy5Ht0BCRNTTUCkMs3PqguJGVH8tEz6YpCeU5OQAgxEeRLl9sdzSRbUBbscMCDX54YuaZGJvNo+wEEaQ444fMFTKUjpeZod2EjMkBIQVmIEJc3BouozEG5+QzExbivEA9qxft4qdyVZZR/vZmgvff45/0um+BQPr7kQj8dbFUTLt2HHpVheRzO1oQAZlIe37zaoyoi43is7TXJcSJlKTUcNZWPlCyoIydjUtCENBIGrsF8zEZ4lCGTugZ3doxQzec+KhDSVdjpFEZip17bwZ5LaMRO5P3ifEXcTz5quGRsvsJemFRJm4pSILrmqHlWCAfrs/Mn1ai0aB2nmUfqG0v0Ek/Ruji5wmlX4QZioG8sgUjmVqPKjxoYI7PDBUELCrWXdOqzKansmMblmgRVW14hK26kGpisDVEUIEZJcoSf7C0lVGi+HoB5YUiWAMg6L0SihOGGxOaPfqWXfLJBqUJlieA6VAYJ9Rr2anOL+2kqxQDC5Cpxn8mOmcBCjEicGFExbkhEBhHsr5cTCJQFtuccAGTwTi+M47adQdmZv4fzMBpDP3/7xfbXwHmMmlNWA5JNp3tGm3+4OPp9ZcoZhO5AG3p8BhJeHR17ERgdbdbDOIGa3dgnNoXA7ym5oK7FNQ/bi9UhcAf29KPCadktrzGNi8FWgIhhLkR6773Mnf0oG34t9xYQ1UanaBYd4kBJydXYQ4zThecgLaBWQjROgClpNwBTOu90CnXq/xGs5JsAEIqNZJlDi3RCJ+8SAAoEYXPqqpfpJzbOJhTDJdcJqBrrGPY2WgAU781evYFq4v1mQRevOk6/sLEvBfgoUsJdaAYAWHTqakjGcNxXehIrnPA9QKWHyvDy4mHEj5GjeLqBTMBwQCvJ8cASAeDNxxV99/BeFXATkWLB0mY08GlFlkyRteLUo+3mHFjao6LIJ3t4m+vKO6XBP+tzfnXZwoNcXNmpamBm4xB6diyKc+0FdgnquMrBQz9dXC5AAAR7v9QsPc/HqPPsMo0Wtz5lk4D+wJSpBYSK8NfV+vy1tf5PSgEALw7YCQvgQTg/aC0AwmBrVmpKUxYviA2529bfbFsKAHoVo+6DTSEtpTELpBxpGHuaHluZHGrXSwAdPpqOYWM+cGWS8NM50V9evFfTSHfUl6mDSZGZRBnCdF3ZCmAEo88YvSkNmtT3El8RgnZFu62KexbHyvlZKADFCNc0pR0IE195L7VBk2L0vhSAEAJbFV1n2GyMzAwmlalLOPQRQ+69+Etc1M0/p30kED7DHp0EMbJ2Hruz4WWE3X/x5JctbCSQWqh++tzJFTsRtGbPcycUhFoNNSShFpi9LjqE09a24WUsC/nzvERpQ2VegNknXFc+SbAJaHVPwX9xD+Iexv0HsII42EomFLbtS3fAeP3e504+natX4dbok19uexXH2y8cu7OJPcCG/UHohk26DsnxMftFGypLG+0q1DJmr20dwVjLrOHlhwzC/+Iexj2I+w8guzbvrPp3YhVNXbx55aEHUxP5XmLhyFBHM3PsyKGB/eSbeqZUwMox3J5H1kcA8YcwY/vL0BHGbEgdGVrY6JTYj9fszl5DVxZtXHX4GfuPlGaE8AE24BUxUpVLrr/479NSNV/g1pOOp//nXGb8DQTPhIKH1v+sz8Cw6Of1cf7/FAnD2qb8iXvuCO7wcdsegnH1M3ej4C59oh7rY8bZTgdXxizQ52g+/fNd8AX883hEO6SF0qEFFKlDvy/JDnl9bLvaDgo2dsEL2M/h1PpimRONyrP3mw88AAu8FkYWoCFvHLwGm12CLRYBcCm2WaV9W0sq3vHRAyHOAYR0Rr3//H8Vv3MQT4rzATgJ9DwcHfqkmEcDymVD8b0w1UZdB4vOgBOzCLJ/eBLCF6oaJAM9eSH3KJtZekiaVAuYHvtDC7kBGUVBcAIEXhUBJ2J7jZWxPZadSJmarOoOvaLLcU+3o9ZdTZOO7saR617bOjumOMcWFW7OyqY7wZ0vgahl897hsWsJ3kMuIcKydYDxh2tkkjrYJ6yDg8v6xNDzTdJ+VIauwq/Oy/iPCjblOPKdbxhmThqo9BvJyXgzPkkkJ8BoiPKgsks3rj7tVV0iw2LhclLb9r+cSBVFjuIZiuhMBhIopr5+eYSNUpIEILyQrCAnGSW+nyj/UmE3TQcHH5bLWhNbZVTlVvTS5RLsjIzm+Rrr1ReEcsb8nElDL4a5RLGZiaY55WDAWwpB8nDiiAzH6rWhPc4tNE5h7WijQiCXjSQOy6nqrZbLl0qwrQxCGNPC8YtLB2YMK5QcCMBhMRCAlMwEy5u5ieO3bItxF4MjsC2ZdgvdeVoua5OXWWQ4Ugd2M2yHIDQVDJ6feouH+LblbTKKemuoHJRQWZk99LVQn0PZafG4EWui0FPGQRhDgBIZWIK+DUi91MOgXY1hhHSGQFiaXsGl3Xrtp9OKWitHZFTtWulx2WwISwyZ7+oYJ1thT+mW7+on4GSzs4SwIuHgbZnS0HpnICH11lAbKKFLtJjRtSv5TfDYC8JKBNbEqpgEVbohDBWBzP9r1L7ToOh5pV5BRbMm6Nm0W9dOQYJhMG9fmIvKJcygboeTlXOkQkRmKFgdGU9Zs+N6GLYKcY0iUDOSL5LNZthUv5YlyGf1Q/Q3vbJ77wEIprnUPvkQrfLvO1wO/AFiWM8zfSSUE3BlMriYUsxsXwPITAO1T3NkJ7DN/BInpxwGcKB1KLhFwTRzW3KbDMebD/CV84OcUgmQttbqzhY+4WdnQ4hAjcPgbRntVuuvQmzNmU0B/XMIkzkxHzco+b08p3XUzyMC7Bx+E9DQcNB7DwKmga9VM47qFExgq7KdETXigmGGu0rIijEo7NqT5sOM1AZ/6DeARRIdHiV9ovkUnSNQ8luYrxAYx0hQ9eKodiNMJIbNbX7f6eRWczIyUO15UY8Y3iNqeUiSntillL8DIxBNVy9lkwzZhvf77JGnmW0BzeLvzMQq2CTB551rrKnlZhHBNKZJKGhwAvp1ePT5y9L3Q4Ai/vhfszFhdhlr43biXNVCPkQYxaBeP9o13dchk3TpNXcReyCnUI2OJ/V/fSrEAuTNe/Tln0vG9deP+6UzKHg8DswoelWVzEFT2SMXVeJFEulB/ZGFk5z8fQgZRyJ/JXKwFPLG80rxeRn7hwRk93HtcxzGXe9oOO9eEUg3JFdqWU0j9TuYxl3LZJy1pEHnhfL1yZkaorNZvyYOp2wruqtdxhvE3XnXRfJ1yREd61gPM3/38wRVv8vHwwL0KbNzHUjO1Ukz/FfXUccsFbTTn5tOi0JS8j4fr8eO5DnqxYgkeV8RYQF6fAPYKJ+h9U9eiv5Gk+eWd1a+Z7acZ0AzTYRKHNO0yXGT44x491vRBvF0UP9e7Vmd2L0WFTKbuvY4WDVle2nHF0bcpaIZWN4SvI5RF/G4t7fsWCljqGSYwS3iX//bi/h4RnHFy/UVyiixfYrb7Q66AxLxpXiJA/3m1qS+HwUvRmU/RGb9zUJxVD/48leX/j2WL/lTRKWUNsioNnPFAfTx2ScS6IpadaXnf6KQgdWN7w11g4l1lVohTucuiMuoj+SHA0nWQLaBzMhTNz3+jpzcvHxfXoyq1qz/Vy9433iAjGZMr3mdvzp5HzP6c7LsnlqzrvODw4vbRYkxyZmNNvHJThj3yqZ5FQ49+S8YyJriWJdIUmTcF8VXGBRdVgSVOTgNiXNpMiuJzY3l9gjE+hKOVCa2hzQ8WT6bmaI7IuLMcNQumyqTmKsJquTqFGNc0x+ksHzsA+6xt0yisDSaDAKhu0enZHUOrPJ1ByPpbY7/kx+KxmvXsd3zKctV4jbHWSHMNNtQ1J9zUVXGLm+xMJ8PY4xaXi7+iBn3lSyY21xfyNVKqESJZt/Qr2Afzb9VHQwP/zbR0W5XClX2qQbN4DKKPzcIFJZGwClViiT8vQLjL3+RpLV2grcjPiNJH7xOs21y/+i1KI8y2xvj6Wy82oioqmL0LqsRrLVjUkk6HxYSmrZDNbelfGpXebo3/lkcl82C+/aklOAZVHC2+9nDdP4/Lu9eunYxVQwW7COmqjRj9hichujwLZBh2WCTdMriMcSr9xaFcKvON+F9/Pl26RDsIdhCn41XyCdvV67VMkENXxrTJaGZUlcuSyytOdZu5BuiKqYj9pi6JYaSIFGCcjpSKJZZOO7qtL6UpMreBxJJyrbOzvUkRIPWM/aQaSyGJXS2LOgONP0pVIyk+5HPxraN5Rp9m7Pk445uj0/uZkjnPWLMF7tCmFBdnBnKT3QsPG8q4GRuxQjFbGwszy/gqFKWUR0665bOtVjr3GzBlsm5nCs8CQguGNWWD4DCebee30rpV0hWo7UrwekJq3jR02h/98LzFRAhCvXjzHFlJabOP+yYhjF8oAmIv0IJrDC+sOv5cggjqNG82yBLylo2eSQd3Cv16+9zZ6MynkxEvV5nVVvLsoxZH1n5vCo/5noeCnaps0S46vxBUq7773O9q/yi9mT8ketRlq9vDofyi1VwfCYQKh3toFmPSh8IU6JbdPjlEFHyj9KTdUkVTfao4sfZnrByCUNLVLrOs9fRVMWTnTOLrChs4p4ik+50fBwXDa0ufcscy8iubhsLS0RNm4Mw6ZMzlQ9hKUNVNe1Z9ZsXVGJ1YIFZL2S7DtptwN5y0aVDJ9vLFWDIQlffNKr3ccSGP1cu2XaBImkeSxKWawZJktj6vMJES4wgbRmbafjD3hdyaEGFkbTsbopzu+yFpOq0aHstd27eDJIC1jYIvxLwh7iiVP9arwORHPEGfnb652JARIlhmGYyLVqSuHuiPCC5xOct4MmyVEVqFYSEGYgJsCoRhhhSuftRHviQw8Q/z1hirijISVCvAeQNkl8FbfRp5Xu5QoSYEFljNUe1km1CSTeXsLPMRpjJ5prNbfrC4snJqwXlUhqNcXZyWGHD+YGjyc6G2RyH8CJHWvXabLGxy2J4ScYqslh5hrlDXayGlUS3X6Z7HcnK9joAYKji/K6trSg8IGvDn4XokK29ZEVmGZ5QKhP/qhPyymBNkV5/cNvxpiaya6+sOgHGGLYz5Y5uBHSj3K3eHiM9Knjrf4AmvgD9goE8J8A1CVoIoZ5yAnwVnH5sNyYVLiUpMp8/J7Gw3Mozq32bUggE/6CNv/EKuyfTOrvcqXy+fbNuCAz7WvkmHitfYDuyrq/QMS9cky51zRfoillwJDCWG2iUmbFz3ZKfr+EcwtBsBG6UXzzEfD/tFEZ4AUY2+r4wFmqR7m8qTnm26Lr+VfztSoEhuHW5aoeQUtJJiixvoXV0taqxvSqrdOwLWkFzZb5T/WYhLqdx6iWmujUWnhzirjflhNBw/MyCHgJNbYWQH3HL9qvXqHoM3fu9jK6OJSuJXnFNF6mXVxhh2FmiaTsMcRVXNNStlBQUlYaMhQJXnNlE8CAiYuO9RPYNLDChFSdG1ScXbRVyohwR9VtMZelKmNpaNSjYQYLCbOHYJkNBH9OJ1Bz7gZCIQdmlwdXl49n1o97166VL1J4ld+Uo37zOdU9fgnjRSccOFKMDj/Czg7aH2i20ezY1ZOwaoo0fpbDkcg375nQ2mWqg8mC9Ek4DNjmWHJbZ2OEu/Mf/wbEMoKGeEztfwb1yQnBYiK75xe+e7X8Q+AC9y15nJryb5JMd0pKjlHjCudLFobtDhIdNA5i2Y+GDK3E0oedD3XwvC4Po+c+BNbNiOWi+MS8ebldCD/9aadDmOF5JM4uqZmajI9r5FqlwwpCwM1UW/iNLr4YereKPGsPsPc8+fda9TUx3+Vhm8TFQqHd9xlFSecUxxvx+nH+Owt92PqcQpPpvx/RR6NayO/owe0v9QflsP/BtUinFyo91/Ez1PD+M+wQqck6xlhLGSFpwPBfGmJQcj1Gd4RpRbLFzV1XBcG4uriGj9Es6z3sm6RcCvvWgAceoVoyJPU8oTIyGW/VJSYg8KfNTuuJ2qOnTGE6L8R+AZwBPdMJuCVu2+bNNMq+Sm8KQtxtmiBB8J9YFy/tIBy7ZACptCerE3wkIl2oDRetmK70ykz97W9hisU/YZ3lQEJ2wG8ub3ypBqNFiqog5jOrdmai71zw2yUkBPmNm+stusYQ/y/abZGgFOOU9Hqg37/eSx+kBuNlXL/gws8ZL2ilbicZlZvs/k2qL/S+m+dZsj5N3SpDA4x2+3DF6+DzVulDZEw4TM3mBjUzpakN2lWszSRwOFTaWbiDMJPL2sX1nAlG63vTs5kMYb5vQmGgLC0rY+kAUeuE2yHTyrq944Tz1LQDqJrW3v3JAuXC63nmeKv1bml2t2UzmhxsKppROUngmF+cd7+05FSiTlGuOtHOmk2nLKdWmoghRRtebmmqX0v8Z2ucaARA+PWeRZvGIcxH957v1PRCB42dvMgxLMrIrXa6SKXADJUn/BnNYXEBD6TBN+Bx7NL21qWYpTg+Na3jT+Z9r59AMjk3RmvrmPs+fd8h3EAJYu4PlBMXULXc/VoXLM3wVIRLNryczf7gi4NSJN4ktAkaSMrv4DzVS2M/lK/eQSaSBRAPh8rJ0jfyShJRiuIG3WyQSgXQule6du4gXWEgh5oW6tJCnpF5zN7CJKf4N5lS7FpHE4Q8+m7Vu+ZY5nRBQE0TyRqh93tpjK471zeGvpSuNakQYwgEbrhzxL1K/A9hsT6iwlLOUhJ+AEAL//eGN4C8nTT7v3z5ytUO2jsBbE+suOjexozRdkQ6X0v9p+T9YVD+5BhRJj/w60BAr6VHi0rJC9Ztkcfb/1fvPlsnFQUFBKWcLzm8kqeVgDnMSNHyqK9ketFP6tGv3jntpNHFSQeIQlfiD6FP35S2oK9g7gxbyCFU4+UEZZKekeNRcbX2veesiwH/c8CzzfPgQl8pfp2rFluBAa35FUYREluJcPLhqkdiKpX6Ht0BmSCNQLbErbk0OsnSl92feijiY7/+uVDWVN6loxZ01tzVv99fALyPD71CwJsMj/lngcN/xvhqDHdfQ57Yv0p6azqwKee5YPx8dJdIEbYLr7llSIq7JPe1L46vug/3OrPqd3Xoe3znQJI/MX5hU25DJu0MToliJDMn1uLPrnVxNaslb8rbKqJZ40u+m9zVOU7m5JTPyrxY3Xc/0nehTUbWy+dWl/oHZgaKEkGfrEY3reKJE1UuMfzBH+mdks+p9twTAUSUNOxaPxGen5bzmFrqaKpqVvBbz2NcHy5w9ba9BSau2AmxNKOC7nxQ0Zou5G1cuXBQJLMfERk64hDYpFX2Vq1P42zDQQn6ImoQKl7ldGD+QQvDM/16W9h1AJfVMMrPg80JrY6A656J6WCCdc3iRmqy5fEHSUuoz4zNB4J+h+KZpmtss+l2rcuVW/rwtKjtlVmQXto5ix1y7NDYNuOr3lxlo7tPTyF68KkFS3eQUBo2FzXWRAoARTDCzOrpeqKrt+G6vaXT8hhHcG7Nlv4s90uLwBHDpxEH17dwmUTJU2lAU66xNg0rpYu04LJ7xULatoyk1czUGJhAVVjUImmee0N+NMnegfe7Q1rsiLcnkRWXXCThcnEp4yeqI3Wdele4ggwt9HA1D4/PZwMk+7Fo+WRs+WtYY1JY1DTUWeNMJ/lZ0ST0yfD2f6Jq1MLOX+MvA9QIUTMtcp+gQbya/GDDMuqGA+onI/3x4smBOUN+vsSMfZiOb1uheD5MyWFrV+scehZHle2SPtWOFhxVbnz7wD5cdJAi7y/UyCKA9iqet/jQh7tr2BQoSQtxeeHCYIiZoWf+7LGmSRlfivEk7dZZOuaV4JT2ieRSk5pK+NZcXNVxy8PxXLSzjbMGNC0DNAcsT2ludQXClQ89zAcgC+OVtuMsD+A2C2XFr9FbBz7qkrVME+ZANQ/3vIi0viHevvMl1N0DywReCUQYpuOlBnN+wnrovBoZu/D9T8E7pNRJ3ZKQvU/oLvbElsxkN910DN/p7InWttq70Ow3LRX3S81TzLW5l6y0lSD9Do2QTTAqf217yXcivf9zWukDZPsyxBdEd2RdbXieZyJmR9rzCeX+rGPXHzuKEXyNj+rkp/HweH3h+uoHzhBuE+HjYJbgAzP7nWvv6j3Sk/l16sHTkRhg55IlKj0teOOyKetP1RP7P54u0wJ6VDkEYfuZZ/Q8ueYzCWQBehxAmTeCA8sxkSfL1iB/DmTKVhmI8NczeJHXN+Y1kB3jNOcSu9i4OFpUCfdgU78sD4GoPPHhOuQQkJYAfyn+PjuZX8ytvKV7OCANEw4Jw6erqA+e4RlMA4C5C3GhsmVR0tIjVOIn9owoXl+gjpnhvHidTtYLNyiWZyCDPpGXVrdokifNb6nuqHTchmsNrKGoMGIiXGjD+o26BliFWef6l7Bm7sgu0Yt44YvL4asFbPklet3tL7/nk+jWdtxSvZERG9yFlA+rAD4aKG7thq3mhdYGivQVADoCcSVrA6/N/opfCRcVbfRl6nmvly9vUy8gGSWhyPpzf//oaqNln3n5F6r1SlgnJ6guz2msX2RrDTYUX/wsIRMcBf9HQoELza+yX5G7LE1MLcqfu9UHERr5cVjhOHlvl3ee7wYZeJBe/SfXgJPi6QK8EO8Hypt2rHTtwLtj9wuAf7ePnkN8XFcylY+CYXJDTt4cJcu49hCz/9hHZx28FAJ67AeQE6JZKs+X/yZSbCu/dH/9877dM/XNleaMzjBndwtR3nJH03PHePNWzS3xzlK2Iry52UwbmRbeTFID38HvKDwENSTvnjKo+GANQeU9VZXpGZTh7QORmRnrewrb5iyOtk21tkc1H51QsHmxrVX61g4tB1VEYnKaP1VIeIgFdpXrlOzKxhPeHgxR1v/Uze/dbBmIKvXUVLqOtRh+ALxlvE63vwNmVs+OY5G2djHvVwjRz29+GwGc3GRi+NBaCmVsgUb2F1HMU5N7qXAZYV3fQZD5QfMBsOhyjOKhQblEoNysVB0BJi+cvfemW6v/ytKgS35CQZlaqIvP/ktQJl28p7lZVq47UIz1sqohRismhBqQUbzkKPFHSomopmcikaFZsUUomMo0yc0LSZkZKB4ZrhTEjjikYXPb/z0b/H+MpcOmzhJlicXjA/B6/KKGgyZdAr2guS0+sqAL/UrPL0+cXvh/jcRVkGrIEq9dETYPWGPuI1B+mNTanJ1RX+Yj4osIGkLI2VZbqh+xushQuFPmy+QA3TZbGBZO6p5aA4pTNrTBwlKR6WC1NySkp0WfgfVVLdIWRZka27M9/4s7KcpkdUMo5sw4KZrkiln2bzYHMTLeZByYXmt0zY8DsA+qDqamhx70JmzvNj4sdKSWsiubUMKseSLfPn3/JwDp23vx51jdCoJRknjfE5xT/FVDVQyy/SZIRcmpWaGITAyKGTOrmAo6EaZmmT0wieYtYsirEbX8/s1ZKSsuPdCAoaZN9wQhbxJ5aipICbzBlildhk0vBB39uWIAiT/1NttT1oIHgDBTF/UBWFNbaUe9WFIgcExFLT3lmuy3IalFUTWw62iBvcYcCcdH0oqw6R0Tl7cLvDcS5D2TBpG2nUsq5m7iKwe99q3YPnzFAjRsUnHVrcluOmL7zfqNn5rdfUBIKi3/EuF4xLQ8Kk7xU9fswjnAlqZMtEP8KltYWHv//cojp4QTP+jLWLDRxfDzGIa3gbAxhHVHmrlqA5ahghnmGQOLSEFhSDPlqaLNELBbx7/BFq8WSza475JgkzPS7JII3zG+08F4dXNxZ8CgHyrP9t0jgM8NXpmzl9zLulo/j6/Xo/pRgLAPB1ruogfn2jiIKQZx1tjRsK56i0DbbxbCk8kS6ZsS+PEWaGA3gAxkXbkvDFmH+JK5kJLEq7kc/2iQcukTTBD8uKy/lx1amZSuFW6W8sTBa/laVPpkHYV9p6jlvg0z+M6rqotPDydVW4iD4QeqW7NYbRoSMthyXNSPXW97SEJJXmmQCgTaGNgPLih1UaVRxIqO1MkGi+k+v/YEnGIuxOv2Z+W2tg/HWMu/kGlqCrzCf/UyzlC/Rl/OJcaEu/PJl8/j1L30R5uJqN5t8P1edjaPgxZ6ZQfO/65K8cbZAwGb3ry7iEr27f2MZrIMFz4XZrhr04xosrkO87rc96EB1a3dic5I/MH99bea7LJ3Yfxx/JWkHtzfi9Q9SmJmkhW+pdcT9+Wl1ELg5DD+Z9MouhYvqhqh4kpnCXskbWgTrBKHTQqAFUKLWjcC2fgCm0YD2JuvX1yGQe+yV5Of2N/j9DlvAb2vBsQgZB2cKPkgRaNka10YAVAwL0pY8A8coYmYQEZVvyMG9zF9q2FpBSumajaAZrKf+sfaAfxq/PTbRpwLTQQ9pj516rBf7YusLA9OuzPz72KuJzRG6wgirAUwuIF3IxFdJMfNjYtDOYgTAIK+3CG6tS/Kmdn9ZWemqGugnmpf3TysaXhKsqKDuHdvO334xP18NDxp0IlfwaPCXzOZZF0vufA0GxqejWxz7iIH+KldlpW75yPBwUVZFhZzf3r5Tv2faiaoqOLl/Pm3mz4rMVbl3Si5OgrvXBhL7z2ODmKFaefSeUgDgH/uyIfOuovXm52C82PadtzGp1Pu8/5Z+1Wb9ipp3uHnNJw2rWjck6DYLSkx7tbG0lA6gVd47ulI13Jx/jsEyRlT+yfN+JyRaX2TeBS/GSDlUSkFZ9633BdHEOfrN+lW3PDtONnPz3qlsHQKbU+iZ6/SLdLRs0BilhM4qVUrXeUq0kDJW6v6B6rQfFKQjCu+RxLkdSHsIeCcp5yXxpJT8Jje3V/XpsdDTyakJ4Y79mlS8JBw4X/o5bPeihY/KpscbMDARTgZl3NDvc1yISYDHL0lA+3CmKaslvq+3UQW/NTJeBUAtHAYyBbB75bcMElgR/Nxt5Cdf5HVxP8306aiyjgo1Ua8671Db4+ix2iwPBQ7BZum8lPoBI5zK/QCXz7j3/OFMa58cYlrBrdPZlCczLnRGYNGVW6sbsxI2KpjEi+6pvikuaa33xq8woIQh+8WlezTP2MmuPQxwvtVKBD6Wrl729yUGbolhRnlz5RIK20XZvHEjI2Tm5XQsk2ToG+6Ia+C8zNANsNlkkey30zLPNRsrY92iV6j8yHkO/whPkMmxZPlTEn+J6Vmn9jIvf0a7jXvMRmYMHS8MlXb5ZItOn2NmOmY1gj6e31clPs5n+9mqJa/jotrICvoN4stSqjOBR8uGmfYSqZqJz61mOndz2RUwi3R22J4a5EcYZzDZP8qcXBUBlBl814IZ8tZkCvuDKn3DFcHKew72Hw0gVAzm6V7TaEbwMk2XcK+MOSQWr8cZfuH5hQPRIXHnwRnrcNAyPo9d/DdVcgrKYl1WxZ3NFNNg3Yt2FBJU0pYS9+kkxfH6uRwcvV3yszC8s1XUURyuKgcTvNsz8PDfJUWNJf44o1J8VMCez1Yc/gilFW9lIdnzUidbTsea7+oz6wqiPURJvMKa7XboJbuB/BHsSu7KkVBId0wwOvKyTHKe5e7IVdCoz53jz92m8T2clND4WfNHkiSondhWHmg5wXzjAXd5ybdKnpfDiL+TylSB1BSNyD4Sjer1rtykjMRZ+vj3m6N4QflzZk9Z2ozfuwe/SJDc0yiaFOzxFW9zZrJzrr61p9PmcUvlYCxnNipNLIU0SnUrhokS0HBOE09wR9hEmGVPnnbB5uTzBT/ijDAWSsGvTsQ3KUNj9o/EA9AGidHwBzgUWtzsRr13cydycBWqeNo/yWzL0DtUrgAoTHL35OF9rmkni3Ej305tfh1etw6g4EssOb1z5vzRl5O6CyMFowF/NMN1XpA1tx5Vglm1WdY1bIwdOuBAAITdNmIxX5ZB+e7+CmFauGQNnvj+FkZKYV6rfX6zUyvO3b7oq7JCa6ACESlfs+oJTHXqOoNl2EizP//3N17cFWqyy7o/4n5EnXTaPa0trg6LduZap9RKuMxN1BubgzjYYiqbplE9rwjMmfAZPCWKw2WO2Qjxt6klKplUJWGdHx3VwI5F3HkQrVxCIbkviDkBLRcyVXMtqf2cmH4cUbBaOWAsQY4EnzkY7cjDLF3ZqXCK4rbmyvVn3qlVc778idakyLvKSEqlIspWXndpoI/9mNKq8FDr25oARS1SWUvbZRCWH8yrwPEgPGDN3tdNVFmT5YKB9xKa6TS1uF6P4uKA92onI7Z25onzeM1OolQG0jpCbgrrWtToRkjvB6upqqTKcp5zSBLPUIRq19IbmSB5xRTHbO4YVrNbwqlVJ10yOzK3ocROjt/gS+0wBqJE7FlaG6i9xaG+KzssarkxX12hzjfKU1A2K8uRqVjMm+TxUnlCRk1tDA/xGfu+bLgjgYkfTAvjv6zlzEYQym9Tix3xDjHr/OgWJ+xYzFn7GKsELKWRC4zCyE0/yuOPsXeyv82GIZG8YnK50sik8g2QSl6kz4zxiKHTrn9yynVuyRj9BS2tlbfO8TlifLuEJ55wK0ElwJpC1SeFknORv2AcID/1C6X7a/QzUbKt+8mf6so5M3mtBsYL6twY5LJb6Z4Q8yR2frAKgQFYAqUDdBf+0FvU7U0tFDjqrmAmB4t1W+WBAUuKwzhz5Bep9XHflcVgH1ZIVMB7j88XJ02+19GTpEn3bu1de/X06ocpg++ZOD3BylnVVbPy143uG+H6t0by1SorAK1vLtnQQunz8Q3r8yLXbI8n2thKGQIMYdx25XgRacqCvb3TA9N/sRlfyGoo4GdRrOzqHQ/Fds583qcWUx4YXlwOOGLyo5QHaooLASXiM+RGb2AwIxImaQ9Hydq8q1NcCdXATlPz1YlN8e9XM+VPZZY6eMgX8zYvgwe0J0O9XSkAnqI+0RFIH3ypKT4RFRdLaMZ+EUoRfXidu2DgYEZAJo33hkb8SQkAjlm7qZUkTtJCetk4Eo0XlTZT7x9e3PB8K7VokJuBx4VP16qIbCVdadp2cW/fzGLSgzXFhc8Xk8CvomSQ25GV/iii9O51pKkLXBlYTPvCdxV7fj/WSO3SQHVieccMOj5KK2ndzrb2qV8baYjx0P2d/l8T/qk9cXazNm3B/mnjYI72OP9u7ZovdrsD7qDbDRhodbZusfsrDpre0lMetN8pp8mtY4n2S9m5XW3d6czLHpkk+ht+rqiemKiueO85f9XTwypffFxcAFCdV6n1Uuv2xe9tXExpracLyQbeseS9DUspo9l6nKDUzevdO2PqvjnHSbUL3g6bth385xgJBj3hqvGdqgq51X01wAOCKa/rrT3sdH5SOsT7rsr8S7fnhPz+SWIyHS+fY7W2hatlCyDRWYqERwerxTp3TdCjaaWKFWPJcit49zU3OTLUX+DYWA49JiqhPd/a9klJYlP8l10MOuMgg6WOH/SF25D+TrdSlS5qxexoSyIzpkBCP9020hapnjUBAK8VPH5XT5Kvyvrg8b85nZ+UJDS47ih0sHKId23n3eG1BoLuUBUVP9Pwq6//X1NOkx9YbbXGSpOqZ01Uh0FFT83+4jdccV/yBXfi7gj4LyluM6BvXsz1g/o+zNWazbv4uvdEjF9m9fn9AQuvAYEV9fUfHjuNW+/3u8DI5BPRf67ATR+Z99xpf6Kxqr2YKXBvWd/fOknWvJNtnzqpzKuoX8hA/DmPqv26N+63hpL0epN5v5X4/9yRbyoqNuMYXCEYa4FWB35rHvpallFZ4y3OyxqfWkcRbmUhxOz4IFJMDdVIMDk88AAVUsQ6OnmOF5eVFVc4KCALU2Ix/Xxpdi9tTt4XbaNlCMoi+OqlJMtfFunjf5/4YFwKgdmiqApOthepPueBRktU8aUuQnGrChdSE2VBwZtGkldTFo1Q4ApZemJjblpgKhIB8D4M20bNPxYCEC9bMCNIYtuDXWQe2k0Sc4OgJkrmUYv0uj04n8MMDV7U0rp8yRYAINeOIxovFthyhIvbtqKh+uM/OO7S0YLqGSuzqwBAHff+zOA/tovxVexHk8B3cmDutnv2OF42otFFMg3+P/qeB/0QIB4qmI2zoyVopKHCr9/f5BeQeE+g9/gC8ag0oBlAWNcTfiLtZKa5X+97aH3ndNAjyEYPKhRSJCc84HxjmlK2kb+VS721BVyqRaw4qeENM9H/5mWjbnCHdfDxK5zuuPygLie5FSeFhWSWPzKnlOqb/nZ2sTu+P6bvS9RATYN3sJ/w/JZ+OjWmP95dnP12v5jaMDLHj4nCQpS19knW5Qfj3M5JMP6Eiug24h28cRZ/xMcZrHQvVboBr5eFNhnvc+qiQ6k+Hs3upWWkDpY+cZkgS0yj9Nv6Tz0Jk6TKcxYtJmZauFx039MzcvX/+xGpfSa5STyVWTPJNNDQHqFAaJ4ExIMOZSic3UFUnu6/oXOGlKEfkyrv+xftHm5jaKyLUKgfQHVsqCsxnkqCuHhkmAS/nmvcIr6oUseJV6MIhkJ2bOpNLooAEcmRVoSgUM8GNxdBKNLSiqRFmkEWzbdQGujN76MwDAbNhZhVXAZj5C8o1HJRmB2G1pSPRRd4fdTEpWwAYHMhB/ZXa0gLQq5xAAIzg6od2xX6gHT5mQtgLGH1ToUaClSJL5xeHpACPLQQXZtbSK3o/DKZUy6K+2ixxZcAvpGLC53D1JrcZUll78/GAn0SyBx0+YrHgSvXVHeJ8PC7FF1U7TKxlNW1Aq76eQ7tignyd0EMJsQyj/9ATek0XnRF5mfxTDHz5PxYo5qDfXqJHgCE7b+eyEH9BzUHcTq/XPyRhoSRVUaS3/wFBPNBcQlzg4P7MrMOIeuIDPbKFoJgucRGdFUI/6Qx4z72mKdWSgL3RIst4YhlAQ8nnfFkc8Wv/YT+JQBBBMbHvF/EuciXCvcwQ7HgEv8Sp/CGWCh3MQ94wHY9Rwc5zIs6uMhs1kGz/qMOfmS26+DUF9TDW8/5MjYXhXTT7HdOssHYIdP1t+ZzaKlPHudIB3a1nh2sagHsgentGy6dW3Xq6pV9s6YkHjkmYLG6SVrQ//H/EoqGovOza2fM3fL+OWrMmRN8Bl36ZAWxg2DRMR3nuAEex48UeX0yrTXHuHG2xEzRLza9UCd8E9fNF0pUF/cB2lpV96iSQ1L1dZJ6R1VX9e6f2w8os0YY26bBeNPW6QTmHoVzvA+OyE3ZAk35diqAVwYw0biib7KvjEwD5TiJrB3eWxpKcQpJ5JDcpXENJlJDOG9sCC9YMQz4rakkFkolU/9O3PzSCCSH5C2wJApRQuS3Z5kd/ua+9evWgaYviHfhm7GsmHVFgTW4XikF3/nFbqWCfe5TF2wSx1zgmouA5ridDAjhgBI6Nm9Pgco2be+ODmDbvA/7Fkz5g0DUkgEQBAxidW9/sSQBFCSFDWzyZyb6fAvtUuSfZD5IHpwouOqqJEWU4sIyTxXOxxUkN7jLyby10RmFmm0IjwtVbPrPdZD5GmeBlC973CGafL6kEkRYY7MrM0iRi3rV1VCP7RYaaaDbBgoJrPrga1yibBulNPw8yClVL5PQhcZKPayTc3nChIh6oTjrHTYSgXtAXHpW+10aZzk9ilNzqFTu1Si8pQ81wJ6DJ4zvwKap+DEiq2/xI5SEEBKiQTMlcU7awDFKsaEQEqDXvUXqL4/8IWX+quSlkPBSLRAn2F2Ea7vtpSrLJ1GlzPvayaldc6nej+8RqPNRPsZE1ufdv9FixpOs4zhYBl6ez3g8kKJguMVMsq+qOkl6IGb3TtPzW/ORfJ6bsf228dRO4bm/WDF7SdYBHHQjFo7sRBPi4S6MeQwl+oiEOu0zKYNJ+hd5xBMNx0qp28qDCfHFgZuqvV1P/pmSm3XQ/ePvCEEgXAslwdqHh7TDwixm4otGPG8T4BagodIqH1WFz8qPIc4hgDimIFvhM5HKPnTTpQmpZKdIjecSNFmRmxRF0kyHspoK4oNUuRPoneCCfALMcoVWfuiiiwkV8VSVzCJFnaHvX8p75aIKBZeqOOU+wikrVhKw/SQlYjICmvwLnVH19DQyU1ZckhiW82X2JbAs0RlCJe2SXIa4jN0LfvyYRw5HEMFzKV2fo2Pr9DarZTfYBKr3y8lKvsn0a/I1RTA6N28ulrREpV/Op98o2wzJgs6Q1Od95GA61SmnTSPLqPi/0yaqUw58zlf/RmKdklECns7UcbR6sAvRRbQu/IGGPFr4N9D8S2Ah4+oR3C4NORSEqf+rQgY+9ROLYrncIP58oa9J9P/gGF8tJBcgvZFKHcUggCcJl127lNfSlMaUePVjAUnWpxJpJBjnFHnFjfnhA5rwe/nj0rbZcxEG7HE0MXXnYwG/HSYx7VASlHbMpPHUITC3XIddNj7NRNNgEnzf5L3xOpfJ0sYYHe5I1MOfLhQP8nmDYuF0wNqAoZNhc0LeNivJ2QsiMafpKxB8ZQPYRhAyK25Cxach04R9TuPnJxDqmNbsxzFMYRhsk7omG6xvCh/66xxCzDeTL30pOy3fhDDPPJ4Y2HvTcErsIXjpSrY9CyFzeqeCFBLjurvrQguHDLFg7xCWKiW4I5tYTmIdUd2jyG8vBwkICiBgf4PLqJvSU3bKuYesWQi9fyTkiMCQCd3VdVFXrWTE+dgUIxjcxWbtvdw2PS9Qp+Nj0CHg3XbiU8Eh5cWIRUkOsR3LCTFemF0Y0PlwRYbBm0uVCA828Z4lEYOPhEEKjs0qw34h6oTIeoVlfkGR+2clgO621/eakqbQ3BME5lcHdblRY/5xYaJEhCa8dmXsj6LANm6fjPLaueV6QQngHjGEYhyeTJc7JVPF12dqUOXaQ1AS5E/Moo7TCJjuwxonvVyKkZ0vrVu+hzWsfFjawYtziqOA9i0GVGBQ30YUxUo+fkcV0BCq5zi56X1mWw3c04H6hp2jtvZm6YAdg4CUez4X5+M3ak7A3LnA6Z8gxz1UZBZjlMIY1j+GjDObJwKnaJJhKjKFI7hE4Jsm7SU4NsfCeUUkIJ2ucGAIVv3W+GQbTmId2GrViMijPqWqo6JBYtG5VVyvNnDquj1Xgln4SOTIdImD91WiytodZID9jMQ5471wzTON+O84E4wVMNmuKcQj91IHsH6YqwmiLwaOE+QRvzUye3BZ8AKaXpo/wCxhDlaEG5PvNZGy2ajb/fRMWhD8E2qRs/hINkqOMFmwLrQ1Ob+IVvrI10tFtDz4B6FBssUIWi8HCWk2OfBtMSPW5hWtereSI+HwObh6BwwlmvUCeCrc9yBJywEQ1SDPDo1CrC2VCqMVIjOVk1WDe6H4/KgzGJYIQxt58HDYVydFaDFyN4bFwitC0YhT+hFTuglBb2ObfUQDArOobwSF2bdElDM1DAySESWkdEq7JaLOauPioDtDkEZ/bfCkVGLwVQtm20nalBUURhHsF16oi43Ecnhbi4GqBmPSiikMnrdYUO0zSKQng7WaMlRovO5eHicSG6tb6MuDwBJnp+g7bK7OPB7L5aADvuf0ccZ+yf7Rrp4ieQzVQO/pScD1csY4fT+jAxzc8Vizbm59LoQQU06LoFL0S886rHZ8mg2W0cu6ZBn9V2q7HyeRR/+ZT1XaN8DNinDAxC5xU6lf91WQiInqUX44GzrMj26S9Ku9DeD8zuEgtVvvf3E65NF/5tPkdtnmJ0faqR9v/cnzAR1xBXE4Erx/sn+vUmUa+cWZtvsAf3zPT5/vsPnHz8i7RIMoaGDAaLA/ygWyCsr8Lw+wPVkQkA8Bnf35ACfOjUBr7qvPO58V4O0hklw/pa6YQKX942qbevzl2tBA5ANiy5uwBBzlcwpe9vT2KIWVFVFY/UML37B72p+n9fzWKJkf4SL07XM2OMsz20spPNREJjN+9l7YWjpd0EjmUfS1pbQq83M4B6VtI8QfHFzAXitbWpll5YTrfsQ0gf/V7dBxYUpScBuaIal7LtNg28QmEcJitk8WIUKwNF9mstONQQSXpzGGlOuLYqWug+kXEYyzkk5QAKPIPyTPgY2uw9RhsQBCzEb/+iEtJ4XSt8lxQ6rMCpMveYqK0MhqLWq8ZEvQWCRwpwTc/G4xBkBzuTfb0wAPZKPZz1AjZh+N7wyTy2Erd1ZqpbC/QMhBt1p494ODZ2TkvSOE4E3eCwDGx/qx6NK6pp19M0TsR6aI0cb38JAFNwII4s94thhkcx7yNC5ScKSAR483wZisbSvSuGdVd3yM2brROi5FNn2fCC8R1zRuyw05TmWY572ffwzrClPTYQth67tizstUVPsQoAEIeRtH1g8/kmjdxHzmHiFQGZSYWHqBlku6DxWfDBEpTO6sUhVRTB+txwk29pyMgat1Nq1H2G0ygK2Ohf1FuEOXvP4f1tYYTN/Yu7SMlmfUgvz25awIRSUZuMuKkThfIfOKyitw9eLO3C7HEvg1F3XAL5g2XXUsJUtLdgOUTS99sg9VW9IU+tgSW3yqzTbFRP8yCWWUQvMG3fjYkol4Gm72MhSj2bYWHdtL+NlqgO/JM8Ke1GzNHHIM3oLSU+CttJYSV35JyT8p/bhPYgvZX2SyNHYYsmYAB+wNQ6lMi9kKMZkj4EfZi4AKN8k9t/OJkisxqeclLhqxfIMhsJ1gTGC6rrKCYIjb6HwxCnCR0i0oewg2eVrj2dy5Z+nG9Dw+WQWZpvpN7NlcvZs1ZQf9AQ8nhqV7t2pJgiL/XaLfBl5JYWsqmOA7yt7SG8m4nKsiIpH7U7hHUo+5dJ5gn73ILfrYTpK65WsQCM08hsMl0k4KyDNqGEsggrJua9biWNaSde0ixzSUHS7QMek4lnjjJsDQnYEq0InL8qk3mQ2i8qSwgZ2JyR7BreQbLHkyq6yl4/HcrhQ33tKZOrHtw4kx17OizSyUE7O7YsMZ/5K9hUJTPnBWUGhkFoUFPnRFqOTn1ymp+KoNdHbpr6ndIdWP7iGX0FTPoHrmvb9avnFwCVmarkv8MewAnuyKGdmVnvgVsk8NqOjZ4tTmxejynC5dTow2Bwg3kwILt2JLdHyNKvpvpDfYLkkeZUiy82PqYYmg1JTkIAwjSWDGv/L1c28uNSvZ1XpRD5m8Z5o00vOtmP60K9uTul+nehO5/uvyDykJuisJ9bwnwLnU2btDy0j4iey6jmJrCxCGeftHP8R8M2LhfjIXtoSOGl61vUxFkBSrN9LVWWjCfB7qqKDKmYkTDZhePcsTH0f9JaDVid51x+pQik1YMxJKQiNeXOjaLKxjpVZabKQ9HuCpx8r6duXSdXSNUQtJAkBIMsrTKovyHdR6fzDElBfnUL1yjl/IZaiaMkbTFNPm67vowJ1HVlc4WKNNeorOmTuZpH5AEnJ+FTFzRy9DVIWtkHQSobLyHhYq8dxPTfcwbkOSKGTscr55szl2X6cEeNZhKZOe5kKVzVjCFTgJKue2MXgxTLdhyZn8uV7CNM3VUB7sattvfI/xTmGbTZZg1xeMWsTvyxTviEXvKKQvA9H1HDKjzvV43itlCrnXhfI9bUBPEcKKjexLEFBQmSQIFWlK2gPLVSGOHsbWVcMRmuo+CpJQnIKAJ1P6c2eqPdjPbLsmEST+9E9szaj1D4Fsm0o2qlOD1wybCi8Lf2MzXDPGNt2Ik34MhDcsXotNZvQaCsu7nuZBMU2LE7CEr+AkqNQqcgFMxw7JjvmqLE1Ig6bZ8MY8WnbeW/C0elb/+4BI/JJMcQlseCDFHhNcw6gMIQatLFaNINJvHkTDjpiz3HpW/q5ZF3xpdhR5unluHHHpbfR3+mun45RUhqgYvIrXr4ohKXt38492laNpwfY4RaDz2aG9WjQJyu8HwzoMpcpdeViM+GJSS8y7R0B2fL1Epi+Rkh8ObACliq9blawt1OUlWeEYKYUImAfMUOwLL6lICt9SRtsgKwKhyPuZJhUCtlffWomkSLnJpp/jmJ8YrsaGtimFvD+bjn/XFmxIK00qjiTR/65k6Z27+hRR1NUJPFNvb3VhvLBr1cySDVQ/STQL+WHz19HjCvO8GJRUQwmSlzsg+kPLvA/xXkAy0n59ydAiN9JTZbKaKrLjexcEQTlXRitZhYzvlWhdMiSinvdpIxLIFCUsBQvWaJp5PCKBgEWfSLZEhSY4xXksOr+HMfeXD8ozp6mccFUUGpPvSTLk0wrC6DRti+3vG2QuGbnlPyriB8xhCDgPkcOciNpVMSRhZAvzSKn04Y65Zb54bVgv2XHcDkVfKFuHlaFftl611piZxS+taF/0s5NHzof3l+GLtVhVzBdmeRJLzLJayjWYHFHAIDgwXkZweH4eUHpL5IEyHV5qYdXnyVnzBoyqFDl0Df7GiQEyQV9Uj5DCQjrVHkOSN25WorWRwkWlT/v4hlhS4jxh0543/iCgpK4wDmHsN//iNVwrjKvCnPsbyPdHM4r4au5vXO9NkJ2PyVG5RJTysN+rZ0k6ny+sskSCPAu9D/Q/19J+fBkmBFf3CbSk0Q65yHWhMkQ/q31XXTWH+JBAIDs0KUKgp8G6d60/YxU+Ajr0sRO0FzDg91UKENvxrywPwEb0UEHSpB/bNSRRL6/QN3JtVJRNhzs/dpah/z3OVPwJdqbnrsyzW7Hl4RZt3qmIhznV2zGYjTE9rWtedVKgzFNanCCTgYSy4H+uzRiBobmBkT4qxkX3BEu9Jqp66/IuTK5eWOvZpevYvCDr+WBfUUUQJDUY84gKqz8RzGahJ+78/tZprvt9RspWXDSGPrStgyB/UHKiowRBuL49cwk25Zogpvrxuw9hOdAx1Q0ABFFHY/p6I/tDKvloF0YxHhLC6ovbqEdNiKu/kJ8Wbyl10imhvxSxDFH4w8BIztDHH4FWzMvlqkgjpf76nLTNOEXAAWH4S06tR4aRCT27aLAa1Ql6+YAdqiEEgQgPmNGCRH5zxvUrwESaq9DwmiC0dUgaLbuoJk6r8mqt74RhJ13chdSj0Cua22r5HfHEVytM7xu1tziWP3kDoDO/yfIuVraujIZuvAJTjB9kdcUj1POQJEEFidNlqobNRUUQd6BksHg9O6HkiIABAlT6zTwSs5bPb46BQnCgW2sT6b/XQW+vjidIBWPyn20/g/Cik/mbEAITSpW8X8EzAoYMxcpYJa+shJ5AB1BZdRCas06BZnWrS9jMkZ+uoNk3IyWfKn46napfoKX9TZJUXD8lJ6iCXiGg3V79/9K6E85dSMKI6qlY6vS2OtqUyU34PBKNeVytQsaN1v1fFOWTszlXRo2O+OG1jeiY3gkRpX3rUM9qS2xpLDfKwespOyoJCjbjsDtf5QtZGp9n6ZDXhBuDWRlsNzRjUv8IhIFRms257yxx5c/rL/R+EC0f0OP718t3hePt/9ksHRZbyFcptsH3XijdrC3SbeUtsDJwbe1Ay0pwN1lS+iBo3soZZKT8BKUjd/bRFVzCh1q/ZDmDENSHevnJbxMDK/HOPYNlckvhSAe7AJWLsrRFP6yVquJGANhR/JMDoqeSB0TPmaqKsjQfy3Uub11BonCMfapBX8DjVw6tw2lZpY8d+z80vY97PYShpyojnAoQhtfXa11fd/m97BJ2VOIO5LrTZxsbFajm1tyhCK//oGJCJYjNMRCuPtSayhYk5gjPYSPT7gORH8Jg6WdFMROKN2HkRocbBhPJvkutiVTUVvGihGnlN3twq0sCvBxNX84+cuiIYG69WVXSZMBqo964hXWxXL+YKa1VGos8u5BhLv0BbVHqCQgRfYrjSkyB2aslhXBOWTERK89XQRtRKYjpBrKV+YbEmNJI6Uy4kE0cexlyhQRF/jRXzK3Pg8XFnuVHeZJLMp6kjLUhxkeOeK59B0w5WXn5rdnG3k93L8xCAASfedOr3vmkMT1Kpm8E7LDcQJ1q4tDSxZgm6xEThhNqsTyYVVwbV7t8Y62nU14bn1RT3jvUWqt95Qr2wYCYn4KeBaKfPb5pK+lale5cs9A4O1VFHcsn7vwb2AfIGVEWmRJpnDHbzjhjQnQCTNpgXF++ZtAvfXOJai5O4OyJv5CH3yiD8oBdn1xWMrWzJKki0SOZFHM9bCLAdPr5yUV1SarXL5gKNroABM4CHg0GnF3TyzCydESzOTvOFFhl5i5YvOiYzY2ZHT0L1ba8A/a01YLa8tzYN9aRUvxZSrthWsp+/R/zazqcYa1j2Rk3vXTQKYl+sdvw6IHN6dKpceFECMCF2H/9w819BHSLK2I21n+JrqXMjfPu9o5wyjNyObXlmFRxHKMwBCszAtBxAD0eU/YIntEVU3r21WIdXI/E8OV9UgWp7p0ysW6bhbYj2b1pwPdJIAXNug1ADhRQCg1wohs5aVhybU905ztcYKkhemfcm6q68O7cMlZYt2Ak3lG+Zg3QGAfTtazjOq1el2XVGCcy4cER6eRPjDj9tH4JnCH2/796QJ3RtaRi99oFZfSe1uwlU8uockx1QATlkTIZOhwAXQ0dIX1giilFkjrqaC1FIdnX7vCGVRgM5+SwtEUr69/+Igk9M/PrWkibb+y26HNnPFimQUeGyyAA1X/16Deojcw3W7tUkDSr0Uer7F+Xtip+tgKAwxCiom6bEW9GoTw4UpGU6v28uRbWE3s1GbBQ2NIUIsX593whwhz/QzAU9X3TWshpOdNtvh18OQoxGDGhOadmskCcsws9vgxi0HLkdlU0kVXc/pQHL00etiYEQk1A2kMDyn2qoQxOKV5WrAO14WJdNc1Q71NAHqqBAz6HvDb5VPoeckrHz2kYOkLfxTGxeZFF3tG5DLzYMY/qpiAhBOv4Ef0Ir+e8DxtdHV742DEBOT+CkogrCqDTHwdnCGkkxY24WRzWD4vxfFbEsuAP0lrINtLogjL2FzTBgjP9dtcBMoZWXFKxBu/b7Q+WzI3og4BYvkYyQxAwvKzXytZGNt+NYQf66HCW/6rDC+2qrWBsDNxUY0NMFVFHwosjgn+MkROn+PKIjVQyOjMTo7z58DcXKnC6h7x4FSJHMN5ST7zFur4NwSUhUnBZSzNUEyxpadx4xYyhjXd8I/H6pinm+Ni2gXSSxIxSR11Oe/18bDnv6fJN0FDwXTaaHuxTdeSWZ7/g5Oj8Lsnsw7RMCGndkd4t37AaLap7w2IuhBFGUzqxC8O1VFj7ED83HBefX7YygtefSWs3oAtbNKNLyZgK50BZTF58URpJxtfxgKvIwsmrdOyle1vQlCYhO5ZrNsXl0wekoJBUT1hB4Rs5RGhUpn+/yh+yrW3XVybKsnH5veUbDi0bizrgHOL4IK0j/5B9DLrnHLE1+OEOmEnUkQjF8+KR7f3Irme7O9GfiwScVtQgeYFdjSQri7+7nk/1LC7yApSCfA1q0LLabWQUPMWGMJrr959X+YP2tS0knc70ADRgUfzeLW2Rh+FLakgr29Mkq83q/N7y9YfaKu6Gi4M7kgMeJoDAiUGYcKF4FC+KR7V57GPBgMP2yoUcCxOnBZ2fpoquu260Et4rp6IGNcnw6NoLC4JttsOWGIAHLAqvP4ReOw8S0jsqT1Kg63yu1U5VA6ntBQfloUUugTDMUe1edPSx05tRPN/DfLg+tAdgTnIaYRR2CLBhm9q/OPho6aMiEdf+bHc35ux1pO3xtykLM/PzfXINLwsgCKGItEaq74QVZGqd5KBockJoryoYdqzt1Z2dqWYlVR040NOydYlVi1p5noZuNysy/dA7o/h6nCG7Xkxvr9pXvbRi4VZ8Dc1QzJfTOq+ndVXf1X8AZ0mW+n9sITT15VmHtm3LlZioNqWs+ovIfkTzBpLGPOtLXfzwiDJRUtP/+lVr+vtIbnRFYIZ+zHcv6bW8TNETw4kHjO2l8iYmhc4CEZ+uMVM8u3fRxrYXqQHCh5hSUuHIbhemRKdfPrDYc2NNDNX5YkZHVeHWDcFqE9XxorfLy83orK5eWjl8yGbE0BwHbrajjIliKM4r5RNQuaPv/IpDOkGbmWJfSVcwDx+Jn/csL2jAx7ztSxt970VWrUXO+Os/o+nvo/iZ4csxmE3yf2iXiAX2FBm/Dbg0gALaZ8IsCgk+xMHQpMTQWVUg4pzX+Wa3aKKi0lN66CWqn4il4PBp74fL15UvD1iBmIcBcovec3x10eCh4KZhr+kquVTNWe8r6Y0uWbraTHFcyejK4KY7UJ+k3r83Gp74rPby0efavku3r6S2LV9J7n3+V4m5/1TzxHbPiwGYpwLE6yo1YeNPOI+U5k+AyAoL99eXCX7RuYTA1E/+t2tNXyXzucrl7WmQ3M8DS+++n7m2ASfTP8v5iIJdw8gYBFtfBX1eBK2wTXr+XNKrlmnHE8TLjjhe7GAvZHpPVw7QL9/lKsxbzaiSk14ztddYE0ejmVlbFaKU928EMYL8EDU52l+0H10mStSOA9Nybu7u8UecmlSVVDkaP+N6PEIphHBfa5xCsUk4BkquSktTV30DTtwJm+Uavm8GGxMIF745rlfOB78WfBBZGaulxcYujJ/oABw2wIi67PeNr2mU0rNsouSvNKnfTBDSs0r3Fy5yCDZOnKmXrtXKt20Cwq1UPdebp5/gctcKpkg5tIT6JuPBAQgRGByt69ZfY3I9VuuSD99jwgtxfhdGrM71F1O7dS0O2JGkbh7lN5eAY+rr/218Yx8HIna5Nqzv1iMc8fJJzRF1N9esIGPHALeiTMHtgQCE0A2JNKPuNCu+UCmMr1t/OEwAEkH+RwIU9fWuGCtOa2bvmucCbMrYN1Qt5uyOltyMQn86v7D6iS4vkbsrcwXD9hVfpSVMUhhSWlc9IHATew41k6VoXbx7wfkTSiHHyhaKIK+xJk3AtrJTbQH3PYe1MQ9ro6+NJK8+/dYYZdhLNN2jKxe1Ta29Nmxt0cb4qsZ3sJXUw262nlMNj66+3IivPtMnnfBaC+HF1uRLE4T+6VfIq0e+SQ8Ppf8gm4Jicp/s+3zOqvLVtqsnpVXUIf36VhhvvvppwTq0Sl03dEWAPCi2+oVbQPLgWKr6spJ7zbhQwOzSHPkh6juDq37YL7p2U/3ntSsGdrUXdA+zm+tp046tGVi1563dXW1fHltfn1BEKALv2391u312n9th16ovBxyw+8T//z224sTShSs371pZNjZ6eHSsPN1vSXCP6XibqZ4jSkQEEz/+2hzjE3fcfLWDoWbHhLZm2Da9fGPy4IlFk1deXDj6ynb+cAJpcIryJoX1OXx0cPj1N6cNnvznn2TVResjK+gEPIK43wFN0AywhNN+Xynrn8xPpT1KkvnHdkR+Skl/jmLLC2Vb+iqWyye4/P/+2DDru4SK9p30f305uGIqxURqkuAVmYNDCNXyy0o37xzDGVX0y1VCguOUjSjvAsafawwl+1xfFcWsK+z/wwPJ0/+raKoIN5THnPVEkvKj0VJJ3Kmyr6a1fTGtOuPSY9LzE/8XeTe2MPY97MW2T16L32qzb42PYex+acus5ujMXa2yN4H08UMlPSOT38A3xdATf8gtSU6q6uTPEzSmNgRfRR5NpPjGyjbOlu5/7tJTET3xdaXUxMb7BCqPVtfT0wqZ3FbipMfXUB6PS+5hVTeIlKI2vimGrfxWyTaZ+G3x/4NKqhkjlJbhydul74pl4YtJCbcXKFIyKh12etcYiYyn/Drz0joguL3OudaUKPCJM6wH7Hska5xzulMnut26nuWRNQrTSNAWHEVGPT9rmoqTyFEJnqserM4azFycsmcQXL69nU+OeZFxQ3sxhux6wDlsSA0zBhqlGV7LnPJBPm7+fCX6jNPH6d9EkF0dfI6MRornD/yQQPUFKL2Zg87hbI5dPmAy8YiHzBdjaM4dPHrMi8yfTJ6FKBv609jJWcTm2MYN+uVV3BhzipeRZnHOM1yl9hnNAOqY1pOX8iWvLrr+5e6UccZrhGcExRe5/T98YTsNqF9+OfP+C6cCJAtBzEgL6WvPDMvVisqBmT6mI/HksqKfvgFUeWET0bAsvZRTyCrVfujY552PD/9WKmlBX+EEIqWapOHjqaL74mFBFjn1iHfzTOqgjbmlbWsqnwkN1Mo54M2ZeIYpvXtPw4iHH/5a+fd/wkJBFrnzEJx8Qsd/HkjakhVpV7OdYNkYQn/b804DhK9M/ejx4dbnRM8LzFTX+/6++lf7vpM5KTGEV9hwyb0UgLX2pdSPn1L5PyAw7ucanL5Ox9/0Jx1MjIxJBK4Yjz2WPG0c7/NnXvP3dWjQdneN3HoAexm2H/cAzNX1Ydr8+IGO+xOFeTWUvzuijtRplJrHuY8GkmrMUT3bpcaNVEpAfn9gHDchsLr1uigg4kesEdtJD1nlrxLkeGagL3pVsXtMtDtvAfizQ+t3BDs9dNlzvBUfrGOoREgGgHJ3lXsaFu7JU5pDgMJ7gqgNouYOXNLy5giYMmZEhGh/kXa+yJqg0jncep3T/mxiZEoWp+ng8FCa64q/z5+3Ru1spGwnFL9MMlXXE0EAZ6ePjgmXCQBNjlf/72g12q4S2KDiEaEZqk0AdVAE05s+PCKDwiLtUkkX1cdO2sPIm+XO18D+E5iK5qliLI4AlRWxd8K14N//xEiHS+j58WasnS+0OfkCm1uvc5nvNbQrI9A+M9QHfx5M3nyNnOTSrsA2f9KhrxKER4RF8u6+NgA/KnzDvA0+3qPTtkHHi24lZhnvGAzlZ9S8p9fX2wgtUTa88UcHNMwjpbgAZ6nlnKqBYa6+cMJAVFmuQ/OTR+TarZq0fUqsAZrrFf4u9NdMiU/GKbX9/1Hnj7dMhnM8epfp8Y4vnQ30tUdFqqqERZQnD+2jud7vueFPFtMcpILzjxLULS5fbxO0VN345PTfkf6eGLrrMemcJ5lSC8z2+5UieF3m7DWw75tC7mzhm0k1vCtJgTlH/mKt4H+9AO/4ZtwP14MV2nRN/lg6S0oNp7TZenWbLbqs8dwJCRtJvo3EoTFOBpoO38maEiP98jmmVQzEO5Kim6qtm7tIXs3tJgC6biKK/B9LtXVjuW67cxRnHapJkuoOq4kWkuws19LBVKHUsnkzYxyf8GcyFU3TJ5v09Nyh8mG6qurqFN0gg4mQa1mX5f3f2V9RhbBHUV9FiDo+t93ouCqYZOiqqk3jC5hlQlvG3CLOEXPxuUxVzBHUHTypN8aVOunQGP9H/Dw2e1G+TaENkazWKQkKi9vIdJTLLSBxMkFJz86tipJOF0H82+f5rZm/xABM+rrEGLsiaJNTVQq51L2BKEO3FTE9h3lofaFs5S/0U6JlF8NNJu1Kmafk+49jfCzPLp76HOv1XqU57/aogrGaO6r+5X5rMHWBM4YDTYMAphvBsGV+YNhjUfnsTI/9X4POF1s5f/oxN+FWIVN3oBjUqtqaBWQ0rZrXmP+T3y6RRsnPDz7k+madUe93SgteWO1eGcwai43JHvz9oGYOnElmsq5v6N55V6t7/y0Q2xETt+hRFetXAeB/giJn1aXoYHqMm5WMZrvhR7qIlcXeKRUL3jfUpwE81A2PC+ibK/X0Ah/bVKZUg/FuaE+RWBbxZS++Ux5RlsgSAFkSLkgmQtIE6ahglBP/eQLo+dROWevGG+KN/SNG6vW5PLo0tFc2vvRTrkRRnVOobgJ2VqlTEowToL19xssfS0XyslCWskaSKtxlTyVYJZ06NOx4gP8ciU98lfmXiPvY/WoDQEbX/DA9NVmeLRTJslLSR38oCqhL3OqS3B0aV7lL44yLDXwapsZJgRRqHKiL2OXl4RxlrSTlMTeDJwlZBGgfrwF6uqrDaVQlWh1pBKsbqECJ/mV9/Zr1Khd5v41PShEI+6bUiTsEGrzDxp4ByLiXpSu3p/AS3ibvsrHhr667iC+wsdN/JvKSt4nPWY68YAMei2aCvshv0cyi3wNYs/kqZppLjv3rFqJ6J/Pu3UUQ+Cp+MEhSMMtSzSAqBMnwZSYZlxtYnEUjFjoqFL9nluLZca6DcDbqk+M2VtWICJW9F08WP3h6xThB7DzUivfyVete5HQqPOEu+YkV2QlF5cTCx0irDm9uobCAKFRPmiJvWJLL+LtiY2DVH+/JicV3zW1Fx0J76+Yg93PtePrHVgWrRBkvHq81y//9rZDetVSCQqbXU9UxWE4F/NtxM43buiVFdSWs0J7CBYL+cpaUlPvneJFrJBq66h7JiSUuH3lvmiJrxWDOsopmYLfC/C2Tz7KbDZJ+niPPp8l8WbEs0525HhItZiHnvtRRoNQvBySkL4c4orc357eXz0yWclPBZjRnQyO3IJJvWhEl3rGwaxZeb42iU6lQdlXXKHsUr9RuYE/Nn4MiE47shke91jGOpdhlPqK8bh7ALR3s3yHIchfwPRfHRLS3G7Ika0jqsRkQsF9HY2L43z+FJ1QSzbT8WTbVJxj8LZUSxdGGghf/zIaGlZIDRhgYTkLuhDRKQ35IndUI8t99g1hfnjnETzUvNizDCC2vmwaB197UnZtBAcZByjFBMGsbcg8WUUYFdEYha3LXkOGZq4FCr7ZnvpnG6mT0KPZ5AKSEOH5UCrTBhORvNfLgJVKoLz2987mY2E4WRu1Z3ciXB1Gw0vafKwoSVz2tqTwgodKC1dzC2vwA02FZI7S9af+s9pb9KmiwCbXGlpSGwFYBV152heJa7Qb/xk+wY42Bh+dslzgmpkCLNq/s+soZSCruSdhp5B441en6SPbanHCDh3X2UyKsOpUfr/MrgbMIFnhJ1wLBenj73BX57ZeOYnycpyZv3kTth/bXrLY37aD/8v6evNRf4Y5IxglXUb3TE/bk13rIzy13khXXr5BTXApfFIG3K90l83LmebtAp7cTcJpb63Da4mZX8RRka1JQ5yZPG7RXue7FiR8+flCm6hrKpdgHBOi12efOfN7NMCvsqPqKj+tw5PQ+BZpBGYNWrmvPgVlh1AaTvptai+Bk0DgT7+C5rqRRVQrNoZCZ72ec2lVFwWCbRFMfS28viEnZQcItHwYuGyunRFr7ImjG8EsoGxCozUGESNJ0h/EdXxO4puezb/+Wq5XvVmGByDBYhuHl99xHdnNFV2FYnSLkNLyAoKifm+SJNTbOIDOGzM3C4wRZXr5rs1Hg5lHvPpHGjoH6Y5sYhr5hS8pa7mxqH4nTtNKNTIehP/SRs+9/q2i9TJwW91rtQIiqqLdHD7Z3kjA4AJY2q3fvZ7l/FmDDXyDgEYQHSY+ZSal3LDG5eliy/IxJNnOFw8be0JC69jm8NFlB2WVxj3LZB7XT9RBRHBuLHlxZSOEnbQ2tVWdmb3pmTFav/qeshHkJwydC3RVJ3p8BvnkEFb2S4X8s5ig1QyZAHASEVXOKSsAR0Mfva1uN+c5CY+bjfM5Pso2DX01LptWrnPQp9m8k6LI0uh/ILsURANnmCMfLRgspxLDYx9qiESn7kLNSNXpfpPEla5UvyDCQuG8/Q45m86DeGHWYzKBBXt42/9y9GOu/Nm0bEHBcOOUrDx+kpqXSp8pSzDBxWwil0NpE0UolTbzJpJebvhiaegfnVszEOZgKRF1pEU2Yom4+gVk9OCpfJI6YThIgZZI6qHzcRwR3odGRIrFECDMY02mMKXFlGVgx/yAq6b7xhvkU7wdC3rcQAmnw7hiW44hAn6fOQitFqaCuncNy/0ACIaTR0edqNlQEKdQPpll7rp/blWDodZfWRQNpEy82uBdWXQ8r7R/3krJG1E4WX2usFk06KMqwxXOx4XrDkIlihWhtmTJBW7Zy3yvoz0jnClF0ZRtXiw4rT7NYt9lIGcu6iOg8lC4/lcClBJ9l+cJWZalgCfZcNyi+qLzCVMa2mkacsi5uKVWJen6gClU61S0lvKNU6K+Z5ueyR/SJedm18jLb35vFEmhRo8+XO6bEa4r7U+eyQsmWvFDGYdHJ1vJfXODzuwIBdYF0/JrgWuvmWoJ+30v36iu9ows7TIsIvGUZ+5+3/5tHje+hUl5TcczL8hSu9oUrauqGFrXJfka0Jx6mb0GO0QTyU1z74JL66oXL2115iuVmjuqaUpUm59TwwHcQCrWaOiZHfFX1373xHN0curn1bMKNY+QFzksUPXSRclWaJL8hXZDekCd9DzTA3cTqFhnKOO11FHaJsjSd/QY2hBGB+bUuuDKDU26ohInLS6oxuZHBZUprbuENrFNVJqmUSKnxRYl5Tc5U7ERLozatOhvu4GPmfFNcHA6f7C7Avju6M7FNJm1NbJXK5n0r27BWB9tr8W0Ah1g/eTnSiKCRb0d20THsXkLHpl2HnbWosWcxx10EopUAcLtJdFO+6xAHGudeUzOoV+WAE5k7WuDNsVqUITEBoT7uvoBr+Q2w5RPJFQSe6oUBSQ1QIhjn7XkZkNVNi2fD+E7CEtbEkgwgXq1kxgOxLBE34cQz54sXr6YLcGGHHcHAJO3mCesUd/gunkALHLkIZRViMlq+lDGglpPVrmEofyIzS2A4ABCWhwOKSJOewsyf3R3Cax+iEDDhqxCGIwAC7JNNHh/X6/SEU+vWrosbABaaeUEQet6t2S0uIO9Va5Fu0lfMjd9WVf6jqST+GgPwSgeQ3BXP/VIZ0m99nl1TVbkOfT1XfyUehs7M6IshfrMHoZioBpo8ynhCgWC8eH77EfSwcCZTKiYTWLJTQG+EAP2b5BL2WjqKsgBsji7LtLULe+MdRFYZ3e3UHKJUB/lkA43QtS9YgnG3goRxshPwcagUTtB/wSMt0VhetP78+taSyLrpVmau31Ao8MFR7usryuRjSQVcSq3zP0+wxr0qb++Gt269yhwKAdzvlv+T0gheGOAtDZSXuMaNWIyHPlt5PPMPBWIQ0MXaGeDa+i1Bb/hRmc/iHRIoMNxqc1Me9JKrFz2VxLh0Kfn5HeCSDnny9gFA+uw2F86ujyXi8DEWvCCDmUyS+s8W+uByKhzPZqFSeOn/GV0tWKwbNimFe9mK3ZaNb/3wijrxAl9BmdHNtqRotWfADo4Bb4dS2xEnrlG3hOVHViZ47YVZhY9LM6z+QQEzpG6vK2if0kDR+5wnUgwQvEP/V8yBKor+ODuC9F/+VCJ3TAWkiagUfm1AGmlpSeZH88+vb50HZ9fHsUX4fJvdY2WL4vz6aTpW5/maTFGERbMh7R8aWfCT251v+FRGdwtiserwEw8EYLkpPxNvRXJHPv/LAFv2ZMbcDh7JeJaHppg7SKuDq8z3lQbKSz3tYhjSZiuPeyhxSqaUQHZiT5JukjQYLehiFyndXNvcOL8vInPAl4UhbqkK2qc2CjVami0QhvsSZFB54b3Tw1jfdlpcQuo/L65rGMH6fB3FxsQaXobKCr4FWRxeHUVTeE+Lwi3fhpsz2CJsfVWPhSMSB4EeTOrEdnQkTNMxk4QUDM3AjGtIGl+8M+EOjsbf47evK2iGcQqpyMY6WNrTguBAoy1aM1bDjDce7tZYxRCeBmzVL3V3aYt90rgLNO0BjmrJstS4f59FU90JhgbKn8PBjf1lDOq/sjFdnuAI1Lvrj9RBc7LZ8qZDVcV/wQ+ohEX70tOk3PzOqRoomv/Na5D2aTeANOkp0D1UO+UtLU/HFq6Fsxri2CKhV99jOaXBM04k64a+nzmgy6NEEjOuK4UjzQkRTjO6Mgq8PVHEdDFqK1rWLMnny/RGepqReA3R7JweYBtfP+eTECgH2mkevqGebNLOfr7SYEWxkWdS6HqNBBmUaEauLcE0lmyL1Tnt5tWR+d/HVq6qP87nzzT7vkMCRjSVrHcE+gGJICugysKPrzla8USGGURkpMeunQqjMYr0XU6PTUjdcYoyi/ZtfxIfN+U5KBq6SRsaSvosa3g0CJj+FhwO3gX7DKivsFKXFufXTY2bKqjq5NJSF4LaJ/40iTGlYMi1ak4bzHxJwSMqsuy7Vfzo7/WTwhOiWKJbMaYwFKR7faKr8CLrIz2ZMPHWmpDAjPHYciP+YeuQQPFj/RrABitysLLo2Y3Ys3FUbVaLxq19/MxudC1+f+RxiXcmQQY4Cdl/SJYfICm3oF07y8YXVXuA/g1AT7gJ7a7sRtJu+X67cxdC4eDXrwyR+ix9IkWhgB4wmB2qeXnPYVdEvgs1URiQctUapqu4Eg3tFp602ckk32oHpyKPtudWKVazOXy/61dHswZqtial1ng3uyLySVgLpSX5U+R/GSBkT/zd/pd83c3ovU1YD2bbzuyS8ofnvH32pXaI9EmgJ2Dst5tqhrPni1rziA1z22tydxgxBtW64VSbWee0WNYcZyuu6kWHGDJAGuJQi7yznYdKqPso0ncFLZYAyjMqtkMVhd9NonHEtSp/asiS5ug38lLkj5HGHVcMdwh/N/jwfG26/Umt5JIQRXbyzB5wJP+bV4fmUKa4KUBAqIg4grhynzwNX8U4duuN3zagn59JwDt497uEoxnDGwytWtWe09Tql+KS8OBpyQPuODRf2ACNq9o1WV2iIrfUxyqgqTBPxtBKgZ6eZajg8k0N7znJZXdcZCJXRa8mxrTt55dGAmQZFBALQfTxWlrHSAQlpxAtQ5+N0pXaJDKuistNUvBzFq8twvv+a6GMiDppIL/CJfyTelKiYvZKpTRe49p47aKcLUn/jrsWpEehd7/pHdmxxa8uxqzrbFtetm2u54/GaC2tgK8kuPjZNQMPXexNL2WRxfxVAZfLmFh28EaGFgWXIFvA6pUCy84xFwU8ca/s/2TW/dGKj5fcO1LbqoRX8P43yJaYxQ2vICF8JnKEjaMsbsCpxQzYaMN3e0ReNXQzVi3cnUbAP7u3/R2hIuzMn8Qe1TQtXzu38FX6KpYpmeofswlT96fcxN+MnFZ1VtBkJQLK/ACA/+h+L9q1xvZKrLjzl6qYFMdrZsaOK3eKlbOqaeNfUZoClqhPQxnyvjTuQDuwZc2ZVm1/1RxaNZRrvQkufslULFVP+TgPbcWP4VZiKfKWw0z99tgfTP3wliisdEfr97GWqVqK/oim6a/rWitdyV5nKynYVE3hLiGd3/oJEoVLGXdz1uKkSI/WHqPIdgsuhpYthVF8ZesFinBxqUNI0jpJapa363xl8BCvqsqDZ9tBVfNoa6HnrkGJxbo+FRgYXN4J4Krm+iiVRTPal1gr+xt/Fi1i6cfrvlncVr8uqSYj1aIq6Kn77qlc2lF1vjR7DXK1HS9XCW79fffK1jq1HEJr4Xjo0/QCWyWZ9dN5m1m9ObUzLLnrtxCUrqWcr8psnOTn5NHEPG81RZNF0yurV2YmlaJMniqqXWHJ5fzSGyxLZaX5L1B4qsXmcM1UXw8jafiWRxruBxw2AZ2a6jTyAmR+xA16Lk7qSbN8noBg6gnkxgcabm0ODGTXJLEPHXgqCEDnnwfWE8G7WGKTai1h3RddrCRrLjwH1La/LF9tWZdkNlU836640H+dVYiLC/v+XZOa9Z2JTtEgLbpLP6GR1ZtI8QXsQm2zN8MwTWLPVrSyywWil5id+n/vE1wAF0+4xReWpkIAlnyewkV0T9ailA/4FX6YxpQCqv2NP9mXZvVJhQlS9it0ZY3i9zNTXJp0j2mvPoUfPDiRNYu+a61YHQWUis4G5meSvprKbpf4kEfMFovQF6lmeUnbk4Yk8gtKIofC5jJfPN2piuXdXpCr/zGHKyVjVin7zMGlhpj3AaXl3Of31f6/rDPM32POHPfGQXbLKzVrYjzv7js+PFc/BzSqqZ1VPAUrIYHYQJKftmmHs3rilMeEfFedOOn0dLrHBW1Ijv6upLSEZ2lDTeWAMral93C++L6MiSwlQec9saall9bRwzDNdcie6q9JcNszx5arYnNNkiKx3JHvVh4D3Ce6oWv8zMEvsWi2gh6nJBR0pdekFEMjvJRaQKnmSJ44gih/LDEH0h2x1XoNr3Kvx/DTz4c55pPugFVnCVXwg2KzKcuy3OaUkmc5uBe5ar0frNZDDmpQ4UsJPsQySuvoZW+ZqTnjVSmRmninM2xlcoeV/Vv7oXQ7TxsXMYv6BXJnHviWCBH0BkMrU40KlRsRkxEx1DLNaABI9hxk4ByQ0Ujef561v9C1rqTcuFuBHTjNO33Otdt4PxDZbQvYC+w7fDoFmbSbopUoYEpGn71v6qySAqZ6+mxQqu86oa5T4a+qldSpoxRgzDSu7l1ZmMk7jx9VbwGUKi1ja3tgjK3jjx4H+l/0n/K5+s8ZCR9z2Irb4AEj9QuqmnqNEf8ZQwe5v1l/tOokOyQAX/LtEOsm6DtwlnWt1STxiVHSactkW2sDk29h9V9LvIcHT2Cdo/LS10VgihvsOFnMLyqTFKbUZZ4Yt6fbC/gFvL+1Y8nZO3jaKpsY5D7ZAa9zg4bhk+mgyQ2tWZLPz709nr34doVPUjicx+fNvV6W+/aHW6enhcGZPztz1rS1Bzwbtbcd6akUjxsWaWUVnsoAQFzi58UYNN9mo6PICfdzjBc4KSLYlBdrpuGkJtIJzkysiMGTzGaA9q3imFi2nClvW3lzX4YzbacwcXg9Pb5Yc40oJkcqDCaBBPIxrqdZoYMGpuc9PsW39AHrbY9oB4RiyysqBhvihW0M9sb37/LYfhebdRy0/vl1FM+6uMZIYO7+EubLMqEVQYddBUtxGXZK5UXca6jGtZvz3KR+XfB4mZD/QWyLuAb+nKnKmIygEapBl8tTUgxvz/i/5nVCRUaQ0C5PBnNTpKReVbl2cDf2QADT+56ayWWiZPUXWNIpC7WF19YXlQqxLtMzScK6EktJk7K7GTgMp4u+SHXZTG8go48OFqg0foKAd1gd19pK4o35VLbvp/0TSuO1LBPFlcfN0SKqMeJTNtOXSDuLhUsp+rJCzq0qxiUlh3CMQGNcamqrN5xS5+4EcEDtG1sU9erfdtNjporY2wGE1vs4yciHwnDmQU/6cISmJcpn1QUyEmqSTV9AZHYz0w5bbVwv03CW2qw8H3DuoTNS5FmJ1myEHii7+C3VPfcgxo90QL0FT/KgORW8Wj0VNUviEBHdHWmmgs9jI+N0Lyl7juAkZdPE/1bmNUAhitMxaO/XlVGWkZHtobw8eqc26JFl1mWWue1erzpgu6qSf6YixudEL3wXyN1ZGhEWKThL9l340fn/BOvfwFfqz4ivSpGlRhLh0356/7jCFq80DR6gejM+ipKNExDADTFECvTJLnGe8rKZo5QS5p7OPC/TsEtt1tcXfJbuDANUWAH6gNiKxUbcBmuL9cK5X31mx6znjJICNtUttzq5/0rq837Lptz1IM8vg9wu9jx+2KtTpcpCrCgwSHNNC7vZe0oi2CC6mtDdtLdcNX314phim3DOjU+9duJDPf/pH9e4fFQb/RauTtDXOjhIbbliJkGTLbuBG2arcb0/303JGtbXdbrGuaiGNnhhBbX/mwgiWpXOEGC61Co5zjClbEk/4yCPEXonQ7RK5uMp4mwJcu5WAbSABhA35ZICCgskKuomwAnDwlJfytWj0HoehlCEWjpGMXr+kav8TkM+ZfIj9yhkIPYRhW0A97jIrn5z2IQ+d4LJe+lQbLfFgGFH/7laPNBZj1O73xwdxlL6zeZiLLXzD0rfWbAtUuqHCxchKWdw1Sn/XsGGMivdmE/90gHbk9Ih4GCLDCwoTXbk79z1xGGbaU4o9TvNGXUTqh5xuo8KvWj+0SNvYyo1P255J7nMsSTY2oS4vkf5SgliqA9NHfnexRXhnY+Af4wVJiFxtC1zfsfCDBHqX1zMRzp1W09hwQtcBRQbr/7Lo7eGrQTz5QjsErzMP0WweJd/PqvuX0ph8QyFeXaCcISMaZRQNfLnlv7JAT9bTJFhQw+F0xPzWgkydZIzXe2btnvmGaSIqRIseATZvIUQU45j1B4z2/6o96gPfoHynWRopPdG6nSAPB5J5aCuHOiK9FFGu/D6FkLGRS/sVPrq/c51H7MRErDgMZOP+NddU5770Wug98RLUD9N4MiFxQr2E4IxIQteb7/8v+CQ/MutSvGldrq8bWuzEAbuD9SuYY2qfESdVkShxxbMg1Kjwxpla7ta3nZBCk9FP3PI3+KNOK6tXSWH4hM3+3Ush5ZtwUmzFyuUm116jXOjElmMPnMwTpmKCy7wwhWegKVx8qIkT0cmrmsk9t7z0vbsVHhrff55n0CYDCRXQXfSabhBFST5sUVy9n2C1r41KsAoWE8mSpIkpDbMxe+nTgF4CwQmzkYJIQsxTqjuW7DFGy8wrvNqIoxTUhUBH79DWVY8j91O0WRpKK4sgM70WE9EyZoshuHtKA1gyoqlZe8V7eZEmEoXCE1IeZG0rHkec7JQFTFCl6E5fNoAY4KkKtLdigK3bMF5sbSssp/ZT1MZwJS9GBur4SOSMSPLFGPQI5KxFbxFQPdH6Y6L/GtTWN1vkxX8TTCGM/2350k+uYY/aIIMvjZ0hzoaUC36Dm4CxIE/fR4PvB0RxTO1HvLEpng8F1YB+HVxqVv6CIgGmg6S0Wgy5T1sgYqL/T+h0Y7JTujp8XaAM4eUSFMV2bYHm1zbk7m4B1DknFk9xZjuvS68btgE1irt5O+lz/eYK7aVzxexJ4tzLU/ygp1W797vnF4A6Gt//afEHjsIjO1E++pO4QqlhVy+rEd0PhRL5c9zgNzphD9CDSLMebRexiy+NbebC5skcRXoKk8IZvBMKAO5yuFMGxeSpxlpEYf8yunp1NN0yFztepaW4TCxze5MSZPBtKArVc6YJk5Qd1k/Glq43d1K3y3ngUquNBcPhTVKjBMu7+c/s9QnG5n5Xjen+PNQbWcVReim4NQFFAchZeMXXQ4905f29RCjJffIYad/XIZcNFDaomJ5TFerGzdofB4Q3vbvR4neXaE1GVZJN7TrvY2rnRjghqYej/R0fciBO4z/CrgaV1c9ewFm6dGcxhmju1Dqhn4VoH8n2ezSB3DXQiJsw5V4pmzH2jh313Mom+FXW17qQthU0ReDk+gGM9BGeI4qLlDm4vc2yjo0TcEqYIl8HxkgL7SUiPkb1NM+zvQz3Q3Ar/+gCuBrCHc/ndy27CPJLH5H7e32Jx1N8jhFzrvhZ+fd2Fgyaqk1ZBSyzEHuaoDH3mwR+SLXJ757IUTE0RZ3+q/lH/M99PQpH3TcW6KLAgCd3XmqpyzhNqO3WrBO8C1I6D+j3zHYgOQIINWz67T0dkuBbAlkAyDaAuUGkP3dykqgHALSElAGO5PJgNgGKNaAuA9k8UaUqob4pvI//iXeRjgBjpeixuV3BIEYDyjSo8kx6WVQkq9eHJ9/IH8IWHNwUaZkDsA7ocNRuiyXFM+F0jZsnYbXkNIFnBkp27Glfxn7L15s148YYnN6mnkoqZEwF75bKIMxtDFthx2ljVdU0g4qpjDZBNiDNTaBUkETeuOl8GjF8BV/9a43BqGPC9CHC9/2lB+gltmsRe+l7MIXzrawcd9E2lhridQ2AucVpZzgBvvQeqcNiB5edMJxflKnuw3GOyY4/j+YcCfz1wdcyOlczGo32y30irOs7+HAmNsA9Rw9qF9lGwQQurQOp2wM+jzH3WieaL045WmBcGMw+gpu4QbdbXpCDZy2gV+lYPV1+Dmd99F7qPy8WB7ptI0/pj17b37HInDCu0lOveTVg7yTt/X/7CemGH32wTsBxM9RbgE2D6GPnWJY9frDvQnToOtKnrbJ1FaDyBVER0qKWks51V7bZs51CMsSzUkm0RRnlKOwYi4CsLSED3mb90986uDnq+tXm+9H/0e7BK6ecEp0bF6j0A5GVWmBrs+fWBlpXbJoxizEtPFJNyKfBUZNcC3S+mBBa2RXIIj+wuTETslb0DvIus/+8wOUL6HBG9BdCGVkPszbV3j2DWhObVqbFaNdtJc58/b33yu9ULRgCuMQpUKLu1Ei0iiJBgmvuVg++5evGY/Uko3W4aCDqTg1zUJRy5kO+xJP0vQnTAumK5yfGcouDQDxdFCobngFD+fYJGbj7WInTPl4fw+odI92V1gqd51jMe8482ku5hquiTWH2pu4pf1mlrzPXWBcCh+3CQNb18ye+VX4lHDJfMdHO3zJVfj21cYhuzQbzt3xoA3mon0N0rbrEeRzzcnvC29J/F9z9vYTeV400KdBJZ0Y6rq5xBW87aS8FZV06QJIDD/8vjhA0sHFryDuwTKz5YVT8xKTKIA5iUv16CRXLLIQmY7hpjBcFFdVVKe/Ucoelvkd6H0e/rPgTZTEpJBEdsB6vTdsiyFYcTPPAHImIPxOdmhmy5yztuI7X/7UAXQEG4omomM9qAQusWkNs2dx3uE7fC7xURV/xSDy7pl2ZNRu6sGXNSj+e0Fvyp4PgC7+sgD8fw6byU376zJaRfmOu8ObZlRR0KacRbrYnoLcGoepDM5lPFYgYNSRa+7YYxUoeN1QOd88GoKSYuicGSjnfSxGhUeHKHffYyncThtspw9czSXWVnIuJN+S9WpeDxYrVB3S066Cfy1OO0C6ibElp6tTPZg74GRl16mSqbri6blODhbuY8jJ/oQuIP08Xt7NvCoGYrWrklVD0nfT6bC4LZfnfG3ALFJOv6KFxoxowDG0AsGTfx3Zam/mM95K6hH8I3thmHnR049wmK4vqABdi+Bco3T7b1iqIble4XBc69wE4foI37x1M8k0/NaYb/8R8mTatGnTpk2bNm2a+gExw2HjMG/uo0JsHXtz7wyutVJvf838DzZZbA6XxxcIRWKJVCZXKFVqjVanNzI2MTUzt7C0sraxtbN3cHRydnEFgSFQGByBRKExWByeQCSRKVQancFksTlcHl8gFIklUpm+XKFUqTVanYGhkbGJqZm5haWVtY2tnb2DIwyOQKLQGCwOTyCSyBQqjc5gstgcLo8vEIrEEqlMrlCq1BqtTm8wmswWq9fnBySIkqyomm745gEkGUUVmm6Ylu24HgBCMIJiOEFSNMNyAqFILJHK5AqlSq3R6vQGo8lssdrsDqfL7fH6/DwECoMjkCg0BovDE4gkMoVKi9VvyC1xP+l9/r+P9gs0mykDLqTSzUCYUAbcdnOhDLjSrrdQBlxo0y4JE8qAC6m0sc7rrwgTysKaMKEMVH8DXuxKG+t6++D6mJOJSbojTCgIZVx/jykccYoQJpRpr39rU/oYARPKgAuptLHO688JE8qAC6m0sd2CMKEsXFQihNx26BhjjDH2mHN+d/yrCWXAhVTaWOf1nyGJCWXAhVTa2G5LmFAGXEjVu487555HwIQy4EIqbWy3L4efIMKEsjAmTCgDLqTSxrrehDChDLiQShvrvP6UMKEMuJBKG9sNhJO5C6m0sc7rL7W4S0c9Q8CEMuBCKm2s8/pbwoQy4KK8qAPw60zAhGZTwoRyIauXYSAM6ugzgjChDLiQShvrvP6Ok2suEuI3k4AJZcCFVPW+MuDiyLOICWXAhVS6GRMmlAEXUmljndefECaUAReymqayzuvPCBPKgAuptGlXhAllwIXx+q/mDWFCGXAhlTbWef0txXuESbpPmFAGXBx5Nkn+kh4IE8qAC6m0sc7rzwkTyoAXa1Paece/h2LGGGM+5o0VYUIZcCGVNtZ5/bu70lprrZ8lYEIZcCGVNtZ5/dfR18OEMuBCHnUuESaUARdSaWOd17/WAmuttdZaa6211lprP/aNJWFCGeRrwoSCUMb1N0yhuFzz3xk++ed/3gRMKAMupNLGOq8/JkwoAy6k0sY6rz8hTCgDLqTSpp0KZeD6M8KEMuBCKm2s1x8IE8qAC6m08/pzwoQy4EIqbazz+gvChDLgQiptrPP6S8KEMuBCKm2s8/orwoQy4EIqbazz+mvCyaaNdV5/S4Qy4EIq67z+jjChDPI9A+719wkTyoAf/8ZcDgAAAAAAAAAAAAAAAADfvzm9GXAhlTa+dX8eAA==) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.css b/data/Leipzig.css index 716af530606..63c4ed0fe54 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index 83f458ce741..ad0d92c499a 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -806,5 +806,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/Leipzig/E926.xml b/data/Leipzig/E926.xml index 579b25d9c07..8733b17664e 100644 --- a/data/Leipzig/E926.xml +++ b/data/Leipzig/E926.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/E927.xml b/data/Leipzig/E927.xml index 221e405dbaa..2e265353c80 100644 --- a/data/Leipzig/E927.xml +++ b/data/Leipzig/E927.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/E928.xml b/data/Leipzig/E928.xml index b2ad64ae2ba..35043f0b21d 100644 --- a/data/Leipzig/E928.xml +++ b/data/Leipzig/E928.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/E929.xml b/data/Leipzig/E929.xml index f3d71370e40..f029f8c868b 100644 --- a/data/Leipzig/E929.xml +++ b/data/Leipzig/E929.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/EE90.xml b/data/Leipzig/EE90.xml index 3baa4150965..70d704fe65c 100644 --- a/data/Leipzig/EE90.xml +++ b/data/Leipzig/EE90.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/EE91.xml b/data/Leipzig/EE91.xml index 546ba2ca94a..1731eeee82d 100644 --- a/data/Leipzig/EE91.xml +++ b/data/Leipzig/EE91.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/EE92.xml b/data/Leipzig/EE92.xml index 21467e1589f..3d788dae24e 100644 --- a/data/Leipzig/EE92.xml +++ b/data/Leipzig/EE92.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/EE93.xml b/data/Leipzig/EE93.xml index 1747954476c..172c21c7097 100644 --- a/data/Leipzig/EE93.xml +++ b/data/Leipzig/EE93.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/Leipzig/EE94.xml b/data/Leipzig/EE94.xml index 6380e75f6a1..f116c069aa7 100644 --- a/data/Leipzig/EE94.xml +++ b/data/Leipzig/EE94.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/include/vrv/smufl.h b/include/vrv/smufl.h index ac712b5bc85..16d44c20537 100644 --- a/include/vrv/smufl.h +++ b/include/vrv/smufl.h @@ -432,6 +432,10 @@ enum { SMUFL_E923_mensuralProlationCombiningThreeDotsTri = 0xE923, SMUFL_E924_mensuralProlationCombiningDotVoid = 0xE924, SMUFL_E925_mensuralProlationCombiningStroke = 0xE925, + SMUFL_E926_mensuralProportion1 = 0xE926, + SMUFL_E927_mensuralProportion2 = 0xE927, + SMUFL_E928_mensuralProportion3 = 0xE928, + SMUFL_E929_mensuralProportion4 = 0xE929, SMUFL_E938_mensuralNoteheadSemibrevisBlack = 0xE938, SMUFL_E939_mensuralNoteheadSemibrevisVoid = 0xE939, SMUFL_E93C_mensuralNoteheadMinimaWhite = 0xE93C, @@ -644,10 +648,15 @@ enum { SMUFL_ECB7_metAugmentationDot = 0xECB7, SMUFL_ED40_articSoftAccentAbove = 0xED40, SMUFL_ED41_articSoftAccentBelow = 0xED41, + SMUFL_EE90_mensuralProportion5 = 0xEE90, + SMUFL_EE91_mensuralProportion6 = 0xEE91, + SMUFL_EE92_mensuralProportion7 = 0xEE92, + SMUFL_EE93_mensuralProportion8 = 0xEE93, + SMUFL_EE94_mensuralProportion9 = 0xEE94, }; /** The number of glyphs for verification **/ -#define SMUFL_COUNT 622 +#define SMUFL_COUNT 631 } // namespace vrv From 5534ad5afabeb88078bbc8a775ec8f079a2dc4f8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 1 Jul 2024 09:49:35 +0200 Subject: [PATCH 299/383] Adjust GetElementAttr to process scoreDef. Closes #3710 --- include/vrv/findfunctor.h | 1 + src/findfunctor.cpp | 13 +++++++++++++ src/toolkit.cpp | 1 + 3 files changed, 15 insertions(+) diff --git a/include/vrv/findfunctor.h b/include/vrv/findfunctor.h index 90d0a4da6ff..6a953e878c2 100644 --- a/include/vrv/findfunctor.h +++ b/include/vrv/findfunctor.h @@ -475,6 +475,7 @@ class FindElementInLayerStaffDefFunctor : public ConstFunctor { */ ///@{ FunctorCode VisitLayer(const Layer *layer) override; + FunctorCode VisitScore(const Score *score) override; ///@} protected: diff --git a/src/findfunctor.cpp b/src/findfunctor.cpp index 7efa54ad70e..b2e2da43b44 100644 --- a/src/findfunctor.cpp +++ b/src/findfunctor.cpp @@ -13,6 +13,7 @@ #include "layer.h" #include "object.h" #include "plistinterface.h" +#include "score.h" namespace vrv { @@ -334,6 +335,18 @@ FunctorCode FindElementInLayerStaffDefFunctor::VisitLayer(const Layer *layer) return m_element ? FUNCTOR_STOP : FUNCTOR_SIBLINGS; } +FunctorCode FindElementInLayerStaffDefFunctor::VisitScore(const Score *score) +{ + if (score->GetScoreDef()->GetID() == m_id) { + m_element = score->GetScoreDef(); + } + else { + m_element = score->GetScoreDef()->FindDescendantByID(m_id); + } + + return (m_element) ? FUNCTOR_STOP : FUNCTOR_CONTINUE; +} + //---------------------------------------------------------------------------- // AddToFlatListFunctor //---------------------------------------------------------------------------- diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 3248a9faf76..9e322ac1ce6 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1325,6 +1325,7 @@ std::string Toolkit::GetElementAttr(const std::string &xmlId) } // If not found again, try looking in the layer staffdefs if (!element) { + // This will also look in the score/scoreDef FindElementInLayerStaffDefFunctor findElementInLayerStaffDef(xmlId); // Check drawing page elements first if (m_doc.GetDrawingPage()) { From 3eb5b1a0a51807e34f8850c7321ab682a803753e Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 1 Jul 2024 09:50:10 +0200 Subject: [PATCH 300/383] Remove wrong attribute classes in ScoreDefInterface --- src/scoredefinterface.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/scoredefinterface.cpp b/src/scoredefinterface.cpp index 2fcd0c8fd47..454762b4815 100644 --- a/src/scoredefinterface.cpp +++ b/src/scoredefinterface.cpp @@ -41,8 +41,6 @@ ScoreDefInterface::ScoreDefInterface() this->RegisterInterfaceAttClass(ATT_DURATIONDEFAULT); this->RegisterInterfaceAttClass(ATT_LYRICSTYLE); this->RegisterInterfaceAttClass(ATT_MEASURENUMBERS); - this->RegisterInterfaceAttClass(ATT_METERSIGDEFAULTLOG); - this->RegisterInterfaceAttClass(ATT_METERSIGDEFAULTVIS); this->RegisterInterfaceAttClass(ATT_MIDITEMPO); this->RegisterInterfaceAttClass(ATT_MMTEMPO); this->RegisterInterfaceAttClass(ATT_MULTINUMMEASURES); From 0df6a66ceda933bc4b5a5f3c66393faa4bbe64b4 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Wed, 26 Jun 2024 12:41:56 -0400 Subject: [PATCH 301/383] Fix closest staff finding and pitch alignment for new inserted element - Revert changes in `ClosestBB::distanceToBB()` - Call `Page::LayOutPitchPos()` after adjust pitch Refs: https://github.com/DDMAL/Neon/issues/1226 --- include/vrv/editortoolkit_neume.h | 4 ++-- src/editortoolkit_neume.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/vrv/editortoolkit_neume.h b/include/vrv/editortoolkit_neume.h index 90dd931c667..1a925b0c8f9 100644 --- a/include/vrv/editortoolkit_neume.h +++ b/include/vrv/editortoolkit_neume.h @@ -122,8 +122,8 @@ struct ClosestBB { int offset = (x - ulx) * tan(rotate * M_PI / 180.0); uly = uly + offset; lry = lry + offset; - int xDiff = std::abs(x - ulx); - int yDiff = std::abs(y - uly); + int xDiff = std::max((ulx > x ? ulx - x : 0), (x > lrx ? x - lrx : 0)); + int yDiff = std::max((uly > y ? uly - y : 0), (y > lry ? y - lry : 0)); return sqrt(xDiff * xDiff + yDiff * yDiff); } diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 73decbabee9..8c440f79d81 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1250,6 +1250,7 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in } layer->ReorderByXPos(); + m_doc->GetDrawingPage()->LayOutPitchPos(); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", status); From fc2fdb36703913b4b56423c2eaa9adea93fa656d Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 27 Jun 2024 09:53:38 -0400 Subject: [PATCH 302/383] Remove redundant for loop --- src/editortoolkit_neume.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 8c440f79d81..e602ae8f6ce 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -664,18 +664,17 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) } // Move staff and all staff children with facsimiles + Zone *staffZone = staff->GetZone(); + assert(staffZone); + staffZone->ShiftByXY(x, -y); ListOfObjects children; InterfaceComparison ic(INTERFACE_FACSIMILE); staff->FindAllDescendantsByComparison(&children, &ic); - std::set zones; - zones.insert(staff->GetZone()); for (auto it = children.begin(); it != children.end(); ++it) { FacsimileInterface *fi = (*it)->GetFacsimileInterface(); assert(fi); - if (fi->GetZone() != NULL) zones.insert(fi->GetZone()); - } - for (auto it = zones.begin(); it != zones.end(); ++it) { - (*it)->ShiftByXY(x, -y); + Zone *zone = fi->GetZone(); + if (zone) zone->ShiftByXY(x, -y); } SortStaves(); From e0f40a9138db5d3bca3571e90c0221472da32c58 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 27 Jun 2024 10:23:49 -0400 Subject: [PATCH 303/383] Align staff children after staff drag - Call `Page::ResetAligners()` after dragging staff Refs: https://github.com/DDMAL/Neon/issues/1230 --- src/editortoolkit_neume.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index e602ae8f6ce..1bd268778c8 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -679,6 +679,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) SortStaves(); + m_doc->GetDrawingPage()->ResetAligners(); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers From 3ae461739514f0c38d3f17b0ba7ab7b287786014 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 27 Jun 2024 12:23:48 -0400 Subject: [PATCH 304/383] Remove empty system after staff merging Refs: https://github.com/DDMAL/Neon/issues/1220 --- src/editortoolkit_neume.cpp | 39 +++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 1bd268778c8..50cf062f61d 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1711,6 +1711,8 @@ bool EditorToolkitNeume::MatchHeight(std::string elementId) bool EditorToolkitNeume::Merge(std::vector elementIds) { if (!m_doc->GetDrawingPage()) return false; + Object *page = m_doc->GetDrawingPage(); + ListOfObjects staves; // Get the staves by element ID and fail if a staff does not exist. @@ -1777,8 +1779,35 @@ bool EditorToolkitNeume::Merge(std::vector elementIds) Layer *sourceLayer = vrv_cast(sourceStaff->GetFirst(LAYER)); fillLayer->MoveChildrenFrom(sourceLayer); assert(sourceLayer->GetChildCount() == 0); - Object *parent = sourceStaff->GetParent(); - parent->DeleteChild(sourceStaff); + + // Delete empty staff with its system parent + // Move SECTION, PB, and SYSTEM_MILESTONE_END if any + Object *system = sourceStaff->GetFirstAncestor(SYSTEM); + if (system->FindDescendantByType(SECTION)) { + Object *section = system->FindDescendantByType(SECTION); + Object *nextSystem = page->GetNext(system, SYSTEM); + if (nextSystem) { + section = system->DetachChild(section->GetIdx()); + nextSystem->InsertChild(section, 0); + } + } + if (system->FindDescendantByType(PB)) { + Object *pb = system->FindDescendantByType(PB); + Object *nextSystem = page->GetNext(system, SYSTEM); + if (nextSystem) { + pb = system->DetachChild(pb->GetIdx()); + nextSystem->InsertChild(pb, 1); + } + } + if (system->FindDescendantByType(SYSTEM_MILESTONE_END)) { + Object *milestoneEnd = system->FindDescendantByType(SYSTEM_MILESTONE_END); + Object *previousSystem = page->GetPrevious(system, SYSTEM); + if (previousSystem) { + milestoneEnd = system->DetachChild(milestoneEnd->GetIdx()); + previousSystem->InsertChild(milestoneEnd, previousSystem->GetChildCount()); + } + } + page->DeleteChild(system); } // Set the bounding box for the staff to the new bounds Zone *staffZone = fillStaff->GetZone(); @@ -1790,14 +1819,12 @@ bool EditorToolkitNeume::Merge(std::vector elementIds) fillLayer->ReorderByXPos(); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); + m_editInfo.import("uuid", fillStaff->GetID()); m_editInfo.import("status", "OK"); m_editInfo.import("message", ""); - if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); - - // TODO change zones for staff children - return true; } From 28145c136c1fd5c2836ec6fbc04d9de0a845f39a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 3 Jul 2024 09:32:56 +0200 Subject: [PATCH 305/383] Change macos-11 to macos-latest * Job not starting --- .github/workflows/ci_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 1d75b6b86ff..4d7384dd54d 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -106,7 +106,7 @@ jobs: compiler: xcode version: "15.3" - - os: macos-11 + - os: macos-latest compiler: g++ version: "12" From a415cd89710f21288ee3fa6df208ef30d1d29fc5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 3 Jul 2024 19:18:18 +0200 Subject: [PATCH 306/383] Refactoring to move some of the Nc drawing code into a data preparation functor --- include/vrv/adjustneumexfunctor.h | 4 +- include/vrv/calcligatureorneumeposfunctor.h | 1 + include/vrv/nc.h | 18 ++ include/vrv/resetfunctor.h | 1 + include/vrv/view.h | 1 + src/adjustneumexfunctor.cpp | 20 +++ src/alignfunctor.cpp | 2 +- src/calcligatureorneumeposfunctor.cpp | 126 ++++++++++++++ src/resetfunctor.cpp | 13 ++ src/view_neume.cpp | 175 ++------------------ 10 files changed, 196 insertions(+), 165 deletions(-) diff --git a/include/vrv/adjustneumexfunctor.h b/include/vrv/adjustneumexfunctor.h index c20509d2ea7..7a2c22722c9 100644 --- a/include/vrv/adjustneumexfunctor.h +++ b/include/vrv/adjustneumexfunctor.h @@ -54,8 +54,10 @@ class AdjustNeumeXFunctor : public DocFunctor { public: // private: - /** The minimu position of the next syl */ + /** The minimum position of the next syl */ int m_minPos; + /** The minimum position of the next neume */ + int m_neumeMinPos; }; } // namespace vrv diff --git a/include/vrv/calcligatureorneumeposfunctor.h b/include/vrv/calcligatureorneumeposfunctor.h index 46482134313..1dba3e5296b 100644 --- a/include/vrv/calcligatureorneumeposfunctor.h +++ b/include/vrv/calcligatureorneumeposfunctor.h @@ -39,6 +39,7 @@ class CalcLigatureOrNeumePosFunctor : public DocFunctor { */ ///@{ FunctorCode VisitLigature(Ligature *ligature) override; + FunctorCode VisitNeume(Neume *neume) override; ///@} protected: diff --git a/include/vrv/nc.h b/include/vrv/nc.h index 4f02ea5430b..fcb4ae0bb69 100644 --- a/include/vrv/nc.h +++ b/include/vrv/nc.h @@ -74,7 +74,25 @@ class Nc : public LayerElement, FunctorCode AcceptEnd(ConstFunctor &functor) const override; ///@} + /** + * A Structure holding a glyph paramter for the nc. + * One single nc might need more than one glyph (e.g., liquescent). + * Set in CalcLigatureOrNeumePosFunctor::VisitNeume + */ + struct DrawingGlyph { + wchar_t m_fontNo = 0; + float m_xOffset = 0.0; + float m_yOffset = 0.0; + }; + +private: + // +public: + /** Drawing glyphs */ + std::vector m_drawingGlyphs; + private: + // }; } // namespace vrv diff --git a/include/vrv/resetfunctor.h b/include/vrv/resetfunctor.h index f8f4d5c1032..e67034ccfc7 100644 --- a/include/vrv/resetfunctor.h +++ b/include/vrv/resetfunctor.h @@ -62,6 +62,7 @@ class ResetDataFunctor : public Functor { FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitMRest(MRest *mRest) override; FunctorCode VisitNote(Note *note) override; + FunctorCode VisitNc(Nc *nc) override; FunctorCode VisitObject(Object *object) override; FunctorCode VisitRepeatMark(RepeatMark *repeatMark) override; FunctorCode VisitRest(Rest *rest) override; diff --git a/include/vrv/view.h b/include/vrv/view.h index d72d1a39703..e7faedd7ad1 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -429,6 +429,7 @@ class View { */ ///@{ void DrawNcAsNotehead(DeviceContext *dc, Nc *nc, Layer *layer, Staff *staff, Measure *measure); + void DrawNcGlyphs(DeviceContext *dc, Nc *nc, Staff *staff); ///@} /** diff --git a/src/adjustneumexfunctor.cpp b/src/adjustneumexfunctor.cpp index eb104e606ee..cce5eeb439a 100644 --- a/src/adjustneumexfunctor.cpp +++ b/src/adjustneumexfunctor.cpp @@ -11,6 +11,7 @@ #include "doc.h" #include "layer.h" +#include "neume.h" #include "score.h" #include "staff.h" #include "syl.h" @@ -54,6 +55,22 @@ FunctorCode AdjustNeumeXFunctor::VisitLayerEnd(Layer *layer) FunctorCode AdjustNeumeXFunctor::VisitNeume(Neume *neume) { + // It is 0 when we process the first neume of the syllable + if (m_neumeMinPos != VRV_UNSET) { + Alignment *alignment = neume->GetAlignment(); + + int selfLeft = neume->GetContentLeft(); + if (selfLeft < m_neumeMinPos) { + const int adjust = m_neumeMinPos - selfLeft; + alignment->SetXRel(alignment->GetXRel() + adjust); + } + } + + m_neumeMinPos = neume->GetContentRight() + m_doc->GetDrawingUnit(100); + + // Check if the neume takes more space the the syllable text + if (m_neumeMinPos > m_minPos) m_minPos = m_neumeMinPos; + return FUNCTOR_CONTINUE; } @@ -68,6 +85,9 @@ FunctorCode AdjustNeumeXFunctor::VisitSyl(Syl *syl) { Alignment *alignment = syl->GetAlignment(); + // Indicates that the neume will be the first of the syllable + m_neumeMinPos = VRV_UNSET; + int selfLeft = syl->GetContentLeft(); if (selfLeft < m_minPos) { const int adjust = m_minPos - selfLeft; diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index 517f3ccf135..476bf07f10e 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -394,7 +394,7 @@ FunctorCode AlignHorizontallyFunctor::VisitMeasureEnd(Measure *measure) if (m_hasMultipleLayer) measure->HasAlignmentRefWithMultipleLayers(true); - // measure->m_measureAligner.LogDebugTree(3); + measure->m_measureAligner.LogDebugTree(3); return FUNCTOR_CONTINUE; } diff --git a/src/calcligatureorneumeposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp index 8f8077329e8..c9447cabcac 100644 --- a/src/calcligatureorneumeposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -11,6 +11,8 @@ #include "doc.h" #include "ligature.h" +#include "nc.h" +#include "neume.h" #include "staff.h" //---------------------------------------------------------------------------- @@ -223,4 +225,128 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitLigature(Ligature *ligature) return FUNCTOR_SIBLINGS; } +FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) +{ + if (m_doc->GetOptions()->m_neumeAsNote.GetValue()) return FUNCTOR_SIBLINGS; + + ListOfObjects ncs = neume->FindAllDescendantsByType(NC); + + int xRel = 0; + + for (Object *object : ncs) { + + Nc *nc = vrv_cast(object); + assert(nc); + + const bool hasLiquescent = (nc->FindDescendantByType(LIQUESCENT)); + const bool hasOriscus = (nc->FindDescendantByType(ORISCUS)); + const bool hasQuilisma = (nc->FindDescendantByType(QUILISMA)); + + // Make sure we have at least one glyph + nc->m_drawingGlyphs.resize(1); + + if (hasLiquescent) { + nc->m_drawingGlyphs.resize(3); + if (nc->GetCurve() == curvatureDirection_CURVE_c) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E995_chantAuctumDesc; + nc->m_drawingGlyphs[1].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs[2].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs[2].m_xOffset = 0.8; + nc->m_drawingGlyphs[1].m_yOffset = -1.5; + nc->m_drawingGlyphs[2].m_yOffset = -1.75; + } + else if (nc->GetCurve() == curvatureDirection_CURVE_a) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E994_chantAuctumAsc; + nc->m_drawingGlyphs[1].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs[2].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs[2].m_xOffset = 0.8; + nc->m_drawingGlyphs[1].m_yOffset = 0.5; + nc->m_drawingGlyphs[2].m_yOffset = 0.75; + } + else { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E9A1_chantPunctumDeminutum; + } + } + else if (hasOriscus) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_EA2A_medRenOriscusCMN; + } + else if (hasQuilisma) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E99B_chantQuilisma; + } + else { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E990_chantPunctum; + + Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); + assert(neume); + int position = neume->GetChildIndex(nc); + + // Check if nc is part of a ligature or is an inclinatum + if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E991_chantPunctumInclinatum; + } + else if (nc->GetLigated() == BOOLEAN_true) { + int pitchDifference = 0; + bool isFirst; + int ligCount = neume->GetLigatureCount(position); + + if (ligCount % 2 == 0) { + isFirst = false; + Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); + assert(lastNc); + pitchDifference = nc->PitchDifferenceTo(lastNc); + nc->m_drawingGlyphs[0].m_yOffset = -pitchDifference; + } + else { + isFirst = true; + Object *nextSibling = neume->GetChild(position + 1); + if (nextSibling != NULL) { + Nc *nextNc = dynamic_cast(nextSibling); + assert(nextNc); + pitchDifference = nextNc->PitchDifferenceTo(nc); + nc->m_drawingGlyphs[0].m_yOffset = pitchDifference; + } + } + + // set the glyph + switch (pitchDifference) { + case -1: + nc->m_drawingGlyphs[0].m_fontNo + = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; + break; + case -2: + nc->m_drawingGlyphs[0].m_fontNo + = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; + break; + case -3: + nc->m_drawingGlyphs[0].m_fontNo + = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; + break; + case -4: + nc->m_drawingGlyphs[0].m_fontNo + = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; + break; + default: break; + } + } + + // If the nc is supposed to be a virga and currently is being rendered as a punctum + // change it to a virga + if (nc->GetTilt() == COMPASSDIRECTION_s && nc->m_drawingGlyphs[0].m_fontNo == SMUFL_E990_chantPunctum) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E996_chantPunctumVirga; + } + + else if (nc->GetTilt() == COMPASSDIRECTION_n + && nc->m_drawingGlyphs[0].m_fontNo == SMUFL_E990_chantPunctum) { + nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E997_chantPunctumVirgaReversed; + } + } + + nc->SetDrawingXRel(xRel); + // The first glyph set the spacing + xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs[0].m_fontNo, 100, false); + } + + return FUNCTOR_SIBLINGS; +} + } // namespace vrv diff --git a/src/resetfunctor.cpp b/src/resetfunctor.cpp index 6c53b73e881..e09d3ee72a7 100644 --- a/src/resetfunctor.cpp +++ b/src/resetfunctor.cpp @@ -23,6 +23,7 @@ #include "layer.h" #include "ligature.h" #include "mrest.h" +#include "nc.h" #include "octave.h" #include "page.h" #include "repeatmark.h" @@ -343,6 +344,18 @@ FunctorCode ResetDataFunctor::VisitNote(Note *note) return FUNCTOR_CONTINUE; } +FunctorCode ResetDataFunctor::VisitNc(Nc *nc) +{ + // Call parent one too + this->VisitLayerElement(nc); + + nc->m_drawingGlyphs.clear(); + + // We want the list of the ObjectListInterface to be regenerated + nc->Modify(); + return FUNCTOR_CONTINUE; +} + FunctorCode ResetDataFunctor::VisitObject(Object *object) { return FUNCTOR_CONTINUE; diff --git a/src/view_neume.cpp b/src/view_neume.cpp index c6055ce75cb..279fc9161c0 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -69,49 +69,8 @@ void View::DrawLiquescent(DeviceContext *dc, LayerElement *element, Layer *layer assert(staff); assert(measure); - NcDrawingParams params[3]; - dc->StartGraphic(element, "", element->GetID()); - Nc *nc = dynamic_cast(element->GetParent()); - - if (nc->GetCurve() == curvatureDirection_CURVE_c) { - params[0].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - params[1].fontNo = SMUFL_E995_chantAuctumDesc; - params[2].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - params[2].xOffset = 0.8; - params[0].yOffset = -1.5; - params[2].yOffset = -1.75; - } - else if (nc->GetCurve() == curvatureDirection_CURVE_a) { - params[0].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - params[1].fontNo = SMUFL_E994_chantAuctumAsc; - params[2].fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - params[2].xOffset = 0.8; - params[0].yOffset = 0.5; - params[2].yOffset = 0.75; - } - else { - params[0].fontNo = SMUFL_E9A1_chantPunctumDeminutum; - } - - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - - int noteX = nc->GetDrawingX(); - int noteY = nc->GetDrawingY(); - - if (staff->HasDrawingRotation()) { - noteY -= staff->GetDrawingRotationOffsetFor(noteX); - } - - for (int i = 0; i < static_cast(sizeof(params)); ++i) { - DrawSmuflCode(dc, noteX + params[i].xOffset * noteWidth, noteY + params[i].yOffset * noteHeight, - params[i].fontNo, staff->m_drawingStaffSize, false, true); - } - dc->EndGraphic(element, this); } @@ -130,104 +89,9 @@ void View::DrawNc(DeviceContext *dc, LayerElement *element, Layer *layer, Staff return; } - NcDrawingParams params; - dc->StartGraphic(element, "", element->GetID()); - const bool hasLiquescent = (nc->FindDescendantByType(LIQUESCENT)); - const bool hasOriscus = (nc->FindDescendantByType(ORISCUS)); - const bool hasQuilisma = (nc->FindDescendantByType(QUILISMA)); - - /******************************************************************/ - - if (!hasLiquescent && !hasOriscus && !hasQuilisma) { - - params.fontNo = SMUFL_E990_chantPunctum; - - Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); - assert(neume); - int position = neume->GetChildIndex(element); - - // Check if nc is part of a ligature or is an inclinatum - if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { - params.fontNo = SMUFL_E991_chantPunctumInclinatum; - } - else if (nc->GetLigated() == BOOLEAN_true) { - int pitchDifference = 0; - bool isFirst; - int ligCount = neume->GetLigatureCount(position); - - if (ligCount % 2 == 0) { - isFirst = false; - Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); - assert(lastNc); - pitchDifference = nc->PitchDifferenceTo(lastNc); - params.xOffset = -1; - params.yOffset = -pitchDifference; - } - else { - isFirst = true; - Object *nextSibling = neume->GetChild(position + 1); - if (nextSibling != NULL) { - Nc *nextNc = dynamic_cast(nextSibling); - assert(nextNc); - pitchDifference = nextNc->PitchDifferenceTo(nc); - params.yOffset = pitchDifference; - } - } - - // set the glyph - switch (pitchDifference) { - case -1: - params.fontNo = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; - break; - case -2: - params.fontNo = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; - break; - case -3: - params.fontNo = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; - break; - case -4: - params.fontNo = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; - break; - default: break; - } - } - - // If the nc is supposed to be a virga and currently is being rendered as a punctum - // change it to a virga - if (nc->GetTilt() == COMPASSDIRECTION_s && params.fontNo == SMUFL_E990_chantPunctum) { - params.fontNo = SMUFL_E996_chantPunctumVirga; - } - - else if (nc->GetTilt() == COMPASSDIRECTION_n && params.fontNo == SMUFL_E990_chantPunctum) { - params.fontNo = SMUFL_E997_chantPunctumVirgaReversed; - } - - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - - int noteX = nc->GetDrawingX(); - int noteY = nc->GetDrawingY(); - - if (nc->HasFacs() && m_doc->IsNeumeLines()) { - params.xOffset = 0; - } - // Not sure about this if - the nc pname and oct are going to be ignored - else if (neume->HasFacs() && m_doc->IsNeumeLines()) { - noteY = staff->GetDrawingY(); - noteX = neume->GetDrawingX() + position * noteWidth; - } - - if (staff->HasDrawingRotation()) { - noteY -= staff->GetDrawingRotationOffsetFor(noteX); - } - - DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, - staff->m_drawingStaffSize, false, true); - } + this->DrawNcGlyphs(dc, nc, staff); /******************************************************************/ @@ -360,26 +224,6 @@ void View::DrawOriscus(DeviceContext *dc, LayerElement *element, Layer *layer, S dc->StartGraphic(element, "", element->GetID()); - Nc *nc = dynamic_cast(element->GetParent()); - assert(nc); - - params.fontNo = SMUFL_EA2A_medRenOriscusCMN; - - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - - int noteX = nc->GetDrawingX(); - int noteY = nc->GetDrawingY(); - - if (staff->HasDrawingRotation()) { - noteY -= staff->GetDrawingRotationOffsetFor(noteX); - } - - DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, - staff->m_drawingStaffSize, false, true); - dc->EndGraphic(element, this); } @@ -394,9 +238,14 @@ void View::DrawQuilisma(DeviceContext *dc, LayerElement *element, Layer *layer, dc->StartGraphic(element, "", element->GetID()); - Nc *nc = dynamic_cast(element->GetParent()); + dc->EndGraphic(element, this); +} - params.fontNo = SMUFL_E99B_chantQuilisma; +void View::DrawNcGlyphs(DeviceContext *dc, Nc *nc, Staff *staff) +{ + assert(dc); + assert(nc); + assert(staff); const int noteHeight = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); @@ -410,10 +259,10 @@ void View::DrawQuilisma(DeviceContext *dc, LayerElement *element, Layer *layer, noteY -= staff->GetDrawingRotationOffsetFor(noteX); } - DrawSmuflCode(dc, noteX + params.xOffset * noteWidth, noteY + params.yOffset * noteHeight, params.fontNo, - staff->m_drawingStaffSize, false, true); - - dc->EndGraphic(element, this); + for (auto &glyph : nc->m_drawingGlyphs) { + DrawSmuflCode(dc, noteX + glyph.m_xOffset * noteWidth, noteY + glyph.m_yOffset * noteHeight, glyph.m_fontNo, + staff->m_drawingStaffSize, false, true); + } } } // namespace vrv From 6d300dbd488c43a97c1233a0bba6c5c089dd461a Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 3 Jul 2024 19:23:37 +0200 Subject: [PATCH 307/383] Remove unused NcDrawingParams --- src/view_neume.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 279fc9161c0..981ff2da5ba 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -33,12 +33,6 @@ namespace vrv { -struct NcDrawingParams { - wchar_t fontNo = 0; - float xOffset = 0; - float yOffset = 0; -}; - void View::DrawSyllable(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) { assert(dc); @@ -220,8 +214,6 @@ void View::DrawOriscus(DeviceContext *dc, LayerElement *element, Layer *layer, S assert(staff); assert(measure); - NcDrawingParams params; - dc->StartGraphic(element, "", element->GetID()); dc->EndGraphic(element, this); @@ -234,8 +226,6 @@ void View::DrawQuilisma(DeviceContext *dc, LayerElement *element, Layer *layer, assert(staff); assert(measure); - NcDrawingParams params; - dc->StartGraphic(element, "", element->GetID()); dc->EndGraphic(element, this); From cfda076b4d49062f09d7953fc4f8236cb0f5530b Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 3 Jul 2024 20:21:01 +0200 Subject: [PATCH 308/383] Apply staff size and change vector access to `.at` --- src/calcligatureorneumeposfunctor.cpp | 58 ++++++++++++++------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/calcligatureorneumeposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp index c9447cabcac..797b0f08317 100644 --- a/src/calcligatureorneumeposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -230,6 +230,8 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) if (m_doc->GetOptions()->m_neumeAsNote.GetValue()) return FUNCTOR_SIBLINGS; ListOfObjects ncs = neume->FindAllDescendantsByType(NC); + + Staff *staff = neume->GetAncestorStaff(); int xRel = 0; @@ -248,33 +250,33 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) if (hasLiquescent) { nc->m_drawingGlyphs.resize(3); if (nc->GetCurve() == curvatureDirection_CURVE_c) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E995_chantAuctumDesc; - nc->m_drawingGlyphs[1].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - nc->m_drawingGlyphs[2].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - nc->m_drawingGlyphs[2].m_xOffset = 0.8; - nc->m_drawingGlyphs[1].m_yOffset = -1.5; - nc->m_drawingGlyphs[2].m_yOffset = -1.75; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E995_chantAuctumDesc; + nc->m_drawingGlyphs.at(1).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs.at(2).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs.at(2).m_xOffset = 0.8; + nc->m_drawingGlyphs.at(1).m_yOffset = -1.5; + nc->m_drawingGlyphs.at(2).m_yOffset = -1.75; } else if (nc->GetCurve() == curvatureDirection_CURVE_a) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E994_chantAuctumAsc; - nc->m_drawingGlyphs[1].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - nc->m_drawingGlyphs[2].m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - nc->m_drawingGlyphs[2].m_xOffset = 0.8; - nc->m_drawingGlyphs[1].m_yOffset = 0.5; - nc->m_drawingGlyphs[2].m_yOffset = 0.75; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E994_chantAuctumAsc; + nc->m_drawingGlyphs.at(1).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs.at(2).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; + nc->m_drawingGlyphs.at(2).m_xOffset = 0.8; + nc->m_drawingGlyphs.at(1).m_yOffset = 0.5; + nc->m_drawingGlyphs.at(2).m_yOffset = 0.75; } else { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E9A1_chantPunctumDeminutum; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9A1_chantPunctumDeminutum; } } else if (hasOriscus) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_EA2A_medRenOriscusCMN; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_EA2A_medRenOriscusCMN; } else if (hasQuilisma) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E99B_chantQuilisma; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E99B_chantQuilisma; } else { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E990_chantPunctum; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E990_chantPunctum; Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); assert(neume); @@ -282,7 +284,7 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) // Check if nc is part of a ligature or is an inclinatum if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E991_chantPunctumInclinatum; + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E991_chantPunctumInclinatum; } else if (nc->GetLigated() == BOOLEAN_true) { int pitchDifference = 0; @@ -294,7 +296,7 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); assert(lastNc); pitchDifference = nc->PitchDifferenceTo(lastNc); - nc->m_drawingGlyphs[0].m_yOffset = -pitchDifference; + nc->m_drawingGlyphs.at(0).m_yOffset = -pitchDifference; } else { isFirst = true; @@ -303,26 +305,26 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) Nc *nextNc = dynamic_cast(nextSibling); assert(nextNc); pitchDifference = nextNc->PitchDifferenceTo(nc); - nc->m_drawingGlyphs[0].m_yOffset = pitchDifference; + nc->m_drawingGlyphs.at(0).m_yOffset = pitchDifference; } } // set the glyph switch (pitchDifference) { case -1: - nc->m_drawingGlyphs[0].m_fontNo + nc->m_drawingGlyphs.at(0).m_fontNo = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; break; case -2: - nc->m_drawingGlyphs[0].m_fontNo + nc->m_drawingGlyphs.at(0).m_fontNo = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; break; case -3: - nc->m_drawingGlyphs[0].m_fontNo + nc->m_drawingGlyphs.at(0).m_fontNo = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; break; case -4: - nc->m_drawingGlyphs[0].m_fontNo + nc->m_drawingGlyphs.at(0).m_fontNo = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; break; default: break; @@ -331,19 +333,19 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) // If the nc is supposed to be a virga and currently is being rendered as a punctum // change it to a virga - if (nc->GetTilt() == COMPASSDIRECTION_s && nc->m_drawingGlyphs[0].m_fontNo == SMUFL_E990_chantPunctum) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E996_chantPunctumVirga; + if (nc->GetTilt() == COMPASSDIRECTION_s && nc->m_drawingGlyphs.at(0).m_fontNo == SMUFL_E990_chantPunctum) { + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E996_chantPunctumVirga; } else if (nc->GetTilt() == COMPASSDIRECTION_n - && nc->m_drawingGlyphs[0].m_fontNo == SMUFL_E990_chantPunctum) { - nc->m_drawingGlyphs[0].m_fontNo = SMUFL_E997_chantPunctumVirgaReversed; + && nc->m_drawingGlyphs.at(0).m_fontNo == SMUFL_E990_chantPunctum) { + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E997_chantPunctumVirgaReversed; } } nc->SetDrawingXRel(xRel); // The first glyph set the spacing - xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs[0].m_fontNo, 100, false); + xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staff->m_drawingStaffSize, false); } return FUNCTOR_SIBLINGS; From f6dc0d3b59921b3b3f1886000327cf9f2e4f4c9f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 4 Jul 2024 12:57:20 +0200 Subject: [PATCH 309/383] Refactor ligature calculation with reduce Nc lookup --- src/calcligatureorneumeposfunctor.cpp | 100 ++++++++++++-------------- 1 file changed, 47 insertions(+), 53 deletions(-) diff --git a/src/calcligatureorneumeposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp index 797b0f08317..3dd95d7dae5 100644 --- a/src/calcligatureorneumeposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -230,10 +230,12 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) if (m_doc->GetOptions()->m_neumeAsNote.GetValue()) return FUNCTOR_SIBLINGS; ListOfObjects ncs = neume->FindAllDescendantsByType(NC); - + Staff *staff = neume->GetAncestorStaff(); int xRel = 0; + Nc *previousNc = NULL; + bool previousLig = false; for (Object *object : ncs) { @@ -278,65 +280,53 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) else { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E990_chantPunctum; - Neume *neume = vrv_cast(nc->GetFirstAncestor(NEUME)); - assert(neume); - int position = neume->GetChildIndex(nc); - - // Check if nc is part of a ligature or is an inclinatum - if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { - nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E991_chantPunctumInclinatum; - } - else if (nc->GetLigated() == BOOLEAN_true) { - int pitchDifference = 0; - bool isFirst; - int ligCount = neume->GetLigatureCount(position); - - if (ligCount % 2 == 0) { - isFirst = false; - Nc *lastNc = dynamic_cast(neume->GetChild(position > 0 ? position - 1 : 0)); - assert(lastNc); - pitchDifference = nc->PitchDifferenceTo(lastNc); - nc->m_drawingGlyphs.at(0).m_yOffset = -pitchDifference; + if (nc->GetLigated() == BOOLEAN_true) { + // This is the first nc of a ligature + if (!previousLig) { + // Temporarily set a second line glyph + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9B4_chantEntryLineAsc2nd; + previousLig = true; } + // This is the second else { - isFirst = true; - Object *nextSibling = neume->GetChild(position + 1); - if (nextSibling != NULL) { - Nc *nextNc = dynamic_cast(nextSibling); - assert(nextNc); - pitchDifference = nextNc->PitchDifferenceTo(nc); - nc->m_drawingGlyphs.at(0).m_yOffset = pitchDifference; + assert(previousNc); + previousLig = false; + const int pitchDifference = nc->PitchDifferenceTo(previousNc); + nc->m_drawingGlyphs.at(0).m_yOffset = -pitchDifference; + previousNc->m_drawingGlyphs.at(0).m_yOffset = pitchDifference; + + // set the glyph for both the current and previous nc + switch (pitchDifference) { + case -1: + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9B9_chantLigaturaDesc2nd; + previousNc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9B4_chantEntryLineAsc2nd; + break; + case -2: + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9BA_chantLigaturaDesc3rd; + previousNc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9B5_chantEntryLineAsc3rd; + break; + case -3: + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9BB_chantLigaturaDesc4th; + previousNc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9B6_chantEntryLineAsc4th; + break; + case -4: + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9BC_chantLigaturaDesc5th; + previousNc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9B7_chantEntryLineAsc5th; + break; + default: break; } } - - // set the glyph - switch (pitchDifference) { - case -1: - nc->m_drawingGlyphs.at(0).m_fontNo - = isFirst ? SMUFL_E9B4_chantEntryLineAsc2nd : SMUFL_E9B9_chantLigaturaDesc2nd; - break; - case -2: - nc->m_drawingGlyphs.at(0).m_fontNo - = isFirst ? SMUFL_E9B5_chantEntryLineAsc3rd : SMUFL_E9BA_chantLigaturaDesc3rd; - break; - case -3: - nc->m_drawingGlyphs.at(0).m_fontNo - = isFirst ? SMUFL_E9B6_chantEntryLineAsc4th : SMUFL_E9BB_chantLigaturaDesc4th; - break; - case -4: - nc->m_drawingGlyphs.at(0).m_fontNo - = isFirst ? SMUFL_E9B7_chantEntryLineAsc5th : SMUFL_E9BC_chantLigaturaDesc5th; - break; - default: break; - } } - + // Check if nc is part of a ligature or is an inclinatum + else if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { + nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E991_chantPunctumInclinatum; + } // If the nc is supposed to be a virga and currently is being rendered as a punctum // change it to a virga - if (nc->GetTilt() == COMPASSDIRECTION_s && nc->m_drawingGlyphs.at(0).m_fontNo == SMUFL_E990_chantPunctum) { + else if (nc->GetTilt() == COMPASSDIRECTION_s + && nc->m_drawingGlyphs.at(0).m_fontNo == SMUFL_E990_chantPunctum) { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E996_chantPunctumVirga; } - else if (nc->GetTilt() == COMPASSDIRECTION_n && nc->m_drawingGlyphs.at(0).m_fontNo == SMUFL_E990_chantPunctum) { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E997_chantPunctumVirgaReversed; @@ -344,8 +334,12 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) } nc->SetDrawingXRel(xRel); - // The first glyph set the spacing - xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staff->m_drawingStaffSize, false); + // The first glyph set the spacing - unless we are starting a ligature, in which case no spacing should be added + // between the two nc + if (!previousLig) + xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staff->m_drawingStaffSize, false); + + previousNc = nc; } return FUNCTOR_SIBLINGS; From 3d827a90b5863d9d34e82606e85fcbec9a7297a1 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 11:02:19 +0200 Subject: [PATCH 310/383] Use pre-calculated offsets in DrawNcGlyphs --- src/view_neume.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/view_neume.cpp b/src/view_neume.cpp index 981ff2da5ba..2fc8879754a 100644 --- a/src/view_neume.cpp +++ b/src/view_neume.cpp @@ -237,21 +237,16 @@ void View::DrawNcGlyphs(DeviceContext *dc, Nc *nc, Staff *staff) assert(nc); assert(staff); - const int noteHeight - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_HEIGHT_TO_STAFF_SIZE_RATIO); - const int noteWidth - = (int)(m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) / NOTE_WIDTH_TO_STAFF_SIZE_RATIO); - - int noteX = nc->GetDrawingX(); - int noteY = nc->GetDrawingY(); + int ncX = nc->GetDrawingX(); + int ncY = nc->GetDrawingY(); if (staff->HasDrawingRotation()) { - noteY -= staff->GetDrawingRotationOffsetFor(noteX); + ncY -= staff->GetDrawingRotationOffsetFor(ncX); } for (auto &glyph : nc->m_drawingGlyphs) { - DrawSmuflCode(dc, noteX + glyph.m_xOffset * noteWidth, noteY + glyph.m_yOffset * noteHeight, glyph.m_fontNo, - staff->m_drawingStaffSize, false, true); + DrawSmuflCode( + dc, ncX + glyph.m_xOffset, ncY + glyph.m_yOffset, glyph.m_fontNo, staff->m_drawingStaffSize, false, true); } } From 481b32f4ec77a3d95033abe78de82aa5c8c5fec6 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 11:02:49 +0200 Subject: [PATCH 311/383] Use unit-based offset and adjust position according to the pitch difference --- src/calcligatureorneumeposfunctor.cpp | 42 ++++++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/calcligatureorneumeposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp index 3dd95d7dae5..b1832a4aa2a 100644 --- a/src/calcligatureorneumeposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -232,6 +232,9 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) ListOfObjects ncs = neume->FindAllDescendantsByType(NC); Staff *staff = neume->GetAncestorStaff(); + assert(staff); + const int staffSize = staff->m_drawingStaffSize; + const int unit = m_doc->GetDrawingUnit(staffSize); int xRel = 0; Nc *previousNc = NULL; @@ -246,26 +249,34 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) const bool hasOriscus = (nc->FindDescendantByType(ORISCUS)); const bool hasQuilisma = (nc->FindDescendantByType(QUILISMA)); + const int lineWidth = m_doc->GetGlyphWidth(SMUFL_E9BE_chantConnectingLineAsc3rd, staffSize, false); + // Make sure we have at least one glyph nc->m_drawingGlyphs.resize(1); + const int pitchDifference = (previousNc) ? nc->PitchDifferenceTo(previousNc) : 0; + bool overlapWithPrevious = (pitchDifference == 0) ? false : true; + if (hasLiquescent) { nc->m_drawingGlyphs.resize(3); + const int ncWidth = m_doc->GetGlyphWidth(SMUFL_E995_chantAuctumDesc, staffSize, false); + const int lineWidth = m_doc->GetGlyphWidth(SMUFL_E9BE_chantConnectingLineAsc3rd, staffSize, false); + if (nc->GetCurve() == curvatureDirection_CURVE_c) { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E995_chantAuctumDesc; nc->m_drawingGlyphs.at(1).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; nc->m_drawingGlyphs.at(2).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - nc->m_drawingGlyphs.at(2).m_xOffset = 0.8; - nc->m_drawingGlyphs.at(1).m_yOffset = -1.5; - nc->m_drawingGlyphs.at(2).m_yOffset = -1.75; + nc->m_drawingGlyphs.at(2).m_xOffset = ncWidth - lineWidth; + nc->m_drawingGlyphs.at(1).m_yOffset = -1.75 * unit; + nc->m_drawingGlyphs.at(2).m_yOffset = -1.9 * unit; } else if (nc->GetCurve() == curvatureDirection_CURVE_a) { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E994_chantAuctumAsc; nc->m_drawingGlyphs.at(1).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; nc->m_drawingGlyphs.at(2).m_fontNo = SMUFL_E9BE_chantConnectingLineAsc3rd; - nc->m_drawingGlyphs.at(2).m_xOffset = 0.8; - nc->m_drawingGlyphs.at(1).m_yOffset = 0.5; - nc->m_drawingGlyphs.at(2).m_yOffset = 0.75; + nc->m_drawingGlyphs.at(2).m_xOffset = ncWidth - lineWidth; + nc->m_drawingGlyphs.at(1).m_yOffset = 0.5 * unit; + nc->m_drawingGlyphs.at(2).m_yOffset = 0.75 * unit; } else { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E9A1_chantPunctumDeminutum; @@ -289,11 +300,12 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) } // This is the second else { + // No overlap in this case since the second starts at the same position as the first + overlapWithPrevious = false; assert(previousNc); previousLig = false; - const int pitchDifference = nc->PitchDifferenceTo(previousNc); - nc->m_drawingGlyphs.at(0).m_yOffset = -pitchDifference; - previousNc->m_drawingGlyphs.at(0).m_yOffset = pitchDifference; + nc->m_drawingGlyphs.at(0).m_yOffset = -pitchDifference * unit; + previousNc->m_drawingGlyphs.at(0).m_yOffset = pitchDifference * unit; // set the glyph for both the current and previous nc switch (pitchDifference) { @@ -320,6 +332,8 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) // Check if nc is part of a ligature or is an inclinatum else if (nc->HasTilt() && nc->GetTilt() == COMPASSDIRECTION_se) { nc->m_drawingGlyphs.at(0).m_fontNo = SMUFL_E991_chantPunctumInclinatum; + // No overlap with this shape + overlapWithPrevious = false; } // If the nc is supposed to be a virga and currently is being rendered as a punctum // change it to a virga @@ -333,11 +347,17 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) } } + // If the nc overlaps with the previous, move it back from a line width + if (overlapWithPrevious) { + xRel -= lineWidth; + } + nc->SetDrawingXRel(xRel); // The first glyph set the spacing - unless we are starting a ligature, in which case no spacing should be added // between the two nc - if (!previousLig) - xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staff->m_drawingStaffSize, false); + if (!previousLig) { + xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staffSize, false); + } previousNc = nc; } From 46bcbc16b34894491baca7b165bbc05b2446f7c5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 12:03:45 +0200 Subject: [PATCH 312/383] Support for `nc@loc` --- src/calcalignmentpitchposfunctor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calcalignmentpitchposfunctor.cpp b/src/calcalignmentpitchposfunctor.cpp index 5c667f5b6de..188095fc068 100644 --- a/src/calcalignmentpitchposfunctor.cpp +++ b/src/calcalignmentpitchposfunctor.cpp @@ -319,6 +319,9 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE if (nc->HasPname() && nc->HasOct()) { loc = PitchInterface::CalcLoc(nc->GetPname(), nc->GetOct(), layerY->GetClefLocOffset(nc)); } + else if (nc->HasLoc()) { + loc = nc->GetLoc(); + } int yRel = staffY->CalcPitchPosYRel(m_doc, loc); nc->SetDrawingLoc(loc); nc->SetDrawingYRel(yRel); From 8d1abce32d7692c1141ed1bbd60f3c5d64525217 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 13:47:43 +0200 Subject: [PATCH 313/383] Also consider `@loc` in the pitch difference --- include/vrv/nc.h | 10 ++++++++++ src/calcligatureorneumeposfunctor.cpp | 2 +- src/nc.cpp | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/vrv/nc.h b/include/vrv/nc.h index fcb4ae0bb69..42b60762da9 100644 --- a/include/vrv/nc.h +++ b/include/vrv/nc.h @@ -64,6 +64,16 @@ class Nc : public LayerElement, const PitchInterface *GetPitchInterface() const override { return vrv_cast(this); } ///@} + /** + * Calclulate the pitch or loc difference between to nc. + * The Pitch difference takes precedence over the loc difference. + */ + int PitchOrLocDifferenceTo(const Nc *nc) const; + + //----------// + // Functors // + //----------// + /** * Interface for class functor visitation */ diff --git a/src/calcligatureorneumeposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp index b1832a4aa2a..a13226e6cfb 100644 --- a/src/calcligatureorneumeposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -254,7 +254,7 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) // Make sure we have at least one glyph nc->m_drawingGlyphs.resize(1); - const int pitchDifference = (previousNc) ? nc->PitchDifferenceTo(previousNc) : 0; + int pitchDifference = (previousNc) ? nc->PitchOrLocDifferenceTo(previousNc) : 0; bool overlapWithPrevious = (pitchDifference == 0) ? false : true; if (hasLiquescent) { diff --git a/src/nc.cpp b/src/nc.cpp index 49b999e4302..c96a5238614 100644 --- a/src/nc.cpp +++ b/src/nc.cpp @@ -67,6 +67,15 @@ void Nc::Reset() this->ResetNcForm(); } +int Nc::PitchOrLocDifferenceTo(const Nc *nc) const +{ + int difference = this->PitchDifferenceTo(nc); + if ((difference == 0) && this->HasLoc() && nc->HasLoc()) { + difference = this->GetLoc() - nc->GetLoc(); + } + return difference; +} + FunctorCode Nc::Accept(Functor &functor) { return functor.VisitNc(this); From 31119b92285c39ae3fea42887d5886e4a9ebc161 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 15:03:08 +0200 Subject: [PATCH 314/383] Align clef in neume notation --- include/vrv/adjustxposfunctor.h | 2 ++ src/adjustxposfunctor.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/vrv/adjustxposfunctor.h b/include/vrv/adjustxposfunctor.h index 641bda82629..ffdb5380762 100644 --- a/include/vrv/adjustxposfunctor.h +++ b/include/vrv/adjustxposfunctor.h @@ -107,6 +107,8 @@ class AdjustXPosFunctor : public DocFunctor { int m_staffN; // The current staff size int m_staffSize; + // The current staff is neume + bool m_isNeumeStaff; // The list of staffN in the top-level scoreDef std::vector m_staffNs; // The bounding boxes in the previous aligner diff --git a/src/adjustxposfunctor.cpp b/src/adjustxposfunctor.cpp index 88b0cf1a031..c901a84a27c 100644 --- a/src/adjustxposfunctor.cpp +++ b/src/adjustxposfunctor.cpp @@ -126,7 +126,7 @@ FunctorCode AdjustXPosFunctor::VisitLayerElement(LayerElement *layerElement) return FUNCTOR_SIBLINGS; } - if (layerElement->GetAlignment()->GetType() == ALIGNMENT_CLEF) { + if ((layerElement->GetAlignment()->GetType() == ALIGNMENT_CLEF) && !m_isNeumeStaff) { return FUNCTOR_CONTINUE; } @@ -232,6 +232,7 @@ FunctorCode AdjustXPosFunctor::VisitMeasure(Measure *measure) m_currentAlignment.Reset(); StaffAlignment *staffAlignment = system->m_systemAligner.GetStaffAlignmentForStaffN(staffN); m_staffSize = (staffAlignment) ? staffAlignment->GetStaffSize() : 100; + m_isNeumeStaff = (staffAlignment && staffAlignment->GetStaff()) ? staffAlignment->GetStaff()->IsNeume() : false; // Prevent collisions of scoredef clefs with thick barlines if (hasSystemStartLine) { From 4075b2526723e06e2c5aa13cff69d8f1347f5ed1 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 15:04:25 +0200 Subject: [PATCH 315/383] Comment log debug --- src/alignfunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index 476bf07f10e..5c2d24bc4f3 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -394,7 +394,7 @@ FunctorCode AlignHorizontallyFunctor::VisitMeasureEnd(Measure *measure) if (m_hasMultipleLayer) measure->HasAlignmentRefWithMultipleLayers(true); - measure->m_measureAligner.LogDebugTree(3); + //measure->m_measureAligner.LogDebugTree(3); return FUNCTOR_CONTINUE; } From 84d996780a32d12db7ce15d970f0aedfce388160 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 5 Jul 2024 15:07:28 +0200 Subject: [PATCH 316/383] Fix formatting --- src/alignfunctor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index 5c2d24bc4f3..517f3ccf135 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -394,7 +394,7 @@ FunctorCode AlignHorizontallyFunctor::VisitMeasureEnd(Measure *measure) if (m_hasMultipleLayer) measure->HasAlignmentRefWithMultipleLayers(true); - //measure->m_measureAligner.LogDebugTree(3); + // measure->m_measureAligner.LogDebugTree(3); return FUNCTOR_CONTINUE; } From b325ef320ddd4cc049323d3985754bc44ef34f6b Mon Sep 17 00:00:00 2001 From: Greg Chapman <75333244+gregchapman-dev@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:11:51 -0700 Subject: [PATCH 317/383] 'maj6' should just be '6'. --- src/iohumdrum.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 8c59931f871..2c833b4a887 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -12419,6 +12419,10 @@ void HumdrumInput::setMxHarmContent(Rend *rend, const std::string &content) kind = U"+"; replacing = true; } + else if (kind == U"maj6") { + kind = U"6"; + replacing = true; + } else if (kind == U"minor-seventh") { kind = U"m7"; replacing = true; From 7fc40e1caea6f6276f297d6c5411e58be8bb8461 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 18 Jul 2024 09:08:42 +0200 Subject: [PATCH 318/383] Update Leipzig to 5.2.92 --- data/Leipzig.css | 2 +- fonts/Leipzig/Leipzig.svg | 6 ++++-- fonts/Leipzig/leipzig_metadata.json | 12 +++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/Leipzig.css b/data/Leipzig.css index 63c4ed0fe54..04da9178052 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/fonts/Leipzig/Leipzig.svg b/fonts/Leipzig/Leipzig.svg index f056ef31e1d..3610a7bf8ab 100644 --- a/fonts/Leipzig/Leipzig.svg +++ b/fonts/Leipzig/Leipzig.svg @@ -2,11 +2,11 @@ -Created by FontForge 20220308 at Sun Jun 30 17:35:16 2024 +Created by FontForge 20220308 at Thu Jul 18 09:04:00 2024 By Laurent Pugin Created by Etienne Darbellay, Jean-Francois Marti, Laurent Pugin, and Klaus Rettinghaus. This font is licensed under the SIL Open Font License \(http://scripts.sil.org/OFL\). -Version 5.2.91 +Version 5.2.92 @@ -2468,5 +2468,7 @@ c0 36 -23 66 -58 66zM140 -3c-41 0 -72 -37 -72 -80s28 -87 69 -87s68 44 68 87s-24 d="M20 190h233v-40l-195 -351l-37 1l188 350s-94 -1 -151 -2c-17.0264 -0.298828 -37 -33 -37 -33z" /> + diff --git a/fonts/Leipzig/leipzig_metadata.json b/fonts/Leipzig/leipzig_metadata.json index 2ad4f419d41..7c16701d43f 100644 --- a/fonts/Leipzig/leipzig_metadata.json +++ b/fonts/Leipzig/leipzig_metadata.json @@ -30,7 +30,7 @@ "tupletBracketThickness": 0.16 }, "fontName": "Leipzig", - "fontVersion": "5.2.91", + "fontVersion": "5.2.92", "glyphBBoxes": { "4stringTabClef": { "bBoxNE": [ @@ -5342,6 +5342,16 @@ 0.0 ] }, + "ornamentLeftVerticalStrokeWithCross": { + "bBoxNE": [ + 0.84, + 1.6 + ], + "bBoxSW": [ + 0.0, + 0.0 + ] + }, "ornamentMordent": { "bBoxNE": [ 2.344, From 51fe55bd2c26891c3cf09ff1d1076add91efcad8 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 18 Jul 2024 13:53:58 +0200 Subject: [PATCH 319/383] Add E594 and E595 glyphs --- data/Bravura.css | 2 +- data/Bravura.xml | 6 ++++++ data/Bravura/E594.xml | 1 + data/Bravura/E595.xml | 1 + data/Gootville.css | 2 +- data/Gootville.xml | 3 +++ data/Gootville/E594.xml | 1 + data/Leipzig.css | 2 +- data/Leipzig.xml | 2 ++ data/Leipzig/E594.xml | 1 + data/Leipzig/E595.xml | 1 + fonts/supported.xml | 4 ++-- include/vrv/smufl.h | 4 +++- 13 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 data/Bravura/E594.xml create mode 100644 data/Bravura/E595.xml create mode 100644 data/Gootville/E594.xml create mode 100644 data/Leipzig/E594.xml create mode 100644 data/Leipzig/E595.xml diff --git a/data/Bravura.css b/data/Bravura.css index 1ddb156c98c..0afc9c3dd89 100644 --- a/data/Bravura.css +++ b/data/Bravura.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Bravura'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Bravura.xml b/data/Bravura.xml index 381aab1252b..33d466382f7 100644 --- a/data/Bravura.xml +++ b/data/Bravura.xml @@ -555,6 +555,12 @@ + + + + + + diff --git a/data/Bravura/E594.xml b/data/Bravura/E594.xml new file mode 100644 index 00000000000..781a4ce3b8c --- /dev/null +++ b/data/Bravura/E594.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Bravura/E595.xml b/data/Bravura/E595.xml new file mode 100644 index 00000000000..67a20bd50fb --- /dev/null +++ b/data/Bravura/E595.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Gootville.css b/data/Gootville.css index 71fd3c2a269..75589fdcf41 100644 --- a/data/Gootville.css +++ b/data/Gootville.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Gootville'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Gootville.xml b/data/Gootville.xml index c248f04a4be..c6c16d27491 100644 --- a/data/Gootville.xml +++ b/data/Gootville.xml @@ -395,6 +395,9 @@ + + + diff --git a/data/Gootville/E594.xml b/data/Gootville/E594.xml new file mode 100644 index 00000000000..40ff514785c --- /dev/null +++ b/data/Gootville/E594.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig.css b/data/Leipzig.css index 04da9178052..ae82764419c 100644 --- a/data/Leipzig.css +++ b/data/Leipzig.css @@ -1,6 +1,6 @@ @font-face { font-family: 'Leipzig'; - src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); + src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); font-weight: normal; font-style: normal; } \ No newline at end of file diff --git a/data/Leipzig.xml b/data/Leipzig.xml index ad0d92c499a..4e89800f449 100644 --- a/data/Leipzig.xml +++ b/data/Leipzig.xml @@ -458,6 +458,7 @@ + @@ -816,4 +817,5 @@ + \ No newline at end of file diff --git a/data/Leipzig/E594.xml b/data/Leipzig/E594.xml new file mode 100644 index 00000000000..772e3064954 --- /dev/null +++ b/data/Leipzig/E594.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/Leipzig/E595.xml b/data/Leipzig/E595.xml new file mode 100644 index 00000000000..b83c699b59f --- /dev/null +++ b/data/Leipzig/E595.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fonts/supported.xml b/fonts/supported.xml index d72c32a4d66..d264f961307 100644 --- a/fonts/supported.xml +++ b/fonts/supported.xml @@ -1365,8 +1365,8 @@ - - + + diff --git a/include/vrv/smufl.h b/include/vrv/smufl.h index 16d44c20537..2c116e8bd14 100644 --- a/include/vrv/smufl.h +++ b/include/vrv/smufl.h @@ -312,6 +312,8 @@ enum { SMUFL_E56F_ornamentHaydn = 0xE56F, SMUFL_E583_ornamentVerticalLine = 0xE583, SMUFL_E587_ornamentSchleifer = 0xE587, + SMUFL_E594_ornamentLeftVerticalStroke = 0xE594, + SMUFL_E595_ornamentLeftVerticalStrokeWithCross = 0xE595, SMUFL_E59D_ornamentZigZagLineNoRightEnd = 0xE59D, SMUFL_E59E_ornamentZigZagLineWithRightEnd = 0xE59E, SMUFL_E5B0_ornamentPrecompSlide = 0xE5B0, @@ -656,7 +658,7 @@ enum { }; /** The number of glyphs for verification **/ -#define SMUFL_COUNT 631 +#define SMUFL_COUNT 633 } // namespace vrv From ff9cde3a5f3ea665c5eacbccc090cdb24fcb413c Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 18 Jul 2024 15:56:57 +0200 Subject: [PATCH 320/383] Reset cloned beams coord list * Fixes https://github.com/rism-digital/verovio/issues/3702 * Test-suite evaluated locally (cherry picked from commit 9dcff602abbca46e1acb55eb21b9b1d3a243e3c4) --- include/vrv/beam.h | 5 +++++ src/beam.cpp | 9 +++++++++ src/drawinginterface.cpp | 2 ++ 3 files changed, 16 insertions(+) diff --git a/include/vrv/beam.h b/include/vrv/beam.h index 54683186f3d..6f6b56482c0 100644 --- a/include/vrv/beam.h +++ b/include/vrv/beam.h @@ -295,6 +295,11 @@ class Beam : public LayerElement, std::string GetClassName() const override { return "Beam"; } ///@} + /** + * Overriding CloneReset() method to be called after copy / assignment calls. + */ + void CloneReset() override; + /** * @name Getter to interfaces */ diff --git a/src/beam.cpp b/src/beam.cpp index 6e643e4ac13..062426112f6 100644 --- a/src/beam.cpp +++ b/src/beam.cpp @@ -1619,6 +1619,15 @@ void Beam::Reset() m_stemSameas = NULL; } +void Beam::CloneReset() +{ + // Since these are owned by the beam we cloned from, empty the list + // Do it before Object::CloneReset since that one will reset the coord list + m_beamElementCoords.clear(); + + LayerElement::CloneReset(); +} + bool Beam::IsSupportedChild(Object *child) { if (child->Is(BEAM)) { diff --git a/src/drawinginterface.cpp b/src/drawinginterface.cpp index 7d4b2622981..c843d0431fb 100644 --- a/src/drawinginterface.cpp +++ b/src/drawinginterface.cpp @@ -102,6 +102,8 @@ void BeamDrawingInterface::Reset() m_beamWidth = 0; m_beamWidthBlack = 0; m_beamWidthWhite = 0; + + ClearCoords(); } int BeamDrawingInterface::GetTotalBeamWidth() const From 755d87bc42d475ed0208982278261c068dd49c44 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 18 Jul 2024 16:33:18 +0200 Subject: [PATCH 321/383] Scaffold Volpiano input class --- Verovio.xcodeproj/project.pbxproj | 16 +++++++++++ include/vrv/iovolpiano.h | 46 +++++++++++++++++++++++++++++++ src/iovolpiano.cpp | 45 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 include/vrv/iovolpiano.h create mode 100644 src/iovolpiano.cpp diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 17426c1ea5e..0f0b7a4035c 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -811,6 +811,12 @@ 4DDBBB5E1C7AE45900054AFF /* hairpin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DDBBB5A1C7AE45900054AFF /* hairpin.cpp */; }; 4DDBBCC51C2EBAE7001AB50A /* view_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DDBBCC41C2EBAE7001AB50A /* view_text.cpp */; }; 4DDBBCC61C2EBAE7001AB50A /* view_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DDBBCC41C2EBAE7001AB50A /* view_text.cpp */; }; + 4DE0198C2C495DB800B5B6BF /* iovolpiano.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DE0198B2C495DB800B5B6BF /* iovolpiano.cpp */; }; + 4DE0198D2C495E6300B5B6BF /* iovolpiano.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE0198A2C495D1F00B5B6BF /* iovolpiano.h */; }; + 4DE0198E2C495E6500B5B6BF /* iovolpiano.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE0198A2C495D1F00B5B6BF /* iovolpiano.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4DE0198F2C495E7400B5B6BF /* iovolpiano.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DE0198B2C495DB800B5B6BF /* iovolpiano.cpp */; }; + 4DE019902C495E7600B5B6BF /* iovolpiano.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DE0198B2C495DB800B5B6BF /* iovolpiano.cpp */; }; + 4DE019912C495E7700B5B6BF /* iovolpiano.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DE0198B2C495DB800B5B6BF /* iovolpiano.cpp */; }; 4DE0B9A12988070C00D4C939 /* interface.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE0B9A02988070C00D4C939 /* interface.h */; }; 4DE0B9A22988070C00D4C939 /* interface.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE0B9A02988070C00D4C939 /* interface.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DE644F51EDBEA01002FBE6C /* breath.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4DE644F41EDBEA01002FBE6C /* breath.cpp */; }; @@ -2051,6 +2057,8 @@ 4DDBBB591C7AE45900054AFF /* dynam.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dynam.cpp; path = src/dynam.cpp; sourceTree = ""; }; 4DDBBB5A1C7AE45900054AFF /* hairpin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = hairpin.cpp; path = src/hairpin.cpp; sourceTree = ""; }; 4DDBBCC41C2EBAE7001AB50A /* view_text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = view_text.cpp; path = src/view_text.cpp; sourceTree = ""; }; + 4DE0198A2C495D1F00B5B6BF /* iovolpiano.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iovolpiano.h; path = include/vrv/iovolpiano.h; sourceTree = ""; }; + 4DE0198B2C495DB800B5B6BF /* iovolpiano.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iovolpiano.cpp; path = src/iovolpiano.cpp; sourceTree = ""; }; 4DE0B9A02988070C00D4C939 /* interface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = interface.h; path = include/vrv/interface.h; sourceTree = ""; }; 4DE644F31EDBE9F8002FBE6C /* breath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = breath.h; path = include/vrv/breath.h; sourceTree = ""; }; 4DE644F41EDBEA01002FBE6C /* breath.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = breath.cpp; path = src/breath.cpp; sourceTree = ""; }; @@ -2879,6 +2887,8 @@ 8F59291A18854BF800FE51AD /* iomusxml.h */, 8F086EC4188539540037FD8E /* iopae.cpp */, 8F59291B18854BF800FE51AD /* iopae.h */, + 4DE0198B2C495DB800B5B6BF /* iovolpiano.cpp */, + 4DE0198A2C495D1F00B5B6BF /* iovolpiano.h */, ); name = io; sourceTree = ""; @@ -3378,6 +3388,7 @@ 4DEC4DD821C8295700D1D273 /* unclear.h in Headers */, 4DDBBB581C7AE43E00054AFF /* dynam.h in Headers */, 8F59295118854BF800FE51AD /* slur.h in Headers */, + 4DE0198D2C495E6300B5B6BF /* iovolpiano.h in Headers */, E78833652995007700D44B01 /* calcspanningbeamspansfunctor.h in Headers */, E75EA9FA29CC3A6B003A97A7 /* calcarticfunctor.h in Headers */, 8F59295218854BF800FE51AD /* staff.h in Headers */, @@ -3612,6 +3623,7 @@ BB4C4ABE22A932B6001F6AF0 /* label.h in Headers */, E71EF3C42975E4DC00D36264 /* resetfunctor.h in Headers */, 403B0515244F3E4D00EE4F71 /* gliss.h in Headers */, + 4DE0198E2C495E6500B5B6BF /* iovolpiano.h in Headers */, BB4C4B0A22A932C3001F6AF0 /* pghead.h in Headers */, 4DFB3E8B23ABDFDA00D688C7 /* pitchinflection.h in Headers */, E7C3AED729550190002DE5AB /* preparedatafunctor.h in Headers */, @@ -3933,6 +3945,7 @@ 4D1693F61E3A44F300569BF4 /* barline.cpp in Sources */, E7901661298BCB2C008FDB4E /* calcalignmentxposfunctor.cpp in Sources */, 400FEDD6206FA74D000D3233 /* gracegrp.cpp in Sources */, + 4DE0198F2C495E7400B5B6BF /* iovolpiano.cpp in Sources */, 4D1AC9782B6A9BB200434023 /* filereader.cpp in Sources */, 4D89F90F201771AE00A4D336 /* num.cpp in Sources */, 4D1693F71E3A44F300569BF4 /* bboxdevicecontext.cpp in Sources */, @@ -4307,6 +4320,7 @@ 4DACC9902990F29A00B55913 /* atts_critapp.cpp in Sources */, 406916DC23833770009E6B04 /* mspace.cpp in Sources */, E7A1640C29AF34750099BD6A /* adjustharmgrpsspacingfunctor.cpp in Sources */, + 4DE0198C2C495DB800B5B6BF /* iovolpiano.cpp in Sources */, 4D95D4F91D718D4A00B2B856 /* systemelement.cpp in Sources */, 4D1EB6A92A2A7C5100AF2F98 /* div.cpp in Sources */, 4DDBBB5B1C7AE45900054AFF /* dynam.cpp in Sources */, @@ -4508,6 +4522,7 @@ 4DB3D8F01F83D1A700B5FC2B /* fig.cpp in Sources */, E790165F298BCB27008FDB4E /* calcalignmentxposfunctor.cpp in Sources */, 8F3DD36718854B410051330C /* verticalaligner.cpp in Sources */, + 4DE019902C495E7600B5B6BF /* iovolpiano.cpp in Sources */, 4D1AC9792B6A9BB200434023 /* filereader.cpp in Sources */, 4D766F0220ACAD6E006875D8 /* nc.cpp in Sources */, 4DEC4D9821C81E3B00D1D273 /* expan.cpp in Sources */, @@ -4796,6 +4811,7 @@ BB4C4B9322A932E5001F6AF0 /* areaposinterface.cpp in Sources */, E7901660298BCB27008FDB4E /* calcalignmentxposfunctor.cpp in Sources */, BB4C4AA122A9328F001F6AF0 /* verticalaligner.cpp in Sources */, + 4DE019912C495E7700B5B6BF /* iovolpiano.cpp in Sources */, 4D1AC97A2B6A9BB200434023 /* filereader.cpp in Sources */, BB4C4B2F22A932CF001F6AF0 /* pedal.cpp in Sources */, 4D2E759222BC2B80004C51F0 /* tuning.cpp in Sources */, diff --git a/include/vrv/iovolpiano.h b/include/vrv/iovolpiano.h new file mode 100644 index 00000000000..a80fd649510 --- /dev/null +++ b/include/vrv/iovolpiano.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: iovolpiano.h +// Author: Laurent Pugin +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_IOVOLPIANO_H__ +#define __VRV_IOVOLPIANO_H__ + +#include +#include + +//---------------------------------------------------------------------------- + +#include "iobase.h" +#include "pugixml.hpp" +#include "vrvdef.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// VolpianoInput +//---------------------------------------------------------------------------- + +class VolpianoInput : public Input { +public: + // constructors and destructors + VolpianoInput(Doc *doc); + virtual ~VolpianoInput(); + + bool Import(const std::string &volpiano) override; + +private: + /** Parse the file */ + void ParseVolpiano(std::istream &infile); + +public: + // +private: + // +}; + +} // namespace vrv + +#endif diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp new file mode 100644 index 00000000000..05e994562cc --- /dev/null +++ b/src/iovolpiano.cpp @@ -0,0 +1,45 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: iovolpiano.cpp +// Author: Laurent Pugin +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "iovolpiano.h" + +//---------------------------------------------------------------------------- + +#include +#include +#include +#include + +//---------------------------------------------------------------------------- + +#include "doc.h" +#include "vrv.h" + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// VolpianoInput +//---------------------------------------------------------------------------- + +VolpianoInput::VolpianoInput(Doc *doc) : Input(doc) {} + +VolpianoInput::~VolpianoInput() {} + +////////////////////////////////////////////////////////////////////////// + +bool VolpianoInput::Import(const std::string &volpiano) +{ + std::istringstream in_stream(volpiano); + ParseVolpiano(in_stream); + return true; +} + +void VolpianoInput::ParseVolpiano(std::istream &infile) {} + +} // namespace vrv From 6d2e33286794dc02f1bd4d9f5ae8b2d3ef38ee76 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 19 Jul 2024 09:37:56 +0200 Subject: [PATCH 322/383] Basic structure and notes mapping --- include/vrv/iovolpiano.h | 4 +-- src/iovolpiano.cpp | 67 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/include/vrv/iovolpiano.h b/include/vrv/iovolpiano.h index a80fd649510..3b990c23985 100644 --- a/include/vrv/iovolpiano.h +++ b/include/vrv/iovolpiano.h @@ -32,9 +32,7 @@ class VolpianoInput : public Input { bool Import(const std::string &volpiano) override; private: - /** Parse the file */ - void ParseVolpiano(std::istream &infile); - + // public: // private: diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 05e994562cc..75847d2f292 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -16,7 +16,18 @@ //---------------------------------------------------------------------------- +#include "clef.h" #include "doc.h" +#include "keysig.h" +#include "layer.h" +#include "mdiv.h" +#include "measure.h" +#include "note.h" +#include "score.h" +#include "section.h" +#include "staff.h" +#include "staffdef.h" +#include "staffgrp.h" #include "vrv.h" //---------------------------------------------------------------------------- @@ -35,11 +46,59 @@ VolpianoInput::~VolpianoInput() {} bool VolpianoInput::Import(const std::string &volpiano) { - std::istringstream in_stream(volpiano); - ParseVolpiano(in_stream); + m_doc->Reset(); + m_doc->SetType(Raw); + // The mDiv + Mdiv *mdiv = new Mdiv(); + mdiv->m_visibility = Visible; + m_doc->AddChild(mdiv); + // The score + Score *score = new Score(); + mdiv->AddChild(score); + // the section + Section *section = new Section(); + score->AddChild(section); + + Staff *staff = new Staff(1); + Measure *measure = new Measure(MEASURED, 1); + Layer *layer = new Layer(); + layer->SetN(1); + + staff->AddChild(layer); + measure->AddChild(staff); + section->AddChild(measure); + + static std::map> notes = { { '9', { PITCHNAME_g, 3 } }, + { 'a', { PITCHNAME_a, 3 } }, { 'b', { PITCHNAME_b, 3 } }, { 'c', { PITCHNAME_c, 4 } }, + { 'd', { PITCHNAME_d, 4 } }, { 'e', { PITCHNAME_e, 4 } }, { 'f', { PITCHNAME_f, 4 } }, + { 'g', { PITCHNAME_g, 4 } }, { 'h', { PITCHNAME_a, 4 } }, { 'j', { PITCHNAME_b, 4 } }, + { 'k', { PITCHNAME_c, 5 } }, { 'l', { PITCHNAME_d, 5 } }, { 'm', { PITCHNAME_e, 5 } }, + { 'n', { PITCHNAME_f, 5 } }, { 'o', { PITCHNAME_g, 5 } }, { 'p', { PITCHNAME_a, 5 } }, + { 'q', { PITCHNAME_b, 5 } }, { 'r', { PITCHNAME_c, 6 } }, { 's', { PITCHNAME_d, 6 } } }; + + static std::map liquescents = { { ')', '9' }, { 'A', 'a' }, { 'B', 'b' }, { 'C', 'c' }, { 'D', 'd' }, + { 'E', 'e' }, { 'F', 'f' }, { 'G', 'g' }, { 'H', 'h' }, { 'J', 'j' }, { 'K', 'k' }, { 'L', 'l' }, { 'M', 'm' }, + { 'N', 'n' }, { 'O', 'o' }, { 'P', 'p' }, { 'Q', 'q' }, { 'R', 'r' }, { 'S', 's' } }; + + for (char ch : volpiano) { + if (notes.contains(ch)) { + auto [pitch, oct] = notes.at(ch); + } + else if (liquescents.contains(ch)) { + auto [pitch, oct] = notes.at(liquescents.at(ch)); + } + } + + // add minimal scoreDef + StaffGrp *staffGrp = new StaffGrp(); + StaffDef *staffDef = new StaffDef(); + staffDef->SetN(1); + staffGrp->AddChild(staffDef); + m_doc->GetFirstScoreDef()->AddChild(staffGrp); + + m_doc->ConvertToPageBasedDoc(); + return true; } -void VolpianoInput::ParseVolpiano(std::istream &infile) {} - } // namespace vrv From abfe798d052d68e97810b5383a8117fea3c6bfb5 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 19 Jul 2024 12:02:51 +0200 Subject: [PATCH 323/383] Add input option and very basic parsing --- include/vrv/toolkitdef.h | 1 + src/iovolpiano.cpp | 53 +++++++++++++++++++++++++++++++++++++--- src/options.cpp | 3 ++- src/toolkit.cpp | 7 ++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/include/vrv/toolkitdef.h b/include/vrv/toolkitdef.h index d8183106e0f..246a4c1bc74 100644 --- a/include/vrv/toolkitdef.h +++ b/include/vrv/toolkitdef.h @@ -20,6 +20,7 @@ enum FileFormat { PAE, ABC, DARMS, + VOLPIANO, MUSICXML, MUSICXMLHUM, MEIHUM, diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 75847d2f292..5db985db5bb 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -22,12 +22,17 @@ #include "layer.h" #include "mdiv.h" #include "measure.h" +#include "nc.h" +#include "neume.h" #include "note.h" #include "score.h" #include "section.h" #include "staff.h" #include "staffdef.h" #include "staffgrp.h" +#include "syl.h" +#include "syllable.h" +#include "text.h" #include "vrv.h" //---------------------------------------------------------------------------- @@ -60,7 +65,7 @@ bool VolpianoInput::Import(const std::string &volpiano) score->AddChild(section); Staff *staff = new Staff(1); - Measure *measure = new Measure(MEASURED, 1); + Measure *measure = new Measure(NEUMELINE, 1); Layer *layer = new Layer(); layer->SetN(1); @@ -68,6 +73,9 @@ bool VolpianoInput::Import(const std::string &volpiano) measure->AddChild(staff); section->AddChild(measure); + Syllable *syllable = NULL; + Neume *neume = NULL; + static std::map> notes = { { '9', { PITCHNAME_g, 3 } }, { 'a', { PITCHNAME_a, 3 } }, { 'b', { PITCHNAME_b, 3 } }, { 'c', { PITCHNAME_c, 4 } }, { 'd', { PITCHNAME_d, 4 } }, { 'e', { PITCHNAME_e, 4 } }, { 'f', { PITCHNAME_f, 4 } }, @@ -80,11 +88,42 @@ bool VolpianoInput::Import(const std::string &volpiano) { 'E', 'e' }, { 'F', 'f' }, { 'G', 'g' }, { 'H', 'h' }, { 'J', 'j' }, { 'K', 'k' }, { 'L', 'l' }, { 'M', 'm' }, { 'N', 'n' }, { 'O', 'o' }, { 'P', 'p' }, { 'Q', 'q' }, { 'R', 'r' }, { 'S', 's' } }; + int dashCount = 0; + for (char ch : volpiano) { - if (notes.contains(ch)) { - auto [pitch, oct] = notes.at(ch); + if (ch == '-') { + dashCount++; + if (dashCount > 1) { + neume = NULL; + } + if (dashCount > 2) { + syllable = NULL; + } + } + else if (notes.contains(ch)) { + dashCount = 0; + if (!syllable) { + syllable = new Syllable(); + layer->AddChild(syllable); + // We add an arbitrary text until spacing is fixed + Syl *syl = new Syl(); + Text *text = new Text(); + text->SetText(UTF8to32("text")); + syl->AddChild(text); + syllable->AddChild(syl); + } + if (!neume) { + neume = new Neume(); + syllable->AddChild(neume); + } + Nc *nc = new Nc(); + auto [pname, oct] = notes.at(ch); + nc->SetPname(pname); + nc->SetOct(oct); + neume->AddChild(nc); } else if (liquescents.contains(ch)) { + // Ignored for now auto [pitch, oct] = notes.at(liquescents.at(ch)); } } @@ -93,6 +132,14 @@ bool VolpianoInput::Import(const std::string &volpiano) StaffGrp *staffGrp = new StaffGrp(); StaffDef *staffDef = new StaffDef(); staffDef->SetN(1); + staffDef->SetLines(5); + // Arbitray clef + Clef *clef = new Clef(); + clef->IsAttribute(true); + clef->SetLine(3); + clef->SetShape(CLEFSHAPE_G); + staffDef->AddChild(clef); + staffDef->SetNotationtype(NOTATIONTYPE_neume); staffGrp->AddChild(staffDef); m_doc->GetFirstScoreDef()->AddChild(staffGrp); diff --git a/src/options.cpp b/src/options.cpp index 86fd84f5c47..94b640dc35b 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -914,7 +914,8 @@ Options::Options() m_baseOptions.AddOption(&m_allPages); m_inputFrom.SetInfo("Input from", - "Select input format from: \"abc\", \"darms\", \"humdrum\", \"mei\", \"pae\", \"xml\" (musicxml)"); + "Select input format from: \"abc\", \"darms\", \"humdrum\", \"mei\", \"pae\", \"volpiano\", \"xml\" " + "(musicxml)"); m_inputFrom.Init("mei"); m_inputFrom.SetKey("inputFrom"); m_inputFrom.SetShortOption('f', false); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 9e322ac1ce6..7b4edae8ea2 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -29,6 +29,7 @@ #include "iomei.h" #include "iomusxml.h" #include "iopae.h" +#include "iovolpiano.h" #include "layer.h" #include "measure.h" #include "nc.h" @@ -200,6 +201,9 @@ bool Toolkit::SetInputFrom(std::string const &inputFrom) else if (inputFrom == "darms") { m_inputFrom = DARMS; } + else if (inputFrom == "volpiano") { + m_inputFrom = VOLPIANO; + } else if ((inputFrom == "humdrum") || (inputFrom == "hum")) { m_inputFrom = HUMDRUM; } @@ -521,6 +525,9 @@ bool Toolkit::LoadData(const std::string &data) return false; #endif } + else if (inputFormat == VOLPIANO) { + input = new VolpianoInput(&m_doc); + } #ifndef NO_HUMDRUM_SUPPORT else if (inputFormat == HUMDRUM) { // LogInfo("Importing Humdrum data"); From cbd19d3fc7e2b119cc31c111bce0901f14840cf0 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 22 Jul 2024 11:40:23 +0200 Subject: [PATCH 324/383] Basic Volpiano input with stemless quarter note output --- src/iovolpiano.cpp | 61 ++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 46 deletions(-) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 5db985db5bb..b7e745794f4 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -18,21 +18,15 @@ #include "clef.h" #include "doc.h" -#include "keysig.h" #include "layer.h" #include "mdiv.h" #include "measure.h" -#include "nc.h" -#include "neume.h" #include "note.h" #include "score.h" #include "section.h" #include "staff.h" #include "staffdef.h" #include "staffgrp.h" -#include "syl.h" -#include "syllable.h" -#include "text.h" #include "vrv.h" //---------------------------------------------------------------------------- @@ -65,7 +59,7 @@ bool VolpianoInput::Import(const std::string &volpiano) score->AddChild(section); Staff *staff = new Staff(1); - Measure *measure = new Measure(NEUMELINE, 1); + Measure *measure = new Measure(MEASURED, 1); Layer *layer = new Layer(); layer->SetN(1); @@ -73,9 +67,6 @@ bool VolpianoInput::Import(const std::string &volpiano) measure->AddChild(staff); section->AddChild(measure); - Syllable *syllable = NULL; - Neume *neume = NULL; - static std::map> notes = { { '9', { PITCHNAME_g, 3 } }, { 'a', { PITCHNAME_a, 3 } }, { 'b', { PITCHNAME_b, 3 } }, { 'c', { PITCHNAME_c, 4 } }, { 'd', { PITCHNAME_d, 4 } }, { 'e', { PITCHNAME_e, 4 } }, { 'f', { PITCHNAME_f, 4 } }, @@ -88,43 +79,22 @@ bool VolpianoInput::Import(const std::string &volpiano) { 'E', 'e' }, { 'F', 'f' }, { 'G', 'g' }, { 'H', 'h' }, { 'J', 'j' }, { 'K', 'k' }, { 'L', 'l' }, { 'M', 'm' }, { 'N', 'n' }, { 'O', 'o' }, { 'P', 'p' }, { 'Q', 'q' }, { 'R', 'r' }, { 'S', 's' } }; - int dashCount = 0; - for (char ch : volpiano) { - if (ch == '-') { - dashCount++; - if (dashCount > 1) { - neume = NULL; - } - if (dashCount > 2) { - syllable = NULL; - } - } - else if (notes.contains(ch)) { - dashCount = 0; - if (!syllable) { - syllable = new Syllable(); - layer->AddChild(syllable); - // We add an arbitrary text until spacing is fixed - Syl *syl = new Syl(); - Text *text = new Text(); - text->SetText(UTF8to32("text")); - syl->AddChild(text); - syllable->AddChild(syl); - } - if (!neume) { - neume = new Neume(); - syllable->AddChild(neume); + if (notes.contains(ch) || liquescents.contains(ch)) { + const bool liquescent = liquescents.contains(ch); + if (liquescent) { + ch = liquescents.at(ch); } - Nc *nc = new Nc(); + Note *note = new Note(); + note->SetDur(DURATION_4); + note->SetStemLen(0.0); auto [pname, oct] = notes.at(ch); - nc->SetPname(pname); - nc->SetOct(oct); - neume->AddChild(nc); - } - else if (liquescents.contains(ch)) { - // Ignored for now - auto [pitch, oct] = notes.at(liquescents.at(ch)); + note->SetPname(pname); + note->SetOct(oct); + if (liquescent) { + note->SetGrace(GRACE_unknown); + } + layer->AddChild(note); } } @@ -136,10 +106,9 @@ bool VolpianoInput::Import(const std::string &volpiano) // Arbitray clef Clef *clef = new Clef(); clef->IsAttribute(true); - clef->SetLine(3); + clef->SetLine(2); clef->SetShape(CLEFSHAPE_G); staffDef->AddChild(clef); - staffDef->SetNotationtype(NOTATIONTYPE_neume); staffGrp->AddChild(staffDef); m_doc->GetFirstScoreDef()->AddChild(staffGrp); From 0efb13613fe51106471ad6bfeda887cd725ee077 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 22 Jul 2024 11:54:25 +0200 Subject: [PATCH 325/383] Read accids --- src/iovolpiano.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index b7e745794f4..8e8b5afac31 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -67,6 +67,8 @@ bool VolpianoInput::Import(const std::string &volpiano) measure->AddChild(staff); section->AddChild(measure); + data_ACCIDENTAL_WRITTEN accidVal = ACCIDENTAL_WRITTEN_NONE; + static std::map> notes = { { '9', { PITCHNAME_g, 3 } }, { 'a', { PITCHNAME_a, 3 } }, { 'b', { PITCHNAME_b, 3 } }, { 'c', { PITCHNAME_c, 4 } }, { 'd', { PITCHNAME_d, 4 } }, { 'e', { PITCHNAME_e, 4 } }, { 'f', { PITCHNAME_f, 4 } }, @@ -91,11 +93,24 @@ bool VolpianoInput::Import(const std::string &volpiano) auto [pname, oct] = notes.at(ch); note->SetPname(pname); note->SetOct(oct); + if (accidVal != ACCIDENTAL_WRITTEN_NONE) { + Accid *accid = new Accid(); + accid->SetAccid(accidVal); + accid->IsAttribute(true); + note->AddChild(accid); + accidVal = ACCIDENTAL_WRITTEN_NONE; + } if (liquescent) { note->SetGrace(GRACE_unknown); } layer->AddChild(note); } + else if (ch == 'y' || ch == 'i' || ch == 'z') { + accidVal = ACCIDENTAL_WRITTEN_f; + } + else if (ch == 'Y' || ch == 'I' || ch == 'Z') { + accidVal = ACCIDENTAL_WRITTEN_n; + } } // add minimal scoreDef From 8f2eb54f7544e94d03c9aec6bb88a342e064a005 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 22 Jul 2024 12:38:19 +0200 Subject: [PATCH 326/383] Make volpiano input un-measured --- src/iovolpiano.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 8e8b5afac31..a8818b71521 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -59,7 +59,7 @@ bool VolpianoInput::Import(const std::string &volpiano) score->AddChild(section); Staff *staff = new Staff(1); - Measure *measure = new Measure(MEASURED, 1); + Measure *measure = new Measure(UNMEASURED, 1); Layer *layer = new Layer(); layer->SetN(1); From efe94265732c2d9a8100d8f3d62fb1c0ba8b4705 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 22 Jul 2024 13:05:19 +0200 Subject: [PATCH 327/383] Adjustment to the MEI output * Remove `@dur` and `@stem.len` * Change liquescents to `@cue` --- src/iovolpiano.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index a8818b71521..70c91ccc5d2 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -88,8 +88,6 @@ bool VolpianoInput::Import(const std::string &volpiano) ch = liquescents.at(ch); } Note *note = new Note(); - note->SetDur(DURATION_4); - note->SetStemLen(0.0); auto [pname, oct] = notes.at(ch); note->SetPname(pname); note->SetOct(oct); @@ -101,7 +99,7 @@ bool VolpianoInput::Import(const std::string &volpiano) accidVal = ACCIDENTAL_WRITTEN_NONE; } if (liquescent) { - note->SetGrace(GRACE_unknown); + note->SetCue(BOOLEAN_true); } layer->AddChild(note); } From 12c22a7c76d36442a77dde6b5013b90015deb1df Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 22 Jul 2024 14:06:30 +0200 Subject: [PATCH 328/383] Update src/iovolpiano.cpp Co-authored-by: Klaus Rettinghaus --- src/iovolpiano.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 70c91ccc5d2..80bc1c37934 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -116,7 +116,7 @@ bool VolpianoInput::Import(const std::string &volpiano) StaffDef *staffDef = new StaffDef(); staffDef->SetN(1); staffDef->SetLines(5); - // Arbitray clef + // Arbitrary clef Clef *clef = new Clef(); clef->IsAttribute(true); clef->SetLine(2); From 8a03a2a22b4860fae25e55ffc7768e367c44b204 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 22 Jul 2024 14:16:39 +0200 Subject: [PATCH 329/383] Add missing values for notes and accids --- src/iovolpiano.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 80bc1c37934..bfdd67ca4f2 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -69,17 +69,18 @@ bool VolpianoInput::Import(const std::string &volpiano) data_ACCIDENTAL_WRITTEN accidVal = ACCIDENTAL_WRITTEN_NONE; - static std::map> notes = { { '9', { PITCHNAME_g, 3 } }, - { 'a', { PITCHNAME_a, 3 } }, { 'b', { PITCHNAME_b, 3 } }, { 'c', { PITCHNAME_c, 4 } }, - { 'd', { PITCHNAME_d, 4 } }, { 'e', { PITCHNAME_e, 4 } }, { 'f', { PITCHNAME_f, 4 } }, - { 'g', { PITCHNAME_g, 4 } }, { 'h', { PITCHNAME_a, 4 } }, { 'j', { PITCHNAME_b, 4 } }, - { 'k', { PITCHNAME_c, 5 } }, { 'l', { PITCHNAME_d, 5 } }, { 'm', { PITCHNAME_e, 5 } }, - { 'n', { PITCHNAME_f, 5 } }, { 'o', { PITCHNAME_g, 5 } }, { 'p', { PITCHNAME_a, 5 } }, - { 'q', { PITCHNAME_b, 5 } }, { 'r', { PITCHNAME_c, 6 } }, { 's', { PITCHNAME_d, 6 } } }; - - static std::map liquescents = { { ')', '9' }, { 'A', 'a' }, { 'B', 'b' }, { 'C', 'c' }, { 'D', 'd' }, - { 'E', 'e' }, { 'F', 'f' }, { 'G', 'g' }, { 'H', 'h' }, { 'J', 'j' }, { 'K', 'k' }, { 'L', 'l' }, { 'M', 'm' }, - { 'N', 'n' }, { 'O', 'o' }, { 'P', 'p' }, { 'Q', 'q' }, { 'R', 'r' }, { 'S', 's' } }; + static std::map> notes + = { { '8', { PITCHNAME_a, 3 } }, { '9', { PITCHNAME_g, 3 } }, { 'a', { PITCHNAME_a, 3 } }, + { 'b', { PITCHNAME_b, 3 } }, { 'c', { PITCHNAME_c, 4 } }, { 'd', { PITCHNAME_d, 4 } }, + { 'e', { PITCHNAME_e, 4 } }, { 'f', { PITCHNAME_f, 4 } }, { 'g', { PITCHNAME_g, 4 } }, + { 'h', { PITCHNAME_a, 4 } }, { 'j', { PITCHNAME_b, 4 } }, { 'k', { PITCHNAME_c, 5 } }, + { 'l', { PITCHNAME_d, 5 } }, { 'm', { PITCHNAME_e, 5 } }, { 'n', { PITCHNAME_f, 5 } }, + { 'o', { PITCHNAME_g, 5 } }, { 'p', { PITCHNAME_a, 5 } }, { 'q', { PITCHNAME_b, 5 } }, + { 'r', { PITCHNAME_c, 6 } }, { 's', { PITCHNAME_d, 6 } } }; + + static std::map liquescents = { { '(', '8' }, { ')', '9' }, { 'A', 'a' }, { 'B', 'b' }, { 'C', 'c' }, + { 'D', 'd' }, { 'E', 'e' }, { 'F', 'f' }, { 'G', 'g' }, { 'H', 'h' }, { 'J', 'j' }, { 'K', 'k' }, { 'L', 'l' }, + { 'M', 'm' }, { 'N', 'n' }, { 'O', 'o' }, { 'P', 'p' }, { 'Q', 'q' }, { 'R', 'r' }, { 'S', 's' } }; for (char ch : volpiano) { if (notes.contains(ch) || liquescents.contains(ch)) { @@ -103,10 +104,10 @@ bool VolpianoInput::Import(const std::string &volpiano) } layer->AddChild(note); } - else if (ch == 'y' || ch == 'i' || ch == 'z') { + else if (ch == 'i' || ch == 'w' || ch == 'x' || ch == 'y' || ch == 'z') { accidVal = ACCIDENTAL_WRITTEN_f; } - else if (ch == 'Y' || ch == 'I' || ch == 'Z') { + else if (ch == 'I' || ch == 'W' || ch == 'X' || ch == 'Y' || ch == 'Z') { accidVal = ACCIDENTAL_WRITTEN_n; } } From ae08e3a273de00aad407932fc54a9448df51a371 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 23 Jul 2024 10:05:33 +0200 Subject: [PATCH 330/383] Fix PAE output from Volpiano input (cherry picked from commit eb414b9b0263799acb335254ec062aa6745986d2) --- src/iopae.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/iopae.cpp b/src/iopae.cpp index 44b8e94cb81..e332f6db032 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -431,7 +431,6 @@ void PAEOutput::WriteMultiRest(MultiRest *multiRest) void PAEOutput::WriteNote(Note *note) { assert(note); - assert(m_currentMeasure); if (m_skip) return; @@ -478,13 +477,15 @@ void PAEOutput::WriteNote(Note *note) if (fermata) m_streamStringOutput << ")"; - PointingToComparison pointingToComparisonTrill(TRILL, note); - Trill *trill = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonTrill, 1)); - if (trill) m_streamStringOutput << "t"; + if (m_currentMeasure) { + PointingToComparison pointingToComparisonTrill(TRILL, note); + Trill *trill = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonTrill, 1)); + if (trill) m_streamStringOutput << "t"; - PointingToComparison pointingToComparisonTie(TIE, note); - Tie *tie = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonTie, 1)); - if (tie) m_streamStringOutput << "+"; + PointingToComparison pointingToComparisonTie(TIE, note); + Tie *tie = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonTie, 1)); + if (tie) m_streamStringOutput << "+"; + } } void PAEOutput::WriteRest(Rest *rest) @@ -613,6 +614,8 @@ void PAEOutput::WriteGrace(AttGraced *attGraced) bool PAEOutput::HasFermata(Object *object) { + if (!m_currentMeasure) return false; + PointingToComparison pointingToComparisonFermata(FERMATA, object); Fermata *fermata = vrv_cast(m_currentMeasure->FindDescendantByComparison(&pointingToComparisonFermata, 1)); From 1c02c633bc02d6e2abb9d346292b203535c9b9ae Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 8 Jul 2024 10:31:36 -0400 Subject: [PATCH 331/383] Fix nc and accid jumping after dragging staff Refs: https://github.com/DDMAL/Neon/issues/1231 --- src/editortoolkit_neume.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 50cf062f61d..341b04c5aa8 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -679,7 +679,6 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) SortStaves(); - m_doc->GetDrawingPage()->ResetAligners(); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers From fbbfe3db2245159b3a848335d761d82549aee2ae Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 30 Jul 2024 01:38:12 -0700 Subject: [PATCH 332/383] Update humlib. --- include/hum/humlib.h | 170 +++-- src/hum/humlib.cpp | 1398 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 1401 insertions(+), 167 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 6dd3ce528cb..374d43cb064 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Sun Jun 30 19:51:54 WEST 2024 +// Last Modified: Mon Jul 29 23:24:23 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -5431,6 +5431,7 @@ int main(int argc, char** argv) { \ infile.readNoRhythm(std::cin); \ } \ int status = interface.run(infile, std::cout); \ + interface.finally(); \ if (interface.hasWarning()) { \ interface.getWarning(std::cerr); \ return 0; \ @@ -5439,7 +5440,6 @@ int main(int argc, char** argv) { \ interface.getError(std::cerr); \ return -1; \ } \ - interface.finally(); \ return !status; \ } @@ -5463,24 +5463,24 @@ int main(int argc, char** argv) { \ bool status = true; \ while (instream.readSingleSegment(infiles)) { \ status &= interface.run(infiles); \ - if (interface.hasWarning()) { \ - interface.getWarning(std::cerr); \ - } \ - if (interface.hasAnyText()) { \ - interface.getAllText(std::cout); \ - } \ - if (interface.hasError()) { \ - interface.getError(std::cerr); \ - return -1; \ - } \ - if (!interface.hasAnyText()) { \ - for (int i=0; i(interface)); \ bool status = interface.run(instream); \ + interface.finally(); \ if (interface.hasWarning()) { \ interface.getWarning(std::cerr); \ } \ @@ -5512,7 +5513,6 @@ int main(int argc, char** argv) { \ interface.getError(std::cerr); \ return -1; \ } \ - interface.finally(); \ interface.clearOutput(); \ return !status; \ } @@ -5536,6 +5536,7 @@ int main(int argc, char** argv) { \ hum::HumdrumFileSet infiles; \ instream.read(infiles); \ bool status = interface.run(infiles); \ + interface.finally(); \ if (interface.hasWarning()) { \ interface.getWarning(std::cerr); \ } \ @@ -5551,7 +5552,6 @@ int main(int argc, char** argv) { \ std::cout << infiles[i]; \ } \ } \ - interface.finally(); \ interface.clearOutput(); \ return !status; \ } @@ -7536,27 +7536,28 @@ class Tool_extract : public HumTool { void fillFieldDataByNoRest (std::vector& field, std::vector& subfield, std::vector& model, const std::string& searchstring, HumdrumFile& infile, int state); + void printInterpretationForKernSpine(HumdrumFile& infile, int index); private: // global variables - int excludeQ = 0; // used with -x option - int expandQ = 0; // used with -e option - std::string expandInterp = ""; // used with -E option - int interpQ = 0; // used with -i option - std::string interps = ""; // used with -i option - int debugQ = 0; // used with --debug option - int kernQ = 0; // used with -k option - int rkernQ = 0; // used with -K option - int fieldQ = 0; // used with -f or -p option - std::string fieldstring = ""; // used with -f or -p option + bool excludeQ = false; // used with -x option + bool expandQ = false; // used with -e option + std::string expandInterp = ""; // used with -E option + bool interpQ = false; // used with -i option + std::string interps = ""; // used with -i option + bool debugQ = false; // used with --debug option + bool kernQ = false; // used with -k option + bool rkernQ = false; // used with -K option + bool fieldQ = false; // used with -f or -p option + std::string fieldstring = ""; // used with -f or -p option std::vector field; // used with -f or -p option std::vector subfield; // used with -f or -p option std::vector model; // used with -p, or -e options and similar - int countQ = 0; // used with -C option - int traceQ = 0; // used with -t option - std::string tracefile = ""; // used with -t option - int reverseQ = 0; // used with -r option + bool countQ = false; // used with -C option + bool traceQ = false; // used with -t option + std::string tracefile = ""; // used with -t option + bool reverseQ = false; // used with -r option std::string reverseInterp = "**kern"; // used with -r and -R options. // sub-spine "b" expansion model: how to generate data for a secondary // spine if the primary spine is not divided. Models are: @@ -7566,17 +7567,18 @@ class Tool_extract : public HumTool { // data. 'n' will be used for non-kern spines when 'r' is used. int submodel = 'd'; // used with -m option std::string editorialInterpretation = "yy"; - std::string cointerp = "**kern"; // used with -c option - int comodel = 0; // used with -M option + std::string cointerp = "**kern"; // used with -c option + int comodel = 0; // used with -M option std::string subtokenseparator = " "; // used with a future option - int interpstate = 0; // used -I or with -i - int grepQ = 0; // used with -g option - std::string grepString = ""; // used with -g option + int interpstate = 0; // used -I or with -i + bool grepQ = false; // used with -g option + std::string grepString = ""; // used with -g option std::string blankName = "**blank"; // used with -n option - int noEmptyQ = 0; // used with --no-empty option - int emptyQ = 0; // used with --empty option - int spineListQ = 0; // used with --spine option - int removerestQ = 0; // used with --no-rest option + bool addRestsQ = false; // used with -n option + bool noEmptyQ = false; // used with --no-empty option + bool emptyQ = false; // used with --empty option + bool spineListQ = false; // used with --spine option + bool removerestQ = false; // used with --no-rest option }; @@ -9869,6 +9871,32 @@ class Tool_ordergps : public HumTool { }; +class Tool_pbar : public HumTool { + public: + Tool_pbar (void); + ~Tool_pbar () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + protected: + void processFile (HumdrumFile& infile); + void initialize (void); + void processSpine (HTp spineStart); + void printDataLine (HumdrumFile& infile, int index); + void printLocalCommentLine (HumdrumFile& infile, int index); + void addBarLineToFollowingNoteOrRest (HTp token); + void printInvisibleBarlines (HumdrumFile& infile, int index); + void printBarLine (HumdrumFile& infile, int index); + + private: + bool m_invisibleQ = false; // used with -i option + +}; + + class Tool_pccount : public HumTool { public: @@ -10293,6 +10321,62 @@ class Tool_rid : public HumTool { }; +class Tool_rphrase : public HumTool { + public: + + class VoiceInfo { + public: + std::string name; + std::vector restsBefore; + std::vector phraseDurs; + std::vector barStarts; + std::vector phraseStartToks; + }; + + Tool_rphrase (void); + ~Tool_rphrase () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + void finally (void); + + protected: + void initialize (void); + void processFile (HumdrumFile& infile); + void fillVoiceInfo (std::vector& voiceInfo, std::vector& kstarts, HumdrumFile& infile); + void fillVoiceInfo (Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile); + void fillCollapseInfo (Tool_rphrase::VoiceInfo& collapseInfo, HumdrumFile& infile); + void printVoiceInfo (std::vector& voiceInfo); + void printVoiceInfo (Tool_rphrase::VoiceInfo& voiceInfo); + void getCompositeStates(std::vector& noteStates, HumdrumFile& infile); + std::string getCompositeLabel(HumdrumFile& infile); + void markPhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& voiceInfo); + void outputMarkedFile (HumdrumFile& infile); + void printDataLine (HumdrumFile& infile, int index); + void markLongaDurations(HumdrumFile& infile); + + private: + bool m_averageQ = false; // for -a option + bool m_allAverageQ = false; // for -A option + bool m_barlineQ = false; // for -m option + bool m_collapseQ = false; // for -c option + bool m_longaQ = false; // for -l option + bool m_filenameQ = false; // for -f option + bool m_fullFilenameQ = false; // for -F option + std::string m_filename; // for -f or -F option + bool m_sortQ = false; // for -s option + bool m_reverseSortQ = false; // for -S option + int m_pcount = 0; // for -a option + double m_sum = 0.0; // for -a option + int m_pcountCollapse= 0; // for -c option + double m_sumCollapse = 0.0; // for -c option + bool m_markQ = false; // for --mark option + +}; + + class Tool_ruthfix : public HumTool { public: Tool_ruthfix (void); diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 227c5637403..38eb3f94e01 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Sun Jun 30 19:51:54 WEST 2024 +// Last Modified: Mon Jul 29 23:24:23 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -24163,7 +24163,7 @@ void HumdrumFileContent::markBeamSpanMembers(HTp beamstart, HTp beamend) { bool HumdrumFileContent::doHandAnalysis(bool attacksOnlyQ) { HumdrumFileContent& infile = *this; vector kstarts = infile.getKernSpineStartList(); - bool status = 0; + bool status = 0; for (int i=0; i<(int)kstarts.size(); i++) { status |= doHandAnalysis(kstarts[i], attacksOnlyQ); } @@ -24426,7 +24426,7 @@ void HumdrumFileContent::fillMidiInfo(vector>>>& tr // // HumdrumFileContent::processStrandNotesForMidi -- store strand tokens/subtokens by MIDI note // in the midi track entry. -// +// // First index if track info is the MIDI note number, second is a list // of tokens for that note number, with the second value of the pair // giving the subtoken index of the note in the token. @@ -39785,7 +39785,7 @@ void MuseData::linkMusicDirections(void) { // // MuseData::getMeasureNumber -- If index == 0, return the next barnumber // minus 1. If on a measure record, return the number on that line. -// If neither, then search backwards for the last measure line (or 0 +// If neither, then search backwards for the last measure line (or 0 // index) and return the measure number for that barline (or 0 index). // @@ -39819,7 +39819,7 @@ string MuseData::getMeasureNumber(int index) { string number = md[i].getMeasureNumber(); return number; } else { - // first measure is not numbered, so return next + // first measure is not numbered, so return next // measure number minus 1. for (int j=index; j terminate) { m_error << "Error: missing option argument" << endl; + m_error << "ARGV count: " << m_argv.size() << endl; + m_error << "terminate: " << terminate << endl; + m_error << "tcount: " << tcount << endl; break; } if (isOption(m_argv[i], i)) { @@ -80160,7 +80163,7 @@ void Tool_extract::fillFieldDataByNoRest(vector& field, vector& subfie } // Go back and mark any empty spines as non-empty if they - // are in a part that contains multiple staves. I.e., only + // are in a part that contains multiple staves. I.e., only // delete a staff if all staves for the part are empty. // There should be a single *part# line at the start of the // score. @@ -80397,12 +80400,8 @@ void Tool_extract::expandSpines(vector& field, vector& subfield, vecto model.reserve(infile.getMaxTrack()*2); model.resize(0); - int allQ = 0; - if (interp.size() == 0) { - allQ = 1; - } + bool allQ = interp.empty(); - // ggg vector dummyfield; vector dummysubfield; vector dummymodel; @@ -80606,25 +80605,25 @@ void Tool_extract::processFieldEntry(vector& field, if ((firstone < 1) && (firstone != 0)) { m_error_text << "Error: range token: \"" << astring << "\"" - << " contains too small a number at start: " << firstone << endl; + << " contains too small a number at start: " << firstone << endl; m_error_text << "Minimum number allowed is " << 1 << endl; return; } if ((lastone < 1) && (lastone != 0)) { m_error_text << "Error: range token: \"" << astring << "\"" - << " contains too small a number at end: " << lastone << endl; + << " contains too small a number at end: " << lastone << endl; m_error_text << "Minimum number allowed is " << 1 << endl; return; } if (firstone > maxtrack) { m_error_text << "Error: range token: \"" << astring << "\"" - << " contains number too large at start: " << firstone << endl; + << " contains number too large at start: " << firstone << endl; m_error_text << "Maximum number allowed is " << maxtrack << endl; return; } if (lastone > maxtrack) { m_error_text << "Error: range token: \"" << astring << "\"" - << " contains number too large at end: " << lastone << endl; + << " contains number too large at end: " << lastone << endl; m_error_text << "Maximum number allowed is " << maxtrack << endl; return; } @@ -80667,13 +80666,13 @@ void Tool_extract::processFieldEntry(vector& field, if ((value < 1) && (value != 0)) { m_error_text << "Error: range token: \"" << astring << "\"" - << " contains too small a number at end: " << value << endl; + << " contains too small a number at end: " << value << endl; m_error_text << "Minimum number allowed is " << 1 << endl; return; } if (value > maxtrack) { m_error_text << "Error: range token: \"" << astring << "\"" - << " contains number too large at start: " << value << endl; + << " contains number too large at start: " << value << endl; m_error_text << "Maximum number allowed is " << maxtrack << endl; return; } @@ -80698,11 +80697,10 @@ void Tool_extract::processFieldEntry(vector& field, vector trackstarts; infile.getTrackStartList(trackstarts); - int i, j; int spine; // convert kern tracks into spine tracks: - for (i=finitsize; i<(int)field.size(); i++) { + for (int i=finitsize; i<(int)field.size(); i++) { if (field[i] > 0) { spine = ktracks[field[i]-1]->getTrack(); field[i] = spine; @@ -80710,7 +80708,7 @@ void Tool_extract::processFieldEntry(vector& field, } int startspineindex, stopspineindex; - for (i=0; i<(int)field.size(); i++) { + for (int i=0; i<(int)field.size(); i++) { newfield.push_back(field[i]); // copy **kern spine index into new list newsubfield.push_back(subfield[i]); newmodel.push_back(model[i]); @@ -80718,7 +80716,7 @@ void Tool_extract::processFieldEntry(vector& field, // search for non **kern spines after specified **kern spine: startspineindex = field[i] + 1 - 1; stopspineindex = maxtrack; - for (j=startspineindex; jisKern()) { break; } @@ -80818,6 +80816,7 @@ void Tool_extract::extractFields(HumdrumFile& infile, vector& field, int subtarget; int modeltarget; string spat; + bool foundBarline = true; for (int i=0; i& field, continue; } + if (infile[i].isBarline()) { + foundBarline = true; + } + start = 0; for (int t=0; t<(int)field.size(); t++) { target = field[t]; @@ -80837,17 +80840,17 @@ void Tool_extract::extractFields(HumdrumFile& infile, vector& field, modeltarget = model[t]; if (modeltarget == 0) { switch (subtarget) { - case 'a': - case 'b': - modeltarget = submodel; - break; - case 'c': - modeltarget = comodel; + case 'a': + case 'b': + modeltarget = submodel; + break; + case 'c': + modeltarget = comodel; } } if (target == 0) { if (start != 0) { - m_humdrum_text << '\t'; + m_humdrum_text << '\t'; } start = 1; if (!infile[i].isManipulator()) { @@ -80856,66 +80859,91 @@ void Tool_extract::extractFields(HumdrumFile& infile, vector& field, } else if (infile[i].isBarline()) { m_humdrum_text << infile[i].token(0); } else if (infile[i].isData()) { - m_humdrum_text << "."; - // interpretations handled in dealWithSpineManipulators() - // [obviously not, so adding a blank one here + if (foundBarline) { + if (addRestsQ) { + HumNum dur = infile[i].getDurationToBarline(); + m_humdrum_text << Convert::durationToRecip(dur); + } else { + m_humdrum_text << "."; + } + } else { + m_humdrum_text << "."; + } + // interpretations handled in dealWithSpineManipulators() + // [obviously not, so adding a blank one here } else if (infile[i].isInterpretation()) { - m_humdrum_text << "*"; - } + HTp token = infile.token(i, 0); + if (token->isExpansionLabel()) { + m_humdrum_text << token; + } else if (token->isExpansionList()) { + m_humdrum_text << token; + } else { + if (addRestsQ) { + printInterpretationForKernSpine(infile, i); + } else { + m_humdrum_text << "*"; + } + } + } } } else { for (int j=0; jgetTrack() != target) { - continue; - } - switch (subtarget) { - case 'a': - getSearchPat(spat, target, "a"); - if (hre.search(infile.token(i,j)->getSpineInfo(), spat) || - !hre.search(infile.token(i, j)->getSpineInfo(), "\\(")) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(i, j); - } - break; - case 'b': - getSearchPat(spat, target, "b"); - if (hre.search(infile.token(i, j)->getSpineInfo(), spat)) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(i, j); - } else if (!hre.search(infile.token(i, j)->getSpineInfo(), - "\\(")) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - dealWithSecondarySubspine(field, subfield, model, t, - infile, i, j, modeltarget); - } - break; - case 'c': - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - dealWithCospine(field, subfield, model, t, infile, i, j, - modeltarget, modeltarget, cointerp); - break; - default: - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(i, j); - } + if (infile[i].token(j)->getTrack() != target) { + continue; + } + switch (subtarget) { + case 'a': + getSearchPat(spat, target, "a"); + if (hre.search(infile.token(i,j)->getSpineInfo(), spat) || + !hre.search(infile.token(i, j)->getSpineInfo(), "\\(")) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } + break; + case 'b': + getSearchPat(spat, target, "b"); + if (hre.search(infile.token(i, j)->getSpineInfo(), spat)) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } else if (!hre.search(infile.token(i, j)->getSpineInfo(), + "\\(")) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + dealWithSecondarySubspine(field, subfield, model, t, + infile, i, j, modeltarget); + } + break; + case 'c': + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + dealWithCospine(field, subfield, model, t, infile, i, j, + modeltarget, modeltarget, cointerp); + break; + default: + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } } } } + + if (infile[i].isData()) { + foundBarline = false; + } + if (start != 0) { m_humdrum_text << endl; } @@ -80924,6 +80952,66 @@ void Tool_extract::extractFields(HumdrumFile& infile, vector& field, +////////////////////////////// +// +// Tool_extract::printInterpretationForKernSpine -- +// + +void Tool_extract::printInterpretationForKernSpine(HumdrumFile& infile, int index) { + HTp kerntok = NULL; + for (int j=0; jisKern()) { + continue; + } + kerntok = token; + break; + } + + if (kerntok == NULL) { + m_humdrum_text << "*"; + return; + } + + if (*kerntok == "*") { + m_humdrum_text << kerntok; + return; + } + + if (kerntok->isKeySignature()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isKeyDesignation()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isTimeSignature()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isMensurationSymbol()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isTempo()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isInstrumentName()) { + m_humdrum_text << "*I\""; + return; + } + if (kerntok->isInstrumentAbbreviation()) { + m_humdrum_text << "*I'"; + return; + } + + m_humdrum_text << "*"; +} + + + ////////////////////////////// // // Tool_extract::dealWithCospine -- extract the required token(s) from a co-spine. @@ -81024,7 +81112,7 @@ void Tool_extract::dealWithCospine(vector& field, vector& subfield, ve if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) || (infile.token(line, j)->getSpineInfo().find(buff) != string::npos)) { printCotokenInfo(start, infile, line, j, cotokens, spineindex, - subspineindex); + subspineindex); } } else if (subfield[i] == 'b') { // this section may need more work... @@ -81032,7 +81120,7 @@ void Tool_extract::dealWithCospine(vector& field, vector& subfield, ve if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) || (strstr(infile.token(line, j)->getSpineInfo().c_str(), buff.c_str()) != NULL)) { printCotokenInfo(start, infile, line, j, cotokens, spineindex, - subspineindex); + subspineindex); } } else { printCotokenInfo(start, infile, line, j, cotokens, spineindex, @@ -81306,18 +81394,18 @@ void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, if (infile[line].token(j)->getTrack() != target) { continue; } - // filter by subfield - if (subtarget == 'a') { - getSearchPat(spat, target, "b"); - if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { + // filter by subfield + if (subtarget == 'a') { + getSearchPat(spat, target, "b"); + if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { continue; - } - } else if (subtarget == 'b') { - getSearchPat(spat, target, "a"); - if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { - continue; - } - } + } + } else if (subtarget == 'b') { + getSearchPat(spat, target, "a"); + if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { + continue; + } + } switch (subtarget) { case 'a': @@ -81338,14 +81426,14 @@ void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, (spinepat == spat)) { storeToken(tempout, "*"); } else { - getSearchPat(spat, target, "b"); - if ((spinepat == spat) && - (*infile.token(line, j) == "*v")) { - // do nothing - suppress = 1; - } else { - storeToken(tempout, *infile.token(line, j)); - } + getSearchPat(spat, target, "b"); + if ((spinepat == spat) && + (*infile.token(line, j) == "*v")) { + // do nothing + suppress = 1; + } else { + storeToken(tempout, *infile.token(line, j)); + } } } @@ -81354,9 +81442,9 @@ void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) { if (*infile.token(line, j) == "*^") { - storeToken(tempout, "*"); + storeToken(tempout, "*"); } else { - storeToken(tempout, *infile.token(line, j)); + storeToken(tempout, *infile.token(line, j)); } } else { getSearchPat(spat, target, "b"); @@ -81365,17 +81453,17 @@ void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, hre.replaceDestructive(spinepat, "\\)", "\\)", "g"); if ((*infile.token(line, j) == "*v") && - (spinepat == spat)) { - storeToken(tempout, "*"); + (spinepat == spat)) { + storeToken(tempout, "*"); } else { - getSearchPat(spat, target, "a"); - if ((spinepat == spat) && - (*infile.token(line, j) == "*v")) { - // do nothing - suppress = 1; - } else { - storeToken(tempout, *infile.token(line, j)); - } + getSearchPat(spat, target, "a"); + if ((spinepat == spat) && + (*infile.token(line, j) == "*v")) { + // do nothing + suppress = 1; + } else { + storeToken(tempout, *infile.token(line, j)); + } } } @@ -81878,19 +81966,19 @@ void Tool_extract::initialize(HumdrumFile& infile) { } if (interpQ) { - fieldQ = 1; + fieldQ = true; } if (emptyQ) { - fieldQ = 1; + fieldQ = true; } if (noEmptyQ) { - fieldQ = 1; + fieldQ = true; } if (expandQ) { - fieldQ = 1; + fieldQ = true; expandInterp = getString("expand-interp"); } @@ -81902,7 +81990,7 @@ void Tool_extract::initialize(HumdrumFile& infile) { } if (reverseQ) { - fieldQ = 1; + fieldQ = true; } if (excludeQ) { @@ -81911,15 +81999,15 @@ void Tool_extract::initialize(HumdrumFile& infile) { fieldstring = getString("f"); } else if (kernQ) { fieldstring = getString("k"); - fieldQ = 1; + fieldQ = true; } else if (rkernQ) { fieldstring = getString("K"); - fieldQ = 1; + fieldQ = true; fieldstring = reverseFieldString(fieldstring, infile.getMaxTrack()); } spineListQ = getBoolean("spine-list"); - grepQ = getBoolean("grep"); + grepQ = getBoolean("grep"); grepString = getString("grep"); if (getBoolean("name")) { @@ -81933,6 +82021,9 @@ void Tool_extract::initialize(HumdrumFile& infile) { blankName = "*" + blankName; } } + if (blankName == "**kern") { + addRestsQ = true; + } } } @@ -83159,6 +83250,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(nproof, infile, commands[i].second, status); } else if (commands[i].first == "ordergps") { RUNTOOL(ordergps, infile, commands[i].second, status); + } else if (commands[i].first == "pbar") { + RUNTOOL(pbar, infile, commands[i].second, status); } else if (commands[i].first == "phrase") { RUNTOOL(phrase, infile, commands[i].second, status); } else if (commands[i].first == "pline") { @@ -112712,6 +112805,274 @@ void Tool_ordergps::printStaffLine(HumdrumFile& infile) { +///////////////////////////////// +// +// Tool_pbar::Tool_pbar -- Set the recognized options for the tool. +// + +Tool_pbar::Tool_pbar(void) { + define("i|invisible-barlines=b", "make barlines invisible"); +} + + + +////////////////////////////// +// +// Tool_pbar::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. +// + +void Tool_pbar::initialize(void) { + m_invisibleQ = getBoolean("invisible-barlines"); +} + + + +///////////////////////////////// +// +// Tool_pbar::run -- Do the main work of the tool. +// + +bool Tool_pbar::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i kstarts = infile.getKernSpineStartList(); + for (int i=0; i<(int)kstarts.size(); i++) { + processSpine(kstarts[i]); + } + + for (int i=0; igetValue("auto", "pbar"); + if (value == "true") { + hasBarline = true; + break; + } + } + + if (hasBarline) { + for (int j=0; jgetValue("auto", "pbar"); + if (value == "true") { + m_humdrum_text << "*bar"; + } else { + m_humdrum_text << "*"; + } + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << "\n"; + } +} + + + +/////////////////////////////// +// +// Tool_pbar::printLocalCommentLine -- +// + +void Tool_pbar::printLocalCommentLine(HumdrumFile& infile, int index) { + HumRegex hre; + bool hasKp = false; + bool hasOther = false; + for (int i=0; iisLocalComment()) { + current = current->getNextToken(); + continue; + } + if (hre.search(current, "kreska\\s*pseudotaktowa")) { + addBarLineToFollowingNoteOrRest(current); + } + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_pbar::addBarLineToFollowingNoteOrRest -- +// + +void Tool_pbar::addBarLineToFollowingNoteOrRest(HTp token) { + HTp current = token->getNextToken(); + int counter = 0; + while (current) { + if (!current->isBarline()) { + if (!current->isData() || current->isNull()) { + current = current->getNextToken(); + continue; + } + } + counter++; + if (counter == 2) { + current->setValue("auto", "pbar", "true"); + break; + } + current = current->getNextToken(); + } +} + + + + ///////////////////////////////// // // Tool_gridtest::Tool_pccount -- Set the recognized options for the tool. @@ -117735,6 +118096,795 @@ void Tool_rid::processFile(HumdrumFile& infile) { +///////////////////////////////// +// +// Tool_rphrase::Tool_rphrase -- Set the recognized options for the tool. +// + +Tool_rphrase::Tool_rphrase(void) { + // add command-line options here + define("a|average=b", "calculate average length of rest-phrases by score"); + define("A|all-average=b", "calculate average length of rest-phrases for all scores"); + define("c|composite|collapse=b", "collapse all voices into single part"); + define("f|filename=b", "include filename in output analysis"); + define("F|full-filename=b", "include full filename location in output analysis"); + define("l|longa=b", "display minim length of longas"); + define("m|b|measure|barline=b", "include barline numbers in output analysis"); + define("mark=b", "mark starts of phrases in score"); + define("s|sort=b", "sort phrases by short to long length"); + define("S|reverse-sort=b", "sort phrases by long to short length"); +} + + + +///////////////////////////////// +// +// Tool_rphrase::run -- Do the main work of the tool. +// + +bool Tool_rphrase::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i kernStarts = infile.getKernSpineStartList(); + vector voiceInfo(kernStarts.size()); + Tool_rphrase::VoiceInfo collapseInfo; + + if (m_collapseQ) { + fillCollapseInfo(collapseInfo, infile); + } else { + fillVoiceInfo(voiceInfo, kernStarts, infile); + } + + + if (m_longaQ) { + markLongaDurations(infile); + } + + if (!m_allAverageQ) { + if (m_collapseQ) { + printVoiceInfo(collapseInfo); + } else { + printVoiceInfo(voiceInfo); + } + } + + if (m_markQ) { + outputMarkedFile(infile); + } + +} + + + +////////////////////////// +// +// Tool_rphrase::markLongaDuratios -- +// + +void Tool_rphrase::markLongaDurations(HumdrumFile& infile) { + string longrdf; + for (int i=0; iisKern()) { + continue; + } + if (token->find(longrdf) != string::npos) { + HumNum duration = token->getTiedDuration(); + stringstream value; + value.str(""); + value << duration.getFloat() / 2.0; + token->setValue("auto", "rphrase-longa", value.str()); + } + } + } +} + + + +////////////////////////////// +// +// Tool_rphrase::outputMarkedFile -- +// + +void Tool_rphrase::outputMarkedFile(HumdrumFile& infile) { + m_free_text.clear(); + m_free_text.str(""); + for (int i=0; iisKern()) { + continue; + } + string lotext = token->getValue("auto", "rphrase-longa"); + if (!lotext.empty()) { + hasLonga = true; + break; + } + } + } + + + bool hasLo = false; + for (int j=0; jisKern()) { + continue; + } + string lotext = token->getValue("auto", "rphrase-start"); + if (!lotext.empty()) { + hasLo = true; + break; + } + } + + if (hasLonga) { + for (int j=0; jisKern()) { + m_humdrum_text << "!"; + } else { + string value = token->getValue("auto", "rphrase-longa"); + if (value.empty()) { + m_humdrum_text << "!"; + } else { + m_humdrum_text << "!LO:TX:a:B:color=silver:t=" << value; + } + } + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; + } + + if (hasLo) { + for (int j=0; jisKern()) { + m_humdrum_text << "!"; + } else { + string value = token->getValue("auto", "rphrase-start"); + if (value.empty()) { + m_humdrum_text << "!"; + } else { + m_humdrum_text << "!LO:TX:a:B:color=red:t=" << value; + } + } + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; + } + } + m_humdrum_text << endl; + } + + m_humdrum_text << infile[index] << endl; +} + + + +////////////////////////////// +// +// Tool_rphrase::getCompositeStates -- +// + +void Tool_rphrase::getCompositeStates(vector& noteStates, HumdrumFile& infile) { + noteStates.resize(infile.getLineCount()); + fill(noteStates.begin(), noteStates.end(), -1); + for (int i=0; iisKern()) { + continue; + } + if (token->isRest()) { + continue; + } else if (token->isNull()) { + HTp resolve = token->resolveNull(); + if (!resolve) { + continue; + } else if (resolve->isRest()) { + continue; + } else { + value = 1; + break; + } + } else { + value = 1; + break; + } + } + noteStates[i] = value; + } +} + + + +////////////////////////////// +// +// Tool_rphrase::printVoiceInfo -- +// + +void Tool_rphrase::printVoiceInfo(vector& voiceInfo) { + for (int i=(int)voiceInfo.size() - 1; i>=0; i--) { + printVoiceInfo(voiceInfo[i]); + } +} + + +void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) { + if (m_filenameQ) { + m_free_text << m_filename << "\t"; + } + m_free_text << voiceInfo.name << "\t"; + + if (m_averageQ) { + double sum = 0; + int count = 0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + count++; + sum += voiceInfo.phraseDurs.at(i); + } + m_free_text << int(sum / count * 100.0 + 0.5)/100.0 << "\t"; + } + + if (m_sortQ || m_reverseSortQ) { + vector> sortList; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + sortList.emplace_back(voiceInfo.phraseDurs[i], i); + } + if (m_sortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + } else if (m_reverseSortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + } + + for (int i=0; i<(int)sortList.size(); i++) { + int ii = sortList[i].second; + if (m_barlineQ) { + m_free_text << "m" << voiceInfo.barStarts.at(ii) << ":"; + } + m_free_text << voiceInfo.phraseDurs.at(ii); + if (i < (int)sortList.size() - 1) { + m_free_text << " "; + } + } + } else { + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.restsBefore.at(i) > 0) { + m_free_text << "r:" << voiceInfo.restsBefore.at(i) << " "; + } + if (m_barlineQ) { + m_free_text << "m" << voiceInfo.barStarts.at(i) << ":"; + } + m_free_text << voiceInfo.phraseDurs.at(i); + if (i < (int)voiceInfo.phraseDurs.size() - 1) { + m_free_text << " "; + } + } + } + + m_free_text << endl; +} + + + +////////////////////////////// +// +// Tool_rphrase::fillCollapseInfo -- +// + +void Tool_rphrase::fillCollapseInfo(Tool_rphrase::VoiceInfo& collapseInfo, HumdrumFile& infile) { + collapseInfo.name = getCompositeLabel(infile); + vector noteStates; + getCompositeStates(noteStates, infile); + + bool inPhraseQ = false; + int currentBarline = 0; + int startBarline = 1; + HumNum startTime = 0; + + HumNum restBefore = 0; + HumNum startTimeRest = 0; + + HumNum scoreDur = infile.getScoreDuration(); + + for (int i=0; ifind("||") != string::npos) { + HumNum tdur = token->getDurationFromStart(); + if (tdur != scoreDur) { + // Only process if double barline is not at the end of the score. + + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + + // ending a phrase + HumNum endTime = infile[i].getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + collapseInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + collapseInfo.barStarts.push_back(startBarline); + m_sumCollapse += duration.getFloat() / 2.0; + m_pcountCollapse++; + collapseInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + + // record rest start + startTimeRest = endTime; + } else { + // Not in phrase, so not splitting a rest region. + // This case should be rare (starting a medial cadence + // with rests and potentially starting new section with rests. + } + + } + } + } + + if (infile[i].isBarline()) { + HTp token = infile.token(i, 0); + HumRegex hre; + if (hre.search(token, "(\\d+)")) { + currentBarline = hre.getMatchInt(1); + continue; + } + } + + + if (!infile[i].isData()) { + continue; + } + + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + if (noteStates[i] == 0) { + // ending a phrase + HumNum endTime = infile[i].getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + collapseInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + collapseInfo.barStarts.push_back(startBarline); + m_sumCollapse += duration.getFloat() / 2.0; + m_pcountCollapse++; + collapseInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + // record rest start + startTimeRest = endTime; + } else { + // continuing a phrase, so do nothing + } + } else { + // Not in phrase, so continue if rest; otherwise, + // if a note, then record a phrase start. + if (noteStates[i] == 0) { + // continuing a non-phrase, so do nothing + } else { + // starting a phrase + startTime = infile[i].getDurationFromStart(); + startBarline = currentBarline; + inPhraseQ = true; + // check if there are rests before the phrase + // The rest duration will be stored when the + // end of the next phrase is encountered. + if (startTimeRest >= 0) { + restBefore = startTime - startTimeRest; + } else { + restBefore = 0; + } + } + } + + } + + if (inPhraseQ) { + // process last phrase + HumNum endTime = infile.getScoreDuration(); + HumNum duration = endTime - startTime; + collapseInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + collapseInfo.barStarts.push_back(startBarline); + m_sumCollapse += duration.getFloat() / 2.0; + m_pcountCollapse++; + collapseInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + } +} + + + +////////////////////////////// +// +// Tool_rphrase::getCompositeLabel -- +// + +string Tool_rphrase::getCompositeLabel(HumdrumFile& infile) { + string voices; + for (int i=0; i kstarts = infile.getKernSpineStartList(); + + string output = "composite "; + output += voices; + + + HumRegex hre; + + if (hre.search(voices, "^\\d+$")) { + int vint = stoi(voices); + if (vint != (int)kstarts.size()) { + output += "("; + output += to_string(kstarts.size()); + output += ")"; + } + } else { + output += "("; + output += to_string(kstarts.size()); + output += ")"; + } + + return output; +} + + + +////////////////////////////// +// +// Tool_rphrase::fillVoiceInfo -- +// + +void Tool_rphrase::fillVoiceInfo(vector& voiceInfo, + vector& kstarts, HumdrumFile& infile) { + for (int i=0; i<(int)kstarts.size(); i++) { + fillVoiceInfo(voiceInfo.at(i), kstarts.at(i), infile); + } +} + + + +void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile) { + HTp current = kstart; + + bool inPhraseQ = false; + int currentBarline = 0; + int startBarline = 1; + HumNum startTime = 0; + + HumNum restBefore = 0; + HumNum startTimeRest = 0; + + HumNum scoreDur = infile.getScoreDuration(); + HTp phraseStartTok = NULL; + + while (current) { + + // Split phrases at double barlines (medial cadences): + if (infile[current->getLineIndex()].isBarline()) { + HTp token = infile.token(current->getLineIndex(), 0); + if (token->find("||") != string::npos) { + HumNum tdur = token->getDurationFromStart(); + if (tdur != scoreDur) { + // Only process if double barline is not at the end of the score. + + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + + HumNum endTime = current->getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + voiceInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + voiceInfo.barStarts.push_back(startBarline); + voiceInfo.phraseStartToks.push_back(phraseStartTok); + phraseStartTok = NULL; + m_sum += duration.getFloat() / 2.0; + m_pcount++; + voiceInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + + // record rest start + startTimeRest = endTime; + } else { + // Not in phrase, so not splitting a rest region. + // This case should be rare (starting a medial cadence + // with rests and potentially starting new section with rests. + } + + } + } + } + + if (current->isBarline()) { + HumRegex hre; + if (hre.search(current, "(\\d+)")) { + currentBarline = hre.getMatchInt(1); + current = current->getNextToken(); + continue; + } + } + + if (current->isInstrumentName()) { + voiceInfo.name = current->substr(3); + } + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + if (current->isRest()) { + // ending a phrase + HumNum endTime = current->getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + voiceInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + voiceInfo.barStarts.push_back(startBarline); + voiceInfo.phraseStartToks.push_back(phraseStartTok); + phraseStartTok = NULL; + m_sum += duration.getFloat() / 2.0; + m_pcount++; + voiceInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + // record rest start + startTimeRest = endTime; + } else { + // continuing a phrase, so do nothing + } + } else { + // Not in phrase, so continue if rest; otherwise, + // if a note, then record a phrase start. + if (current->isRest()) { + // continuing a non-phrase, so do nothing + } else { + // starting a phrase + startTime = current->getDurationFromStart(); + startBarline = currentBarline; + inPhraseQ = true; + // check if there are rests before the phrase + // The rest duration will be stored when the + // end of the next phrase is encountered. + if (startTimeRest >= 0) { + restBefore = startTime - startTimeRest; + } else { + restBefore = 0; + } + phraseStartTok = current; + } + } + + current = current->getNextToken(); + } + if (inPhraseQ) { + // process last phrase + HumNum endTime = kstart->getLine()->getOwner()->getScoreDuration(); + HumNum duration = endTime - startTime; + voiceInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + voiceInfo.barStarts.push_back(startBarline); + voiceInfo.phraseStartToks.push_back(phraseStartTok); + m_sum += duration.getFloat() / 2.0; + m_pcount++; + voiceInfo.restsBefore.push_back(0.0); + voiceInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + } + + if (m_markQ) { + markPhraseStartsInScore(infile, voiceInfo); + } +} + + +////////////////////////////// +// +// Tool_rphrase::markPhraseStartsInScore -- +// + +void Tool_rphrase::markPhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& voiceInfo) { + stringstream buffer; + for (int i=0; i<(int)voiceInfo.phraseStartToks.size(); i++) { + HTp tok = voiceInfo.phraseStartToks.at(i); + string measure = ""; + if (m_barlineQ) { + measure = to_string(voiceInfo.barStarts.at(i)); + } + double duration = voiceInfo.phraseDurs.at(i); + buffer.str(""); + if (!measure.empty()) { + buffer << "m" << measure << ":"; + } + buffer << duration; + tok->setValue("auto", "rphrase-start", buffer.str()); + } +} + + + + ///////////////////////////////// // // Tool_ruthfix::Tool_ruthfix -- Set the recognized options for the tool. From 24be976d6801298c74103c8bf90bfc8a1d417494 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 30 Jul 2024 01:38:21 -0700 Subject: [PATCH 333/383] Add more instrument to automatic humdrum instrument names for staves. --- include/vrv/iohumdrum.h | 1 + src/iohumdrum.cpp | 329 +++++++++++++++++++++++++++++++--------- 2 files changed, 262 insertions(+), 68 deletions(-) diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index 92795b20182..98d356f904e 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -900,6 +900,7 @@ class HumdrumInput : public vrv::Input { bool checkIfReversedSpineOrder(std::vector &staffstarts); bool hasOmdText(int startline, int endline); void processMeiOptions(hum::HumdrumFile &infile); + std::string getInstrumentNumber(hum::HTp icode); // header related functions: /////////////////////////////////////////// void createHeader(); diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 2c833b4a887..3d6247b9b96 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -8070,76 +8070,238 @@ bool HumdrumInput::isBlackNotation(hum::HTp starting) std::string HumdrumInput::getLabelFromInstrumentCode(hum::HTp icode, const std::string &transpose) { - std::string output; - std::string name = icode->substr(2); - if (name == "piano") { - output = "Piano"; - } - else if (name == "flt") { - output = "Flute"; - } - else if (name == "picco") { - output = "Piccolo"; - } - else if (name == "oboe") { - output = "Oboe"; - } - else if (name == "clars") { - output = "Clarinet"; - } - else if (name == "clara") { - output = "Alto Clarinet"; - } - else if (name == "clarb") { - output = "Bass Clarinet"; - } - else if (name == "fagot") { - output = "Bassoon"; - } - else if (name == "fagot") { - output = "Bassoon"; - } - else if (name == "tromp") { - output = "Trumpet"; - } - else if (name == "tromb") { - output = "Trombone"; - } - else if (name == "violin") { - // Deal with Violin 1 versus Violin 2, but need more info to do that. - output = "Violin"; - } - else if (name == "viola") { - output = "Viola"; - } - else if (name == "cello") { - output = "Violoncello"; - } - else if (name == "cemba") { - output = "Harpsichord"; - } - else if (name == "organ") { - output = "Organ"; - } - else if (name == "clavi") { - output = "Clavichord"; - } - else if (name == "forte") { - output = "Fortepiano"; - } - else if (name == "guitr") { - output = "Guitar"; - } - else if (name == "cbass") { - output = "Contrabass"; - } - else if (name == "koto") { - output = "Koto"; - } + static std::map codeToLabel; + if (codeToLabel.empty()) { + codeToLabel["piano"] = "Piano"; + codeToLabel["accor"] = "Accordion"; + codeToLabel["alto"] = "Alto"; + codeToLabel["anvil"] = "Anvil"; + codeToLabel["archl"] = "Archlute"; + codeToLabel["armon"] = "Harmonic"; + codeToLabel["arpa"] = "Harp"; + codeToLabel["bagpI"] = "Irish bagpipe"; + codeToLabel["bagpS"] = "Scottish bagpipe"; + codeToLabel["banjo"] = "Banjo"; + codeToLabel["bansu"] = "Bansuri"; + codeToLabel["barit"] = "Baritone"; + codeToLabel["mbari"] = "High baritone"; + codeToLabel["baset"] = "Bassett horn"; + codeToLabel["bass"] = "Bass"; + codeToLabel["bdrum"] = "Bass drum"; + codeToLabel["bongo"] = "Bongo"; + codeToLabel["bguit"] = "Bass guitar"; + codeToLabel["biwa"] = "Biwa"; + codeToLabel["bscan"] = "Singing bass"; + codeToLabel["bspro"] = "Basso profondo"; + codeToLabel["brush"] = "Brush"; + codeToLabel["calam"] = "Chalumeau"; + codeToLabel["calpe"] = "Calliope"; + codeToLabel["calto"] = "Contralto"; + codeToLabel["campn"] = "Bells"; + codeToLabel["cangl"] = "English horn"; + codeToLabel["canto"] = "Canto"; + codeToLabel["caril"] = "Carillon"; + codeToLabel["castr"] = "Castrato"; + codeToLabel["casts"] = "Castanets"; + codeToLabel["cbass"] = "Contrabass"; + codeToLabel["cello"] = "Violoncello"; + codeToLabel["cemba"] = "Harpsichord"; + codeToLabel["cetra"] = "Cittern"; + codeToLabel["chain"] = "Chain"; + codeToLabel["chime"] = "Tubular bells"; + codeToLabel["chcym"] = "China cymbal"; + codeToLabel["chlma"] = "Soprano shawm"; + codeToLabel["chlmt"] = "Tenor shawm"; + codeToLabel["clap"] = "Hand clap"; + codeToLabel["clara"] = "Alto Clarinet"; + codeToLabel["clarb"] = "Bass Clarinet"; + codeToLabel["claro"] = "Sopranino clarinet"; + codeToLabel["clarp"] = "Piccolo clarinet"; + codeToLabel["clars"] = "Clarinet"; + codeToLabel["clave"] = "Claves"; + codeToLabel["clavi"] = "Clavichord"; + codeToLabel["clest"] = "Celesta"; + codeToLabel["clrno"] = "Clarino"; + codeToLabel["colsp"] = "Coloratura soprano"; + codeToLabel["conga"] = "Conga"; + codeToLabel["cor"] = "Horn"; + codeToLabel["cornm"] = "Cornemuse"; + codeToLabel["corno"] = "Cornett"; + codeToLabel["cornt"] = "Cornet"; + codeToLabel["coro"] = "Chorus"; + codeToLabel["crshc"] = "Crash cymbal"; + codeToLabel["ctenor"] = "Contratenor"; + codeToLabel["ctina"] = "concertina"; + codeToLabel["drmsp"] = "Dramatic soprano"; + codeToLabel["drum"] = "Drum"; + codeToLabel["drumP"] = "Small drum"; + codeToLabel["dulc"] = "Dulcimer"; + codeToLabel["eguit"] = "Electric guitar"; + codeToLabel["fag_c"] = "Contrabassoon"; + codeToLabel["fagot"] = "Bassoon"; + codeToLabel["false"] = "Falsetto"; + codeToLabel["feme"] = "Female voice"; + codeToLabel["fife"] = "Fife"; + codeToLabel["fingc"] = "Finger Cymbals"; + codeToLabel["flex"] = "Flexatone"; + codeToLabel["flt"] = "Flute"; + codeToLabel["flt_a"] = "Alto flute"; + codeToLabel["flt_b"] = "Bass flute"; + codeToLabel["fltda"] = "Alto recorder"; + codeToLabel["fltdb"] = "Bass recorder"; + codeToLabel["fltdn"] = "Sopranino recorder"; + codeToLabel["fltds"] = "Soprano recorder"; + codeToLabel["fltdt"] = "Tenor recorder"; + codeToLabel["flugh"] = "Flugelhorn"; + codeToLabel["forte"] = "Fortepiano"; + codeToLabel["glock"] = "Glockenspiel"; + codeToLabel["gen"] = "Generic instrument"; + codeToLabel["genT"] = "Generic treble instrument"; + codeToLabel["genB"] = "Generic bass instrument"; + codeToLabel["gong"] = "Gong"; + codeToLabel["guitr"] = "Guitar"; + codeToLabel["hammd"] = "Hammond eletronic organ"; + codeToLabel["hbell"] = "Hand bells"; + codeToLabel["heck"] = "Heckelphone"; + codeToLabel["heltn"] = "Heroic tenor"; + codeToLabel["hichi"] = "Hichiriki"; + codeToLabel["hurdy"] = "Hurdy-gurdy"; + codeToLabel["kitv"] = "Kit violin"; + codeToLabel["klav"] = "Generic keyboard"; + codeToLabel["kokyu"] = "Kokyu"; + codeToLabel["komun"] = "Koumngo"; + codeToLabel["koto"] = "Koto"; + codeToLabel["kruma"] = "Alto crumhorn"; + codeToLabel["krumb"] = "Bass crumhorn"; + codeToLabel["krums"] = "Crumhorn"; + codeToLabel["krumt"] = "Tenor crumhorn"; + codeToLabel["lion"] = "Lion's roar"; + codeToLabel["liuto"] = "Lute"; + codeToLabel["lyrsp"] = "Lyric soprano"; + codeToLabel["lyrtn"] = "Lyric tenor"; + codeToLabel["male"] = "Male voice"; + codeToLabel["mando"] = "Mandolin"; + codeToLabel["marac"] = "Maracas"; + codeToLabel["marim"] = "Marimba"; + codeToLabel["mezzo"] = "Mezzo soprano"; + codeToLabel["nfant"] = "Child's voice"; + codeToLabel["nokan"] = "Nokan"; + codeToLabel["oboe"] = "Oboe"; + codeToLabel["oboeD"] = "Oboe d'amore"; + codeToLabel["ocari"] = "Ocarina"; + codeToLabel["ondes"] = "Ondes Martenot"; + codeToLabel["ophic"] = "Ophicleide"; + codeToLabel["organ"] = "Organ"; + codeToLabel["oud"] = "Oud"; + codeToLabel["panpi"] = "Panpipes"; + codeToLabel["paila"] = "Timbales"; + codeToLabel["pbell"] = "Bell plate"; + codeToLabel["pguit"] = "Portuguese guitar"; + codeToLabel["physh"] = "Physharmonica"; + codeToLabel["piano"] = "Piano"; + codeToLabel["piatt"] = "Cymbales"; + codeToLabel["picco"] = "Piccolo"; + codeToLabel["pipa"] = "Pipa"; + codeToLabel["piri"] = "Piri"; + codeToLabel["porta"] = "Portative organ"; + codeToLabel["psalt"] = "Psaltery"; + codeToLabel["qin"] = "Qin"; + codeToLabel["quinto"] = "Quinto"; + codeToLabel["quitr"] = "Gittern"; + codeToLabel["rackt"] = "Rackett"; + codeToLabel["ratch"] = "Ratchet"; + codeToLabel["ratl"] = "Rattle"; + codeToLabel["rebec"] = "Rebec"; + codeToLabel["recit"] = "Recitativo"; + codeToLabel["reedo"] = "Reed organ"; + codeToLabel["rhode"] = "Rhodes piano"; + codeToLabel["ridec"] = "Ride cymbal"; + codeToLabel["sarod"] = "Sarod"; + codeToLabel["sarus"] = "Sarrusophone"; + codeToLabel["saxA"] = "Alto saxophone"; + codeToLabel["saxB"] = "Bass saxophone"; + codeToLabel["saxN"] = "Sopranino saxophone"; + codeToLabel["saxR"] = "Baritone saxophone"; + codeToLabel["saxS"] = "Saxophone"; + codeToLabel["saxT"] = "Tenor saxophone"; + codeToLabel["sbell"] = "Sleigh bells"; + codeToLabel["sdrum"] = "Snare drum"; + codeToLabel["serp"] = "Serpent"; + codeToLabel["sesto"] = "Sesto"; + codeToLabel["shaku"] = "Shakuhachi"; + codeToLabel["shami"] = "Shamisen"; + codeToLabel["sheng"] = "Sheng"; + codeToLabel["sho"] = "Sho"; + codeToLabel["siren"] = "Siren"; + codeToLabel["sitar"] = "Sitar"; + codeToLabel["slap"] = "Slapstick"; + codeToLabel["soprn"] = "Soprano"; + codeToLabel["spshc"] = "Splash cymbal"; + codeToLabel["spok"] = "Spoken voice"; + codeToLabel["spokF"] = "Female spoken voice"; + codeToLabel["spokM"] = "Male spoken voice"; + codeToLabel["steel"] = "Steel drum"; + codeToLabel["stim"] = "Sprechstimme"; + codeToLabel["stimS"] = "Soprano Sprechstimme"; + codeToLabel["stimA"] = "Alto Sprechstimme"; + codeToLabel["stimC"] = "Contralto Sprechstimme"; + codeToLabel["stimR"] = "Baritone Sprechstimme"; + codeToLabel["stimB"] = "Bass Sprechstimme"; + codeToLabel["strdr"] = "String drum"; + codeToLabel["sxhA"] = "Alto saxhorn"; + codeToLabel["sxhB"] = "Bass saxhorn"; + codeToLabel["sxhC"] = "Contrabass saxhorn"; + codeToLabel["sxhR"] = "Baritons saxhorn"; + codeToLabel["sxhS"] = "Saxhorn"; + codeToLabel["sxhT"] = "Tenor saxhorn"; + codeToLabel["synth"] = "Synthesizer"; + codeToLabel["tabla"] = "Tabla"; + codeToLabel["tambn"] = "Tambourine"; + codeToLabel["tambu"] = "Tambura"; + codeToLabel["tambr"] = "Tambur"; + codeToLabel["tblok"] = "Temple blocks"; + codeToLabel["tdrum"] = "Tenor drum"; + codeToLabel["tenor"] = "Tenor"; + codeToLabel["timpa"] = "Timpani"; + codeToLabel["tiorb"] = "Theorbo"; + codeToLabel["tom"] = "Tom-tom"; + codeToLabel["trngl"] = "Triangle"; + codeToLabel["troma"] = "Alto trombone"; + codeToLabel["tromb"] = "Bass trombone"; + codeToLabel["tromp"] = "Trumpet"; + codeToLabel["tromP"] = "Piccolo trumpet"; + codeToLabel["tromB"] = "Bass trumpet"; + codeToLabel["tromt"] = "Trombone"; + codeToLabel["tuba"] = "Tuba"; + codeToLabel["tubaB"] = "Bass Tuba"; + codeToLabel["tubaC"] = "Contrabass Tuba"; + codeToLabel["tubaT"] = "Tenor Tuba"; + codeToLabel["tubaU"] = "Subcontrabass Tuba"; + codeToLabel["ukule"] = "Ukulele"; + codeToLabel["vibra"] = "Vibraphone"; + codeToLabel["vina"] = "Vina"; + codeToLabel["viola"] = "Viola"; + codeToLabel["violb"] = "Bass viola da gamba"; + codeToLabel["viold"] = "Viola d'amore"; + codeToLabel["viole"] = "violone"; + codeToLabel["violn"] = "Violin"; + codeToLabel["violp"] = "Piccolo violin"; + codeToLabel["viols"] = "Viola da gamba"; + codeToLabel["violt"] = "Tenor viola da gamba"; + codeToLabel["vox"] = "Voice"; + codeToLabel["wblok"] = "Woodblock"; + codeToLabel["xylo"] = "Xylophone"; + codeToLabel["zithr"] = "Zither"; + codeToLabel["zurna"] = "Zurna"; + } + + std::string code = icode->substr(2); + + std::string output = codeToLabel[code]; if (output.empty()) { - // could not find an automatic name for the instrument. + // Could not find an automatic name for the instrument. return output; } @@ -8158,13 +8320,44 @@ std::string HumdrumInput::getLabelFromInstrumentCode(hum::HTp icode, const std:: else if (transpose == "*ITrd-5c-9") { output += " in E-flat"; } + // Add other keys here. + + // Add instrument number + string number = getInstrumentNumber(icode); + if (!number.empty()) { + output += " "; + output += number; + } return output; } ////////////////////////////// // -// hasIndent -- true if *indent tandem interpretation before first data token. +// HumdrumInput::getInstrumentNumber -- search until data has been found +// for an interpretation in the form *I#4 for instrument 4. +// + +std::string HumdrumInput::getInstrumentNumber(hum::HTp icode) +{ + hum::HTp current = icode->getNextToken(); + while (current && !current->isData()) { + if (!current->isInterpretation()) { + current = current->getNextToken(); + continue; + } + hum::HumRegex hre; + if (hre.search(current, "^\\*I#(\\d+)")) { + return hre.getMatch(1); + } + current = current->getNextToken(); + } + return ""; +} + +////////////////////////////// +// +// HumdrumInput::hasIndent -- true if *indent tandem interpretation before first data token. // bool HumdrumInput::hasIndent(hum::HTp tok) From d02dc6f6b10daabcc716a073da9d02a509752ba5 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 8 Jul 2024 10:31:36 -0400 Subject: [PATCH 334/383] Fix nc and accid jumping after dragging staff Refs: https://github.com/DDMAL/Neon/issues/1231 --- src/editortoolkit_neume.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 50cf062f61d..341b04c5aa8 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -679,7 +679,6 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) SortStaves(); - m_doc->GetDrawingPage()->ResetAligners(); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers From 464b2cef14ae089251d2220d72f3d797ae830396 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Wed, 31 Jul 2024 20:04:41 -0700 Subject: [PATCH 335/383] Allow verse labels for **silbe spines. --- include/hum/humlib.h | 2 +- src/hum/humlib.cpp | 2 +- src/iohumdrum.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 374d43cb064..a0e6a56448a 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Mon Jul 29 23:24:23 PDT 2024 +// Last Modified: Tue Jul 30 20:12:26 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 38eb3f94e01..1a089b2d485 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Mon Jul 29 23:24:23 PDT 2024 +// Last Modified: Tue Jul 30 20:12:26 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 3d6247b9b96..ed4bffdf391 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -13682,7 +13682,7 @@ void HumdrumInput::checkForVerseLabels(hum::HTp token) current = current->getNextFieldToken(); } while (current && !current->isStaff()) { - if (!(current->isDataTypeLike("**text") || current->isDataTypeLike("**vdata"))) { + if (!(current->isDataTypeLike("**text") || current->isDataTypeLike("**silbe") || current->isDataTypeLike("**vdata"))) { current = current->getNextFieldToken(); continue; } From c63d38ea738624c96ee30fe82685fb34c7898722 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 1 Aug 2024 09:50:27 -0400 Subject: [PATCH 336/383] Fix nc and staff jumping after staff dragging --- src/calcligatureorneumeposfunctor.cpp | 21 ++++++++++++--------- src/editortoolkit_neume.cpp | 1 + src/page.cpp | 19 ++++++++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/calcligatureorneumeposfunctor.cpp b/src/calcligatureorneumeposfunctor.cpp index a13226e6cfb..efefc3dc3f5 100644 --- a/src/calcligatureorneumeposfunctor.cpp +++ b/src/calcligatureorneumeposfunctor.cpp @@ -347,16 +347,19 @@ FunctorCode CalcLigatureOrNeumePosFunctor::VisitNeume(Neume *neume) } } - // If the nc overlaps with the previous, move it back from a line width - if (overlapWithPrevious) { - xRel -= lineWidth; - } + // xRel remains unset with facsimile + if (!m_doc->HasFacsimile()) { + // If the nc overlaps with the previous, move it back from a line width + if (overlapWithPrevious) { + xRel -= lineWidth; + } - nc->SetDrawingXRel(xRel); - // The first glyph set the spacing - unless we are starting a ligature, in which case no spacing should be added - // between the two nc - if (!previousLig) { - xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staffSize, false); + nc->SetDrawingXRel(xRel); + // The first glyph set the spacing - unless we are starting a ligature, in which case no spacing should be + // added between the two nc + if (!previousLig) { + xRel += m_doc->GetGlyphWidth(nc->m_drawingGlyphs.at(0).m_fontNo, staffSize, false); + } } previousNc = nc; diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 341b04c5aa8..8d6f62e5063 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -679,6 +679,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) SortStaves(); + m_doc->GetDrawingPage()->LayOutTranscription(true); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers diff --git a/src/page.cpp b/src/page.cpp index f336123d746..17388407eb8 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -253,6 +253,9 @@ void Page::LayOutTranscription(bool force) CalcAlignmentPitchPosFunctor calcAlignmentPitchPos(doc); this->Process(calcAlignmentPitchPos); + CalcLigatureOrNeumePosFunctor calcLigatureOrNeumePos(doc); + this->Process(calcLigatureOrNeumePos); + CalcStemFunctor calcStem(doc); this->Process(calcStem); @@ -262,13 +265,15 @@ void Page::LayOutTranscription(bool force) CalcDotsFunctor calcDots(doc); this->Process(calcDots); - // Render it for filling the bounding box - View view; - view.SetDoc(doc); - BBoxDeviceContext bBoxDC(&view, 0, 0, BBOX_HORIZONTAL_ONLY); - // Do not do the layout in this view - otherwise we will loop... - view.SetPage(this->GetIdx(), false); - view.DrawCurrentPage(&bBoxDC, false); + if (!m_layoutDone) { + // Render it for filling the bounding box + View view; + view.SetDoc(doc); + BBoxDeviceContext bBoxDC(&view, 0, 0, BBOX_HORIZONTAL_ONLY); + // Do not do the layout in this view - otherwise we will loop... + view.SetPage(this->GetIdx(), false); + view.DrawCurrentPage(&bBoxDC, false); + } AdjustXRelForTranscriptionFunctor adjustXRelForTranscription; this->Process(adjustXRelForTranscription); From c3925f2d4553a3d0394a12f598ad0d15f40000ce Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 1 Aug 2024 10:00:24 -0400 Subject: [PATCH 337/383] Redo layout after insert nc --- src/editortoolkit_neume.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 8d6f62e5063..69ae37050fe 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1250,7 +1250,8 @@ bool EditorToolkitNeume::Insert(std::string elementType, std::string staffId, in } layer->ReorderByXPos(); - m_doc->GetDrawingPage()->LayOutPitchPos(); + m_doc->GetDrawingPage()->LayOutTranscription(true); + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", status); From e16f7cd544020b93720b008bddafb9da0cd58ec7 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 1 Aug 2024 12:13:20 -0400 Subject: [PATCH 338/383] Redo layout after toggle ligature Refs: https://github.com/DDMAL/Neon/issues/1236 --- src/editortoolkit_neume.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 69ae37050fe..30d922dc302 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -3518,6 +3518,7 @@ bool EditorToolkitNeume::ToggleLigature(std::vector elementIds) return false; } + m_doc->GetDrawingPage()->LayOutTranscription(true); if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); m_editInfo.import("status", "OK"); From 1b4fad318eeb22f2aff8bcaf261d92eb300391bc Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 1 Aug 2024 12:43:09 -0400 Subject: [PATCH 339/383] Calculate clef displacement when inserting custos Refs: https://github.com/DDMAL/Neon/issues/1235 --- src/editortoolkit_neume.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 50cf062f61d..333d3362482 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -4248,6 +4248,14 @@ bool EditorToolkitNeume::AdjustPitchFromPosition(Object *obj, Clef *clef) } pi->SetOct(3); + // The default octave = 3, but the actual octave is calculated by + // taking into account the displacement of the clef + int octave = 3; + if (clef->GetDis() && clef->GetDisPlace()) { + octave += (clef->GetDisPlace() == STAFFREL_basic_above ? 1 : -1) * (clef->GetDis() / 7); + } + pi->SetOct(octave); + const int staffSize = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); const int pitchDifference From e859bc1e1ab3f0ddc5f7b648a6839297fe877c21 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Aug 2024 09:28:16 +0200 Subject: [PATCH 340/383] Fix formatting --- src/editortoolkit_neume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 7a60b37b4db..a93ed908165 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -680,7 +680,7 @@ bool EditorToolkitNeume::Drag(std::string elementId, int x, int y) SortStaves(); m_doc->GetDrawingPage()->LayOutTranscription(true); - + if (m_doc->IsTranscription() && m_doc->HasFacsimile()) m_doc->SyncFromFacsimileDoc(); return true; // Can't reorder by layer since staves contain layers From 57818ed55e02a807554872aff64efd6fb4c97149 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 2 Aug 2024 09:59:18 +0200 Subject: [PATCH 341/383] Do not process expansion in PreparePlistFunctor [skip-ci] --- src/preparedatafunctor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index c33f2677f1e..aad6ed25246 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -501,7 +501,8 @@ void PreparePlistFunctor::InsertInterfaceIDPair(const std::string &elementID, Pl FunctorCode PreparePlistFunctor::VisitObject(Object *object) { if (this->IsCollectingData()) { - if (object->HasInterface(INTERFACE_PLIST)) { + // Skip expansion elements because these are handled in Doc::ExpandExpansions + if (object->HasInterface(INTERFACE_PLIST) && !object->Is(EXPANSION)) { PlistInterface *interface = object->GetPlistInterface(); assert(interface); return interface->InterfacePreparePlist(*this, object); From 96ccc62dc44e0fb60c9d543e75f6b86fb1a2408d Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 8 Aug 2024 16:52:55 +0200 Subject: [PATCH 342/383] add i/o for att.barline.vis --- include/vrv/barline.h | 2 ++ src/barline.cpp | 7 +++++-- src/iomei.cpp | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/vrv/barline.h b/include/vrv/barline.h index 164c2214130..3bb1c41a053 100644 --- a/include/vrv/barline.h +++ b/include/vrv/barline.h @@ -9,6 +9,7 @@ #define __VRV_BARLINE_H__ #include "atts_shared.h" +#include "atts_visual.h" #include "layerelement.h" namespace vrv { @@ -27,6 +28,7 @@ enum class BarLinePosition { None, Left, Right }; */ class BarLine : public LayerElement, public AttBarLineLog, + public AttBarLineVis, public AttColor, public AttNNumberLike, public AttVisibility { diff --git a/src/barline.cpp b/src/barline.cpp index 30952b11ed7..6181c9dba4b 100644 --- a/src/barline.cpp +++ b/src/barline.cpp @@ -33,9 +33,10 @@ namespace vrv { static const ClassRegistrar s_factory("barLine", BARLINE); -BarLine::BarLine() : LayerElement(BARLINE, "bline-"), AttBarLineLog(), AttColor(), AttNNumberLike(), AttVisibility() +BarLine::BarLine() : LayerElement(BARLINE, "bline-"), AttBarLineLog(), AttBarLineVis(), AttColor(), AttNNumberLike(), AttVisibility() { this->RegisterAttClass(ATT_BARLINELOG); + this->RegisterAttClass(ATT_BARLINEVIS); this->RegisterAttClass(ATT_COLOR); this->RegisterAttClass(ATT_VISIBILITY); @@ -43,9 +44,10 @@ BarLine::BarLine() : LayerElement(BARLINE, "bline-"), AttBarLineLog(), AttColor( } BarLine::BarLine(ClassId classId) - : LayerElement(classId, "bline-"), AttBarLineLog(), AttColor(), AttNNumberLike(), AttVisibility() + : LayerElement(classId, "bline-"), AttBarLineLog(), AttBarLineVis(), AttColor(), AttNNumberLike(), AttVisibility() { this->RegisterAttClass(ATT_BARLINELOG); + this->RegisterAttClass(ATT_BARLINEVIS); this->RegisterAttClass(ATT_COLOR); this->RegisterAttClass(ATT_VISIBILITY); @@ -59,6 +61,7 @@ void BarLine::Reset() LayerElement::Reset(); this->ResetBarLineLog(); + this->ResetBarLineVis(); this->ResetColor(); this->ResetVisibility(); diff --git a/src/iomei.cpp b/src/iomei.cpp index eeb2f763486..c74c8a8939a 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2381,6 +2381,7 @@ void MEIOutput::WriteBarLine(pugi::xml_node currentNode, BarLine *barLine) this->WriteLayerElement(currentNode, barLine); barLine->WriteBarLineLog(currentNode); + barLine->WriteBarLineVis(currentNode); barLine->WriteColor(currentNode); barLine->WriteNNumberLike(currentNode); barLine->WriteVisibility(currentNode); @@ -6428,6 +6429,7 @@ bool MEIInput::ReadBarLine(Object *parent, pugi::xml_node barLine) this->ReadLayerElement(barLine, vrvBarLine); vrvBarLine->ReadBarLineLog(barLine); + vrvBarLine->ReadBarLineVis(barLine); vrvBarLine->ReadColor(barLine); vrvBarLine->ReadNNumberLike(barLine); vrvBarLine->ReadVisibility(barLine); From 4b4f0cd684e5111a2a90e93b26d13acf66caf52a Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 8 Aug 2024 16:53:39 +0200 Subject: [PATCH 343/383] draw takt with barLine --- src/view_element.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index eb253fc26fd..f62b9741e68 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -420,11 +420,24 @@ void View::DrawBarLine(DeviceContext *dc, LayerElement *element, Layer *layer, S barLine->SetEmptyBB(); return; } - + StaffDef *drawingStaffDef = staff->m_drawingStaffDef; + // Determine the method + assert(drawingStaffDef); + auto [hasMethod, method] = barLine->GetMethod(drawingStaffDef); + if (barLine->HasMethod()) { + LogWarning("Hat Methode!"); + method = barLine->AttBarLineVis::GetMethod(); + } + dc->StartGraphic(element, "", element->GetID()); - const int yTop = staff->GetDrawingY(); - const int yBottom = yTop - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int yTop = staff->GetDrawingY(); + int yBottom = yTop - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + + if (method == BARMETHOD_takt) { + yTop += m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + yBottom = yTop - m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + } const int offset = (yTop == yBottom) ? m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) : 0; From 3ada618dfb2c44ac76c6b2f097461a9cd91fb88c Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 8 Aug 2024 17:16:11 +0200 Subject: [PATCH 344/383] import Volpiano barlines and breaks --- src/iovolpiano.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index bfdd67ca4f2..90a3e0f2504 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -16,6 +16,7 @@ //---------------------------------------------------------------------------- +#include "barline.h" #include "clef.h" #include "doc.h" #include "layer.h" @@ -110,6 +111,20 @@ bool VolpianoInput::Import(const std::string &volpiano) else if (ch == 'I' || ch == 'W' || ch == 'X' || ch == 'Y' || ch == 'Z') { accidVal = ACCIDENTAL_WRITTEN_n; } + else if (ch == '3') { + BarLine *single = new BarLine(); + layer->AddChild(single); + } + else if (ch == '4') { + BarLine *dbl = new BarLine(); + dbl->SetForm(BARRENDITION_dbl); + layer->AddChild(dbl); + } + else if (ch == '7') { + BarLine *takt = new BarLine(); + takt->SetMethod(BARMETHOD_takt); + layer->AddChild(takt); + } } // add minimal scoreDef From 9e37989b759bb19fbf9585cd0785b0820d29e7a0 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 8 Aug 2024 17:25:46 +0200 Subject: [PATCH 345/383] formatting --- src/barline.cpp | 3 ++- src/view_element.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/barline.cpp b/src/barline.cpp index 6181c9dba4b..9a7beea2900 100644 --- a/src/barline.cpp +++ b/src/barline.cpp @@ -33,7 +33,8 @@ namespace vrv { static const ClassRegistrar s_factory("barLine", BARLINE); -BarLine::BarLine() : LayerElement(BARLINE, "bline-"), AttBarLineLog(), AttBarLineVis(), AttColor(), AttNNumberLike(), AttVisibility() +BarLine::BarLine() + : LayerElement(BARLINE, "bline-"), AttBarLineLog(), AttBarLineVis(), AttColor(), AttNNumberLike(), AttVisibility() { this->RegisterAttClass(ATT_BARLINELOG); this->RegisterAttClass(ATT_BARLINEVIS); diff --git a/src/view_element.cpp b/src/view_element.cpp index f62b9741e68..519320ac31c 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -428,7 +428,7 @@ void View::DrawBarLine(DeviceContext *dc, LayerElement *element, Layer *layer, S LogWarning("Hat Methode!"); method = barLine->AttBarLineVis::GetMethod(); } - + dc->StartGraphic(element, "", element->GetID()); int yTop = staff->GetDrawingY(); From 36a02dac19c74444d9a857bfd8a3b555a2e561c0 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 9 Aug 2024 08:47:19 +0200 Subject: [PATCH 346/383] fix: remove debug message --- src/view_element.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/view_element.cpp b/src/view_element.cpp index 519320ac31c..d14d3715d42 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -425,7 +425,6 @@ void View::DrawBarLine(DeviceContext *dc, LayerElement *element, Layer *layer, S assert(drawingStaffDef); auto [hasMethod, method] = barLine->GetMethod(drawingStaffDef); if (barLine->HasMethod()) { - LogWarning("Hat Methode!"); method = barLine->AttBarLineVis::GetMethod(); } From 877db2a811466534b7f938fbefbf9be7a5bddfb2 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Tue, 13 Aug 2024 22:02:02 -0400 Subject: [PATCH 347/383] Reset layout after change nc head shape Refs: https://github.com/DDMAL/Neon/issues/1239 --- src/editortoolkit_neume.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editortoolkit_neume.cpp b/src/editortoolkit_neume.cpp index 39ef113c585..11b5ea9a0a6 100644 --- a/src/editortoolkit_neume.cpp +++ b/src/editortoolkit_neume.cpp @@ -1862,6 +1862,7 @@ bool EditorToolkitNeume::Set(std::string elementId, std::string attrType, std::s else if (AttModule::SetVisual(element, attrType, attrValue)) success = true; + m_doc->GetDrawingPage()->LayOutTranscription(true); m_editInfo.import("status", success ? "OK" : "FAILURE"); m_editInfo.import("message", success ? "" : "Could not set attribute '" + attrType + "' to '" + attrValue + "'."); return success; From 58ce9e8238437a6969440dc19f44917f0bd190d0 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Wed, 14 Aug 2024 22:40:15 +0200 Subject: [PATCH 348/383] fix: Consider staff during search for unison note buddy --- include/vrv/elementpart.h | 8 ++++---- src/layerelement.cpp | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/vrv/elementpart.h b/include/vrv/elementpart.h index 4387902979c..3f5c98d4d2c 100644 --- a/include/vrv/elementpart.h +++ b/include/vrv/elementpart.h @@ -22,7 +22,7 @@ class TupletNum; //---------------------------------------------------------------------------- /** - * This class models a group of dots as a layer element part and has not direct MEI equivlatent. + * This class models a group of dots as a layer element part and has no direct MEI equivalent. */ class Dots : public LayerElement, public AttAugmentDots { public: @@ -92,7 +92,7 @@ class Dots : public LayerElement, public AttAugmentDots { //---------------------------------------------------------------------------- /** - * This class models a stem as a layer element part and has not direct MEI equivlatent. + * This class models a stem as a layer element part and has no direct MEI equivalent. */ class Flag : public LayerElement { public: @@ -144,7 +144,7 @@ class Flag : public LayerElement { //---------------------------------------------------------------------------- /** - * This class models a bracket as a layer element part and has not direct MEI equivlatent. + * This class models a bracket as a layer element part and has no direct MEI equivalent. * It is used to represent tuplet brackets. */ class TupletBracket : public LayerElement, public AttTupletVis { @@ -243,7 +243,7 @@ class TupletBracket : public LayerElement, public AttTupletVis { //---------------------------------------------------------------------------- /** - * This class models a tuplet num as a layer element part and has not direct MEI equivlatent. + * This class models a tuplet num as a layer element part and has no direct MEI equivalent. * It is used to represent tuplet number */ class TupletNum : public LayerElement, public AttNumberPlacement, public AttTupletVis { diff --git a/src/layerelement.cpp b/src/layerelement.cpp index 93766929238..78cae4a13e8 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -901,13 +901,16 @@ MapOfDotLocs LayerElement::CalcOptimalDotLocations() // Special treatment for two layers if (layerCount == 2) { - // Find the first note on the other layer + // Find the first note on the other layer, but in the same staff Alignment *alignment = this->GetAlignment(); + const Staff *currentStaff = this->GetAncestorStaff(RESOLVE_CROSS_STAFF); const int currentLayerN = abs(this->GetAlignmentLayerN()); ListOfObjects notes = alignment->FindAllDescendantsByType(NOTE, false); - auto noteIt = std::find_if(notes.cbegin(), notes.cend(), [currentLayerN](Object *obj) { - const int otherLayerN = abs(vrv_cast(obj)->GetAlignmentLayerN()); - return (currentLayerN != otherLayerN); + auto noteIt = std::find_if(notes.cbegin(), notes.cend(), [currentLayerN, currentStaff](Object *obj) { + const Note *otherNote = vrv_cast(obj); + const Staff *otherStaff = otherNote->GetAncestorStaff(RESOLVE_CROSS_STAFF); + const int otherLayerN = abs(otherNote->GetAlignmentLayerN()); + return ((currentLayerN != otherLayerN) && (currentStaff == otherStaff)); }); if (noteIt != notes.cend()) { From 8ada6eb95741761ef3630d3f0a21cc86722bf632 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Fri, 16 Aug 2024 11:29:20 -0700 Subject: [PATCH 349/383] Humlib updates. --- include/hum/humlib.h | 60 ++-- src/hum/humlib.cpp | 754 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 729 insertions(+), 85 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index a0e6a56448a..5dfe597853f 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Jul 30 20:12:26 PDT 2024 +// Last Modified: Thu Aug 8 21:55:11 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -8386,9 +8386,12 @@ class Tool_kernify : public HumTool { void generateDummyKernSpine (HumdrumFile& infile); std::string makeNullLine (HumdrumLine& line); std::string makeReverseLine (HumdrumLine& line); + bool prepareDataSpines (HumdrumFile& infile); + bool prepareDataSpine (HTp spinestart); private: bool m_forceQ = false; // used with -f option + bool m_hasDataInterpretations = false; }; @@ -10347,32 +10350,53 @@ class Tool_rphrase : public HumTool { void processFile (HumdrumFile& infile); void fillVoiceInfo (std::vector& voiceInfo, std::vector& kstarts, HumdrumFile& infile); void fillVoiceInfo (Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile); - void fillCollapseInfo (Tool_rphrase::VoiceInfo& collapseInfo, HumdrumFile& infile); + void fillCompositeInfo (Tool_rphrase::VoiceInfo& collapseInfo, HumdrumFile& infile); void printVoiceInfo (std::vector& voiceInfo); void printVoiceInfo (Tool_rphrase::VoiceInfo& voiceInfo); + + void printEmbeddedVoiceInfo(std::vector& voiceInfo, Tool_rphrase::VoiceInfo& collapseInfo, HumdrumFile& infile); + void printEmbeddedIndividualVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HumdrumFile& infile); + void printEmbeddedCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile); + void getCompositeStates(std::vector& noteStates, HumdrumFile& infile); std::string getCompositeLabel(HumdrumFile& infile); void markPhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& voiceInfo); - void outputMarkedFile (HumdrumFile& infile); + void markCompositePhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& collapseInfo); + void outputMarkedFile (HumdrumFile& infile, std::vector& voiceInfo, + Tool_rphrase::VoiceInfo& compositeInfo); void printDataLine (HumdrumFile& infile, int index); void markLongaDurations(HumdrumFile& infile); + std::string getVoiceInfo(HumdrumFile& infile); + void printEmbeddedVoiceInfoSummary(std::vector& voiceInfo, HumdrumFile& infile); + double twoDigitRound(double input); + void printHyperlink(const std::string& urlType); private: - bool m_averageQ = false; // for -a option - bool m_allAverageQ = false; // for -A option - bool m_barlineQ = false; // for -m option - bool m_collapseQ = false; // for -c option - bool m_longaQ = false; // for -l option - bool m_filenameQ = false; // for -f option - bool m_fullFilenameQ = false; // for -F option - std::string m_filename; // for -f or -F option - bool m_sortQ = false; // for -s option - bool m_reverseSortQ = false; // for -S option - int m_pcount = 0; // for -a option - double m_sum = 0.0; // for -a option - int m_pcountCollapse= 0; // for -c option - double m_sumCollapse = 0.0; // for -c option - bool m_markQ = false; // for --mark option + bool m_averageQ = false; // for -a option + bool m_allAverageQ = false; // for -A option + bool m_breathQ = true; // for -B option + bool m_barlineQ = false; // for -m option + bool m_compositeQ = false; // for -c option + bool m_longaQ = false; // for -l option + bool m_filenameQ = false; // for -f option + bool m_fullFilenameQ = false; // for -F option + std::string m_filename; // for -f or -F option + bool m_sortQ = false; // for -s option + bool m_reverseSortQ = false; // for -S option + int m_pcount = 0; // for -a option + double m_sum = 0.0; // for -a option + int m_pcountComposite = 0; // for -c option + double m_sumComposite = 0.0; // for -c option + bool m_markQ = false; // for --mark option + double m_durUnit = 2.0; // for -d option + bool m_infoQ = false; // for -i option (when --mark is active) + bool m_squeezeQ = false; // for -z option + bool m_closeQ = false; // for --close option + std::string m_urlType; // for -u option + + int m_line = 1; + std::string m_voiceLengthColor = "crimson"; + std::string m_compositeLengthColor = "limegreen"; }; diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 1a089b2d485..1b5e14d1d75 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Tue Jul 30 20:12:26 PDT 2024 +// Last Modified: Thu Aug 8 21:55:11 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -83262,6 +83262,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { RUNTOOL(recip, infile, commands[i].second, status); } else if (commands[i].first == "restfill") { RUNTOOL(restfill, infile, commands[i].second, status); + } else if (commands[i].first == "rphrase") { + RUNTOOL(rphrase, infile, commands[i].second, status); } else if (commands[i].first == "sab2gs") { RUNTOOL(sab2gs, infile, commands[i].second, status); } else if (commands[i].first == "scordatura") { @@ -91672,6 +91674,7 @@ bool Tool_kernify::run(HumdrumFile& infile, ostream& out) { bool Tool_kernify::run(HumdrumFile& infile) { initialize(); + m_hasDataInterpretations = prepareDataSpines(infile); processFile(infile); return true; } @@ -91703,6 +91706,71 @@ void Tool_kernify::processFile(HumdrumFile& infile) { +////////////////////////////// +// +// Tool_kernify::prepareDataSpines -- +// + +bool Tool_kernify::prepareDataSpines(HumdrumFile& infile) { + vector spinestarts; + infile.getSpineStartList(spinestarts); + bool output = false; + for (int i=0; i<(int)spinestarts.size(); i++) { + output |= prepareDataSpine(spinestarts[i]); + } + return output; +} + + + +////////////////////////////// +// +// Tool_kernify::prepareDataSpine -- check to see if *[abcv]data interpretation and if so process +// and return true; +// + +bool Tool_kernify::prepareDataSpine(HTp spinestart) { + HumRegex hre; + if (hre.search(spinestart, "^\\*\\*[abcv]data-")) { + return false; + } + + string prefix; + HTp current = spinestart->getNextToken(); + while (current && !current->isData()) { + if (current->isInterpretation()) { + if (*current == "*adata") { + prefix = "adata"; + break; + } else if (*current == "*bdata") { + prefix = "bdata"; + break; + } else if (*current == "*cdata") { + prefix = "cdata"; + break; + } else if (*current == "*vdata") { + prefix = "vdata"; + break; + } + } + current = current->getNextToken(); + } + + if (prefix.empty()) { + return false; + } + + string text = "**"; + text += prefix; + text += "-"; + text += spinestart->substr(2); + spinestart->setText(text); + + return true; +} + + + ////////////////////////////// // // Tool_kernify::generateDummyKernSpine -- @@ -91776,18 +91844,22 @@ void Tool_kernify::generateDummyKernSpine(HumdrumFile& infile) { } else if (token->find("**kern") != std::string::npos) { string value = *token; hre.replaceDestructive(value, "nrek", "kern", "g"); - hre.replaceDestructive(value, "**cdata-", "^\\*\\*"); + hre.replaceDestructive(value, "**zcdata-", "^\\*\\*"); m_humdrum_text << "\t" << value; } else if (token->find("**mens") != std::string::npos) { string value = *token; hre.replaceDestructive(value, "snem", "mens", "g"); - hre.replaceDestructive(value, "**cdata-", "^\\*\\*"); + hre.replaceDestructive(value, "**ycdata-", "^\\*\\*"); m_humdrum_text << "\t" << value; } else if (token->find("**cdata") == std::string::npos) { - string value = token->substr(2); - hre.replaceDestructive(value, "nrek", "kern", "g"); - hre.replaceDestructive(value, "snem", "snem", "g"); - m_humdrum_text << "\t**cdata-" << value; + if (!m_hasDataInterpretations) { + string value = token->substr(2); + hre.replaceDestructive(value, "nrek", "kern", "g"); + hre.replaceDestructive(value, "snem", "snem", "g"); + m_humdrum_text << "\t**xcdata-" << value; + } else { + m_humdrum_text << "\t" << token; + } } else { m_humdrum_text << "\t" << token; } @@ -91842,7 +91914,12 @@ void Tool_kernify::generateDummyKernSpine(HumdrumFile& infile) { m_humdrum_text << "*clefXyy" << "\t" << makeReverseLine(infile[i]); clefIndex = -1; } else { - m_humdrum_text << "*" << "\t" << makeReverseLine(infile[i]); + HTp token = infile[i].token(0); + if (token->compare(0, 2, "*>") == 0) { + m_humdrum_text << token << "\t" << makeReverseLine(infile[i]); + } else { + m_humdrum_text << "*" << "\t" << makeReverseLine(infile[i]); + } } } else { m_humdrum_text << "!!UNKNONWN LINE TYPE FOR LINE " << i+1 << ":\t" << infile[i]; @@ -118102,17 +118179,22 @@ void Tool_rid::processFile(HumdrumFile& infile) { // Tool_rphrase::Tool_rphrase(void) { - // add command-line options here define("a|average=b", "calculate average length of rest-phrases by score"); define("A|all-average=b", "calculate average length of rest-phrases for all scores"); + define("B|no-breath=b", "ignore breath interpretations"); define("c|composite|collapse=b", "collapse all voices into single part"); + define("d|duration-unit=d:2.0", "duration units, default: 2.0 (minims/half notes)"); define("f|filename=b", "include filename in output analysis"); define("F|full-filename=b", "include full filename location in output analysis"); + define("I|no-info=b", "do not display summary info"); define("l|longa=b", "display minim length of longas"); define("m|b|measure|barline=b", "include barline numbers in output analysis"); define("mark=b", "mark starts of phrases in score"); define("s|sort=b", "sort phrases by short to long length"); define("S|reverse-sort=b", "sort phrases by long to short length"); + define("u|url-type=s", "URL type (jrp, 1520s) for hyperlink"); + define("z|squeeze=b", "squeeze notation"); + define("close=b", "close details element initially"); } @@ -118170,8 +118252,8 @@ bool Tool_rphrase::run(HumdrumFile& infile) { void Tool_rphrase::finally(void) { if (!m_markQ) { if (m_allAverageQ) { - if (m_collapseQ) { - double average = m_sumCollapse / m_pcountCollapse; + if (m_compositeQ) { + double average = m_sumComposite / m_pcountComposite; m_free_text << "Composite average phrase length: " << average << " minims" << endl; } else { double average = m_sum / m_pcount; @@ -118189,25 +118271,27 @@ void Tool_rphrase::finally(void) { // void Tool_rphrase::initialize(void) { - m_averageQ = getBoolean("average"); m_barlineQ = getBoolean("measure"); m_allAverageQ = getBoolean("all-average"); - #ifndef __EMSCRIPTEN__ - m_collapseQ = !getBoolean("collapse"); - #else - m_collapseQ = getBoolean("collapse"); - #endif + m_breathQ = !getBoolean("no-breath"); + m_compositeQ = getBoolean("collapse"); m_filenameQ = getBoolean("filename"); m_fullFilenameQ = getBoolean("full-filename"); + m_urlType = getString("url-type"); m_longaQ = getBoolean("longa"); #ifndef __EMSCRIPTEN__ m_markQ = getBoolean("mark"); + m_averageQ = getBoolean("average"); #else m_markQ = !getBoolean("mark"); + m_averageQ = !getBoolean("average"); #endif m_sortQ = getBoolean("sort"); m_reverseSortQ = getBoolean("reverse-sort"); - m_longaQ = getBoolean("longa"); + m_durUnit = getDouble("duration-unit"); + m_infoQ = !getDouble("no-info"); + m_squeezeQ = getBoolean("squeeze"); + m_closeQ = getBoolean("close"); } @@ -118228,29 +118312,64 @@ void Tool_rphrase::processFile(HumdrumFile& infile) { } vector kernStarts = infile.getKernSpineStartList(); vector voiceInfo(kernStarts.size()); - Tool_rphrase::VoiceInfo collapseInfo; + Tool_rphrase::VoiceInfo compositeInfo; - if (m_collapseQ) { - fillCollapseInfo(collapseInfo, infile); + if (m_compositeQ) { + fillCompositeInfo(compositeInfo, infile); } else { fillVoiceInfo(voiceInfo, kernStarts, infile); } - if (m_longaQ) { markLongaDurations(infile); } - if (!m_allAverageQ) { - if (m_collapseQ) { - printVoiceInfo(collapseInfo); + if ((!m_allAverageQ) && (!m_markQ)) { + if (m_line == 1) { + if (m_compositeQ) { + m_free_text << "Filename"; + if (!m_urlType.empty()) { + m_free_text << "\tVHV"; + } + m_free_text << "\tVoice"; + m_free_text << "\tComp seg count"; + if (m_averageQ) { + m_free_text << "\tAvg comp seg dur"; + } + m_free_text << "\tComposite seg durs"; + m_free_text << endl; + } else { + m_free_text << "Filename"; + if (!m_urlType.empty()) { + m_free_text << "\tVHV"; + } + m_free_text << "\tVoice"; + m_free_text << "\tSounding dur"; + m_free_text << "\tResting dur"; + m_free_text << "\tTotal dur"; + m_free_text << "\tSeg count"; + if (m_averageQ) { + m_free_text << "\tSeg dur average"; + } + m_free_text << "\tSegment durs"; + m_free_text << endl; + } + } + if (m_compositeQ) { + if (m_compositeQ) { + m_line++; + } + printVoiceInfo(compositeInfo); } else { printVoiceInfo(voiceInfo); } } if (m_markQ) { - outputMarkedFile(infile); + outputMarkedFile(infile, voiceInfo, compositeInfo); + if (m_squeezeQ) { + m_humdrum_text << "!!!verovio: evenNoteSpacing" << endl; + } } } @@ -118303,7 +118422,7 @@ void Tool_rphrase::markLongaDurations(HumdrumFile& infile) { HumNum duration = token->getTiedDuration(); stringstream value; value.str(""); - value << duration.getFloat() / 2.0; + value << duration.getFloat() / m_durUnit; token->setValue("auto", "rphrase-longa", value.str()); } } @@ -118317,7 +118436,8 @@ void Tool_rphrase::markLongaDurations(HumdrumFile& infile) { // Tool_rphrase::outputMarkedFile -- // -void Tool_rphrase::outputMarkedFile(HumdrumFile& infile) { +void Tool_rphrase::outputMarkedFile(HumdrumFile& infile, vector& voiceInfo, + Tool_rphrase::VoiceInfo& compositeInfo) { m_free_text.clear(); m_free_text.str(""); for (int i=0; i 0.0) { + sounding += voiceInfo.phraseDurs[i]; + } + if (voiceInfo.restsBefore[i] > 0.0) { + resting += voiceInfo.restsBefore[i]; + } + } + double total = sounding + resting; + // double sounding_percent = int (sounding/total * 100.0 + 0.5); + // double resting_percent = int (sounding/total * 100.0 + 0.5); + + m_free_text << twoDigitRound(sounding) << "\t"; + m_free_text << twoDigitRound(resting) << "\t"; + m_free_text << twoDigitRound(total) << "\t"; + } + + m_free_text << voiceInfo.phraseDurs.size() << "\t"; + if (m_averageQ) { double sum = 0; int count = 0; @@ -118503,7 +118712,7 @@ void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) { if (m_barlineQ) { m_free_text << "m" << voiceInfo.barStarts.at(ii) << ":"; } - m_free_text << voiceInfo.phraseDurs.at(ii); + m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(ii)); if (i < (int)sortList.size() - 1) { m_free_text << " "; } @@ -118511,12 +118720,15 @@ void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) { } else { for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { if (voiceInfo.restsBefore.at(i) > 0) { - m_free_text << "r:" << voiceInfo.restsBefore.at(i) << " "; + m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } else if (i > 0) { + // force display r:0 for section boundaries. + m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; } if (m_barlineQ) { m_free_text << "m" << voiceInfo.barStarts.at(i) << ":"; } - m_free_text << voiceInfo.phraseDurs.at(i); + m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(i)); if (i < (int)voiceInfo.phraseDurs.size() - 1) { m_free_text << " "; } @@ -118530,11 +118742,362 @@ void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) { ////////////////////////////// // -// Tool_rphrase::fillCollapseInfo -- +// Tool_rphrase::printEmbeddedVoiceInfo -- +// + +void Tool_rphrase::printEmbeddedVoiceInfo(vector& voiceInfo, Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { + + m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;; + + m_humdrum_text << "!!@SCRIPT:" << endl; + m_humdrum_text << "!! function rphraseGotoMeasure(measure) {" << endl; + m_humdrum_text << "!! let target = `svg .measure.m-${measure}`;" << endl; + m_humdrum_text << "!! let element = document.querySelector(target);" << endl; + m_humdrum_text << "!! if (element) {" << endl; + m_humdrum_text << "!! element.scrollIntoViewIfNeeded({ behavior: 'smooth' });" << endl; + m_humdrum_text << "!! }" << endl; + m_humdrum_text << "!! }" << endl; + + m_humdrum_text << "!!@CONTENT:\n"; + if (m_compositeQ) { + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + } else { + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + } + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + + if (m_compositeQ) { + m_humdrum_text << "!!

Composite rest phrasing\n"; + } else { + m_humdrum_text << "!!Voice rest phrasing\n"; + } + if (m_compositeQ) { + printEmbeddedCompositeInfo(compositeInfo, infile); + } else { + if (voiceInfo.size() > 0) { + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + for (int i=(int)voiceInfo.size() - 1; i>=0; i--) { + printEmbeddedIndividualVoiceInfo(voiceInfo[i], infile); + } + m_humdrum_text << "!!
VoiceSoundingRestingSegmentsAverageSegment durations
" << endl; + printEmbeddedVoiceInfoSummary(voiceInfo, infile); + } + } + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!@@END: PREHTML" << endl; +} + + + +////////////////////////////// +// +// Tool_rphrase::printEmbeddedCompositeInfo -- // -void Tool_rphrase::fillCollapseInfo(Tool_rphrase::VoiceInfo& collapseInfo, HumdrumFile& infile) { - collapseInfo.name = getCompositeLabel(infile); +void Tool_rphrase::printEmbeddedCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { + + m_humdrum_text << "!!
" << endl; + m_humdrum_text << "!!
    " << endl; + m_humdrum_text << "!!
  • Composite segment count: " << compositeInfo.phraseDurs.size() << "
  • " << endl; + + if (!compositeInfo.phraseDurs.empty()) { + m_humdrum_text << "!!
  • Composite segment duration"; + if (compositeInfo.phraseDurs.size() != 1) { + m_humdrum_text << "s"; + } + m_humdrum_text << ": "; + if (m_sortQ || m_reverseSortQ) { + vector> sortList; + for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { + sortList.emplace_back(compositeInfo.phraseDurs[i], i); + } + if (m_sortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + } else if (m_reverseSortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + } + + for (int i=0; i<(int)sortList.size(); i++) { + int ii = sortList[i].second; + if (m_barlineQ) { + m_humdrum_text << "m" << compositeInfo.barStarts.at(ii) << ":"; + } + m_humdrum_text << "" << twoDigitRound(compositeInfo.phraseDurs.at(ii)) << ""; + if (i < (int)sortList.size() - 1) { + m_humdrum_text << " "; + } + } + } else { + for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { + if (compositeInfo.restsBefore.at(i) > 0) { + m_humdrum_text << "" << twoDigitRound(compositeInfo.restsBefore.at(i)) << " "; + } else if (i > 0) { + // force display r:0 for section boundaries. + m_humdrum_text << "" << twoDigitRound(compositeInfo.restsBefore.at(i)) << " "; + } + if (m_barlineQ) { + m_humdrum_text << "m" << compositeInfo.barStarts.at(i) << ":"; + } + m_humdrum_text << "" << twoDigitRound(compositeInfo.phraseDurs.at(i)) << ""; + if (i < (int)compositeInfo.phraseDurs.size() - 1) { + m_humdrum_text << " "; + } + } + } + m_humdrum_text << "
  • " << endl; + + if (m_averageQ && (compositeInfo.phraseDurs.size() > 1)) { + double sum = 0; + int count = 0; + for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { + count++; + sum += compositeInfo.phraseDurs.at(i); + } + double average = int(sum / count * 100.0 + 0.5)/100.0; + m_humdrum_text << "!!
  • Average composite segment durations: " << average << "
  • " << endl; + } + + m_humdrum_text << "!!
  • Voices: " << getVoiceInfo(infile) << "
  • " << endl; + + if (m_durUnit != 2.0) { + m_humdrum_text << "!!
  • Duration unit: " << m_durUnit << "
  • " << endl; + } + } + + m_humdrum_text << "!!
" << endl; + m_humdrum_text << "!!
" << endl; +} + + + +////////////////////////////// +// +// Tool_rphrase::getVoiceInfo -- +// + +string Tool_rphrase::getVoiceInfo(HumdrumFile& infile) { + vector kspines = infile.getKernSpineStartList(); + string vcount = to_string(kspines.size()); + string ocount; + for (int i=0; i& voiceInfo, HumdrumFile& infile) { + m_humdrum_text << "!!
    " << endl; + + double total = 0.0; + for (int i=0; i<(int)voiceInfo[0].phraseDurs.size(); i++) { + if (voiceInfo[0].phraseDurs[i] > 0.0) { + total += voiceInfo[0].phraseDurs[i]; + } + if (voiceInfo[0].restsBefore[i] > 0.0) { + total += voiceInfo[0].restsBefore[i]; + } + } + m_humdrum_text << "!!
  • Score duration: " << twoDigitRound(total) << "
  • " << endl; + + int countSum = 0; + for (int i=0; i<(int)voiceInfo.size(); i++) { + countSum += (int)voiceInfo[i].phraseDurs.size(); + } + m_humdrum_text << "!!
  • Total segments: " << countSum << "
  • " << endl; + + double averageCount = countSum / (double)voiceInfo.size(); + averageCount = (int)(averageCount * 10 + 0.5) / 10.0; + m_humdrum_text << "!!
  • Average voice segments: " << averageCount << "
  • " << endl; + + double durSum = 0.0; + for (int i=0; i<(int)voiceInfo.size(); i++) { + for (int j=0; j<(int)voiceInfo[i].phraseDurs.size(); j++) { + durSum += voiceInfo[i].phraseDurs[j]; + } + } + double averageDur = durSum / countSum; + averageDur = (int)(averageDur * 10 + 0.5) / 10.0; + m_humdrum_text << "!!
  • Average segment duration: " << averageDur << "
  • " << endl; + + m_humdrum_text << "!!
  • Voices: " << getVoiceInfo(infile) << "
  • " << endl; + + m_humdrum_text << "!!
" << endl; +} + + + +////////////////////////////// +// +// Tool_rphrase::printEmbeddedIndividualVoiceInfo -- +// + +void Tool_rphrase::printEmbeddedIndividualVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HumdrumFile& infile) { + m_humdrum_text << "!!" << endl; + + m_humdrum_text << "!!" << voiceInfo.name << "" << endl; + + double sounding = 0.0; + double resting = 0.0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.phraseDurs[i] > 0.0) { + sounding += voiceInfo.phraseDurs[i]; + } + if (voiceInfo.restsBefore[i] > 0.0) { + resting += voiceInfo.restsBefore[i]; + } + } + double total = sounding + resting; + double spercent = int(sounding/total * 100.0 + 0.5); + double rpercent = int(resting/total * 100.0 + 0.5); + m_humdrum_text << "!!" << sounding << "(" << spercent << "%)" << endl; + m_humdrum_text << "!!" << resting << "(" << rpercent << "%)" << endl; + + // Segment count + m_humdrum_text << "!!"; + m_humdrum_text << voiceInfo.phraseDurs.size(); + m_humdrum_text << "" << endl; + + // Segment duration average + m_humdrum_text << "!!"; + double sum = 0; + int count = 0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + count++; + sum += voiceInfo.phraseDurs.at(i); + } + double average = int(sum / count * 100.0 + 0.5)/100.0; + m_humdrum_text << average; + m_humdrum_text << "" << endl; + + // Segments + m_humdrum_text << "!!"; + if (m_sortQ || m_reverseSortQ) { + vector> sortList; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + sortList.emplace_back(voiceInfo.phraseDurs[i], i); + } + if (m_sortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + } else if (m_reverseSortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + } + for (int i=0; i<(int)sortList.size(); i++) { + int ii = sortList[i].second; + if (m_barlineQ) { + m_humdrum_text << "m" << voiceInfo.barStarts.at(ii) << ":"; + } + m_humdrum_text << "" << twoDigitRound(voiceInfo.phraseDurs.at(ii)) << ""; + if (i < (int)sortList.size() - 1) { + m_humdrum_text << " "; + } + } + } else { + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.restsBefore.at(i) > 0) { + m_humdrum_text << "" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } else if (i > 0) { + // force display r:0 for section boundaries. + m_humdrum_text << "" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } + if (m_barlineQ) { + m_humdrum_text << "m" << voiceInfo.barStarts.at(i) << ":"; + } + m_humdrum_text << "" << twoDigitRound(voiceInfo.phraseDurs.at(i)) << ""; + if (i < (int)voiceInfo.phraseDurs.size() - 1) { + m_humdrum_text << " "; + } + } + } + + m_humdrum_text << "" << endl; + m_humdrum_text << "!!" << endl; +} + + + +////////////////////////////// +// +// Tool_rphrase::fillCompositeInfo -- +// + +void Tool_rphrase::fillCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { + compositeInfo.name = getCompositeLabel(infile); vector noteStates; getCompositeStates(noteStates, infile); @@ -118542,11 +119105,10 @@ void Tool_rphrase::fillCollapseInfo(Tool_rphrase::VoiceInfo& collapseInfo, Humdr int currentBarline = 0; int startBarline = 1; HumNum startTime = 0; - HumNum restBefore = 0; HumNum startTimeRest = 0; - - HumNum scoreDur = infile.getScoreDuration(); + HumNum scoreDur = infile.getScoreDuration(); + HTp phraseStartTok = NULL; for (int i=0; i& voiceInfo, } - void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile) { HTp current = kstart; @@ -118753,13 +119330,15 @@ void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart HumNum duration = endTime - startTime; startTime = -1; inPhraseQ = false; - voiceInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + double value = duration.getFloat() / m_durUnit; + voiceInfo.phraseDurs.push_back(value); voiceInfo.barStarts.push_back(startBarline); voiceInfo.phraseStartToks.push_back(phraseStartTok); phraseStartTok = NULL; - m_sum += duration.getFloat() / 2.0; + m_sum += duration.getFloat() / m_durUnit; m_pcount++; - voiceInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + double rvalue = restBefore.getFloat() / m_durUnit; + voiceInfo.restsBefore.push_back(rvalue); // record rest start startTimeRest = endTime; @@ -118785,7 +119364,7 @@ void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart if (current->isInstrumentName()) { voiceInfo.name = current->substr(3); } - if (!current->isData()) { + if (!(current->isData() || (m_breathQ && (*current == "*breath")))) { current = current->getNextToken(); continue; } @@ -118798,19 +119377,21 @@ void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart // In phrase, so continue if still notes, otherwise // if a rest, then record the currently active phrase // that has ended. - if (current->isRest()) { + if (current->isRest() || (*current == "*breath")) { // ending a phrase HumNum endTime = current->getDurationFromStart(); HumNum duration = endTime - startTime; startTime = -1; inPhraseQ = false; - voiceInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + double value = duration.getFloat() / m_durUnit; + voiceInfo.phraseDurs.push_back(value); voiceInfo.barStarts.push_back(startBarline); voiceInfo.phraseStartToks.push_back(phraseStartTok); phraseStartTok = NULL; - m_sum += duration.getFloat() / 2.0; + m_sum += duration.getFloat() / m_durUnit; m_pcount++; - voiceInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + double rvalue = restBefore.getFloat() / m_durUnit; + voiceInfo.restsBefore.push_back(rvalue); // record rest start startTimeRest = endTime; } else { @@ -118819,7 +119400,7 @@ void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart } else { // Not in phrase, so continue if rest; otherwise, // if a note, then record a phrase start. - if (current->isRest()) { + if (current->isRest() || (*current == "*breath")) { // continuing a non-phrase, so do nothing } else { // starting a phrase @@ -118844,13 +119425,14 @@ void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart // process last phrase HumNum endTime = kstart->getLine()->getOwner()->getScoreDuration(); HumNum duration = endTime - startTime; - voiceInfo.phraseDurs.push_back(duration.getFloat() / 2.0); + double value = duration.getFloat() / m_durUnit; + voiceInfo.phraseDurs.push_back(value); voiceInfo.barStarts.push_back(startBarline); voiceInfo.phraseStartToks.push_back(phraseStartTok); - m_sum += duration.getFloat() / 2.0; + m_sum += duration.getFloat() / m_durUnit; m_pcount++; - voiceInfo.restsBefore.push_back(0.0); - voiceInfo.restsBefore.push_back(restBefore.getFloat() / 2.0); + double rvalue = restBefore.getFloat() / m_durUnit; + voiceInfo.restsBefore.push_back(rvalue); } if (m_markQ) { @@ -118859,6 +119441,44 @@ void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart } + +////////////////////////////// +// +// Tool_rphrase::markCompositePhraseStartsInScore -- +// + +void Tool_rphrase::markCompositePhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& compositeInfo) { + stringstream buffer; + for (int i=0; i<(int)compositeInfo.phraseStartToks.size(); i++) { + HTp tok = compositeInfo.phraseStartToks.at(i); + string measure = ""; + if (m_barlineQ) { + measure = to_string(compositeInfo.barStarts.at(i)); + } + double duration = compositeInfo.phraseDurs.at(i); + buffer.str(""); + if (!measure.empty()) { + buffer << "m" << measure << ":"; + } + buffer << twoDigitRound(duration); + int lineIndex = tok->getLineIndex(); + infile[lineIndex].setValue("auto", "rphrase-composite-start", buffer.str()); + } +} + + + +////////////////////////////// +// +// Tool_rphrase::twoDigitRound -- +// + +double Tool_rphrase::twoDigitRound(double input) { + return int(input * 100.0 + 0.499999) / 100.0; +} + + + ////////////////////////////// // // Tool_rphrase::markPhraseStartsInScore -- From de956056fe0fbd0546e27804b6b1a950c34bbdc2 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Wed, 21 Aug 2024 03:56:50 -0700 Subject: [PATCH 350/383] Allow newines and symbols mixing on staff labels. --- src/iohumdrum.cpp | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index ed4bffdf391..70c8a2b4131 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -13682,7 +13682,8 @@ void HumdrumInput::checkForVerseLabels(hum::HTp token) current = current->getNextFieldToken(); } while (current && !current->isStaff()) { - if (!(current->isDataTypeLike("**text") || current->isDataTypeLike("**silbe") || current->isDataTypeLike("**vdata"))) { + if (!(current->isDataTypeLike("**text") || current->isDataTypeLike("**silbe") + || current->isDataTypeLike("**vdata"))) { current = current->getNextFieldToken(); continue; } @@ -18263,11 +18264,11 @@ bool HumdrumInput::setLabelContent(Label *label, const std::string &name) } if (symbol.empty()) { - addTextElement(label, name2); + insertTextWithNewlines(label, name2); } else { if (!prestring.empty()) { - addTextElement(label, prestring); + insertTextWithNewlines(label, prestring); } Rend *rend = new Rend(); Text *text = new Text(); @@ -18276,7 +18277,7 @@ bool HumdrumInput::setLabelContent(Label *label, const std::string &name) label->AddChild(rend); rend->SetGlyphAuth("smufl"); if (!poststring.empty()) { - addTextElement(label, poststring); + insertTextWithNewlines(label, poststring); } // verovio probably eats the space surronding the // rend, so may need to force to be non-breaking space. @@ -18285,6 +18286,30 @@ bool HumdrumInput::setLabelContent(Label *label, const std::string &name) return true; } +////////////////////////////// +// +// HumdrumInput::insertTextWithNewlines -- +// + +void HumdrumInput::insertTextWithNewlines(Label *label, const std::string &text) +{ + vector pieces; + hum::HumRegex hre; + hre.split(pieces, text, "\\\\n"); + if (pieces.size() == 1) { + addTextElement(label, text); + } + else { + for (int i = 0; i < (int)pieces.size(); i++) { + addTextElement(label, pieces.at(i)); + if (i < (int)pieces.size() - 1) { + Lb *lb = new Lb(); + label->AddChild(lb); + } + } + } +} + ////////////////////////////// // // HumdrumInput::setTempoContent -- From a94213390bd7a73d3c86e09c6b092d9bdcdc31a9 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Wed, 21 Aug 2024 04:10:32 -0700 Subject: [PATCH 351/383] Allow rz accents/dynamics. --- src/iohumdrum.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 70c8a2b4131..15137e742f3 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -19340,7 +19340,7 @@ void HumdrumInput::processDynamics(hum::HTp token, int staffindex) ////////////////////////////// // -// HumdrumInput::addDynamicsMark -- Add dynamics marks such as p, f, sfz, rfz. +// HumdrumInput::addDynamicsMark -- Add dynamics marks such as p, f, sfz, rz, rfz. // The dynamics marking will be added at a tstamp rather than a startid. // @@ -19386,6 +19386,9 @@ void HumdrumInput::addDynamicsMark(hum::HTp dyntok, hum::HTp token, hum::HLp lin else if (hre.search(letters, "^s?f+z?p+$")) { dynamic = letters; } + else if (letters == "rz") { + dynamic = letters; + } if (!dynamic.empty()) { int staffadj = ss[si].m_dynamstaffadj; @@ -19633,9 +19636,14 @@ void HumdrumInput::addDynamicsMark(hum::HTp dyntok, hum::HTp token, hum::HLp lin ////////////////////////////// // // HumdrumInput::addSforzandoToNote -- A "z" on a note/chord indicates -// a sforzando mark ("sf", or use "zz" for "sfz"). This will be -// inserted into the floating elements as a with a @startid -// pointing to the note/chord. Other dynamics are placed using @tstamp. +// a sforzando mark. Repeated z's will choose one of the following accents: +// z = sf +// zz = sfz +// zzz = rz +// zzzz = rfz +// This accent be inserted into the floating elements as a with +// a @startid pointing to the note/chord. Other dynamics are placed +// using @tstamp. // void HumdrumInput::addSforzandoToNote(hum::HTp token, int staffindex) @@ -19767,7 +19775,13 @@ void HumdrumInput::addSforzandoToNote(hum::HTp token, int staffindex) data_FONTSIZE fs; fs.SetTerm(FONTSIZETERM_large); rend->SetFontsize(fs); - if (token->find("zz") != std::string::npos) { + if (token->find("zzzz") != std::string::npos) { + addTextElement(rend, "rfz "); + } + else if (token->find("zzzz") != std::string::npos) { + addTextElement(rend, "rz "); + } + else if (token->find("zz") != std::string::npos) { addTextElement(rend, "sfz "); } else { @@ -19784,7 +19798,13 @@ void HumdrumInput::addSforzandoToNote(hum::HTp token, int staffindex) } } else { - if (token->find("zz") != std::string::npos) { + if (token->find("zzzz") != std::string::npos) { + addTextElement(dynam, "rfz"); + } + else if (token->find("zzz") != std::string::npos) { + addTextElement(dynam, "rz"); + } + else if (token->find("zz") != std::string::npos) { addTextElement(dynam, "sfz"); } else { From 6e49bf709a2373d2dcfd3d4c57b5d0e47955b65a Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Wed, 21 Aug 2024 04:11:06 -0700 Subject: [PATCH 352/383] Commit header with updates for staff labels. --- include/vrv/iohumdrum.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/vrv/iohumdrum.h b/include/vrv/iohumdrum.h index 98d356f904e..b36fee2b2e7 100644 --- a/include/vrv/iohumdrum.h +++ b/include/vrv/iohumdrum.h @@ -901,6 +901,7 @@ class HumdrumInput : public vrv::Input { bool hasOmdText(int startline, int endline); void processMeiOptions(hum::HumdrumFile &infile); std::string getInstrumentNumber(hum::HTp icode); + void insertTextWithNewlines(Label *label, const std::string &text); // header related functions: /////////////////////////////////////////// void createHeader(); From b5a1ac3dd83e639b0e8908fdc13cfda1f4380618 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 22 Aug 2024 11:49:39 +0200 Subject: [PATCH 353/383] handle numeral harmonics --- src/iomusxml.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index 001017a6f9d..1b36e508d5a 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -2590,6 +2590,10 @@ void MusicXmlInput::ReadMusicXmlHarmony(pugi::xml_node node, Measure *measure, c int durOffset = 0; std::string harmText = GetContentOfChild(node, "root/root-step"); + if (harmText.empty()) { + pugi::xml_node numeral = node.select_node("numeral/numeral-root").node(); + harmText = numeral.attribute("text") ? numeral.attribute("text").as_string() : numeral.text().as_string(); + } pugi::xpath_node alter = node.select_node("root/root-alter"); if (alter) harmText += ConvertAlterToSymbol(GetContent(alter.node())); pugi::xml_node kind = node.child("kind"); From 900142cad2c05c194bdb1faccea8700b1eef1611 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 26 Aug 2024 10:21:59 +0200 Subject: [PATCH 354/383] take numeral-alter into account --- src/iomusxml.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp index 1b36e508d5a..0432bf0caf1 100644 --- a/src/iomusxml.cpp +++ b/src/iomusxml.cpp @@ -2590,11 +2590,12 @@ void MusicXmlInput::ReadMusicXmlHarmony(pugi::xml_node node, Measure *measure, c int durOffset = 0; std::string harmText = GetContentOfChild(node, "root/root-step"); + pugi::xpath_node alter = node.select_node("root/root-alter"); if (harmText.empty()) { pugi::xml_node numeral = node.select_node("numeral/numeral-root").node(); harmText = numeral.attribute("text") ? numeral.attribute("text").as_string() : numeral.text().as_string(); + alter = node.select_node("numeral/numeral-alter"); } - pugi::xpath_node alter = node.select_node("root/root-alter"); if (alter) harmText += ConvertAlterToSymbol(GetContent(alter.node())); pugi::xml_node kind = node.child("kind"); if (kind) { From 8fa21ddd3f44b2eebf95cce3a00994d08abfb84f Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 2 Sep 2024 10:48:14 +0200 Subject: [PATCH 355/383] Update changelog [skip-ci] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bde5a9c1af1..691b852e5ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [unreleased] +* Support (initial) for Volpiano input +* Support for neumatic notation oriscus and quilisma +* Support for neume layout without facsimile +* Support for numeral harmonics in MusicXML importer (@eNote-GmBH) ## [4.2.1] - 2024-05-07 * Fix GitHub actions (Python release only) From 6055053c02cafe91c697a611ca645691c4920c58 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 2 Sep 2024 14:51:39 +0200 Subject: [PATCH 356/383] add support for end barlines --- src/iovolpiano.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 90a3e0f2504..96d899800b9 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -120,6 +120,14 @@ bool VolpianoInput::Import(const std::string &volpiano) dbl->SetForm(BARRENDITION_dbl); layer->AddChild(dbl); } + else if (ch == '5') { + BarLine *end = new BarLine(); + end->SetForm(BARRENDITION_end); + layer->AddChild(end); + } + else if (ch == '6') { + // not supported yet + } else if (ch == '7') { BarLine *takt = new BarLine(); takt->SetMethod(BARMETHOD_takt); From 44ad2fc41241ebf71c2b13cb7c45fcd5a1828bfc Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Mon, 2 Sep 2024 16:06:26 +0200 Subject: [PATCH 357/383] add warning Co-authored-by: Laurent Pugin --- src/iovolpiano.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iovolpiano.cpp b/src/iovolpiano.cpp index 96d899800b9..58313c9e66d 100644 --- a/src/iovolpiano.cpp +++ b/src/iovolpiano.cpp @@ -126,7 +126,7 @@ bool VolpianoInput::Import(const std::string &volpiano) layer->AddChild(end); } else if (ch == '6') { - // not supported yet + LogWarning("Volpiano '6' barline is not supported"); } else if (ch == '7') { BarLine *takt = new BarLine(); From db67b8caaaa0065714d6c7c9c2b0ff1a0276c2a3 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 3 Sep 2024 12:53:02 +0200 Subject: [PATCH 358/383] Rename methods to avoid ambiguities [skip-ci] --- include/vrv/barline.h | 6 +++--- src/barline.cpp | 6 +++--- src/view_element.cpp | 4 ++-- src/view_page.cpp | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/vrv/barline.h b/include/vrv/barline.h index 3bb1c41a053..407c4ca4ac7 100644 --- a/include/vrv/barline.h +++ b/include/vrv/barline.h @@ -78,9 +78,9 @@ class BarLine : public LayerElement, * @return First entry is true if the attribute was found, second entry contains the value */ ///@{ - std::pair GetLength(const StaffDef *staffDef) const; - std::pair GetMethod(const StaffDef *staffDef) const; - std::pair GetPlace(const StaffDef *staffDef) const; + std::pair GetLengthFromContext(const StaffDef *staffDef) const; + std::pair GetMethodFromContext(const StaffDef *staffDef) const; + std::pair GetPlaceFromContext(const StaffDef *staffDef) const; ///@} //----------// diff --git a/src/barline.cpp b/src/barline.cpp index 9a7beea2900..8a5a6c81c87 100644 --- a/src/barline.cpp +++ b/src/barline.cpp @@ -95,7 +95,7 @@ bool BarLine::IsDrawnThrough(const StaffGrp *staffGrp) const return false; } -std::pair BarLine::GetLength(const StaffDef *staffDef) const +std::pair BarLine::GetLengthFromContext(const StaffDef *staffDef) const { // First check the parent measure const Measure *measure = dynamic_cast(this->GetParent()); @@ -120,7 +120,7 @@ std::pair BarLine::GetLength(const StaffDef *staffDef) const return { false, 0.0 }; } -std::pair BarLine::GetMethod(const StaffDef *staffDef) const +std::pair BarLine::GetMethodFromContext(const StaffDef *staffDef) const { // First check the parent measure const Measure *measure = dynamic_cast(this->GetParent()); @@ -145,7 +145,7 @@ std::pair BarLine::GetMethod(const StaffDef *staffDef) con return { false, BARMETHOD_NONE }; } -std::pair BarLine::GetPlace(const StaffDef *staffDef) const +std::pair BarLine::GetPlaceFromContext(const StaffDef *staffDef) const { // First check the parent measure const Measure *measure = dynamic_cast(this->GetParent()); diff --git a/src/view_element.cpp b/src/view_element.cpp index d14d3715d42..250400ac4bd 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -423,9 +423,9 @@ void View::DrawBarLine(DeviceContext *dc, LayerElement *element, Layer *layer, S StaffDef *drawingStaffDef = staff->m_drawingStaffDef; // Determine the method assert(drawingStaffDef); - auto [hasMethod, method] = barLine->GetMethod(drawingStaffDef); + auto [hasMethod, method] = barLine->GetMethodFromContext(drawingStaffDef); if (barLine->HasMethod()) { - method = barLine->AttBarLineVis::GetMethod(); + method = barLine->GetMethod(); } dc->StartGraphic(element, "", element->GetID()); diff --git a/src/view_page.cpp b/src/view_page.cpp index 626aa7f91e7..d8e7797b34a 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -772,7 +772,7 @@ void View::DrawBarLines(DeviceContext *dc, Measure *measure, StaffGrp *staffGrp, } // Determine the method - const auto [hasMethod, method] = barLine->GetMethod(staffDef); + const auto [hasMethod, method] = barLine->GetMethodFromContext(staffDef); const bool methodMensur = hasMethod && (method == BARMETHOD_mensur); const bool methodTakt = hasMethod && (method == BARMETHOD_takt); @@ -798,7 +798,7 @@ void View::DrawBarLines(DeviceContext *dc, Measure *measure, StaffGrp *staffGrp, // Adjust start and length if (!methodMensur && !methodTakt) { - const auto [hasPlace, place] = barLine->GetPlace(staffDef); + const auto [hasPlace, place] = barLine->GetPlaceFromContext(staffDef); if (hasPlace) { // bar.place counts upwards (note order). yBottom += place * unit; @@ -808,7 +808,7 @@ void View::DrawBarLines(DeviceContext *dc, Measure *measure, StaffGrp *staffGrp, yBottom -= 2 * unit; } - const auto [hasLength, length] = barLine->GetLength(staffDef); + const auto [hasLength, length] = barLine->GetLengthFromContext(staffDef); if (hasLength) { yLength = length * unit; } From fe5cc93bdbed5ddb33e50cf7ba6bb8b2bffeff62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:47:43 +0000 Subject: [PATCH 359/383] Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/ci_build.yml | 4 ++-- .github/workflows/python-ci-wheel.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 4d7384dd54d..42642cbb3a1 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -366,7 +366,7 @@ jobs: path: ${{ env.GH_PAGES_DIR }} - name: Download TOOLKIT_BUILD artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 with: name: ${{ env.TOOLKIT_BUILD }} path: artifacts/${{ env.TOOLKIT_BUILD }} @@ -485,7 +485,7 @@ jobs: path: ${{ env.DOXYGEN_DIR }} - name: Download DOC_BUILD artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 with: name: ${{ env.DOC_BUILD }} path: artifacts/${{ env.DOC_BUILD }} diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index 2c337e47b39..6a939e31a3a 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -256,7 +256,7 @@ jobs: #===============================================# # Prepare artifacts - name: Download artifacts - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@v4.1.7 # v4.1.7 with: path: bindings/python/artifacts/ From 783b7c63875751751b50b9ee2287dda1ab78f956 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Wed, 4 Sep 2024 22:06:43 +0200 Subject: [PATCH 360/383] Revert "Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows" --- .github/workflows/ci_build.yml | 4 ++-- .github/workflows/python-ci-wheel.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 42642cbb3a1..4d7384dd54d 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -366,7 +366,7 @@ jobs: path: ${{ env.GH_PAGES_DIR }} - name: Download TOOLKIT_BUILD artifacts - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v3 with: name: ${{ env.TOOLKIT_BUILD }} path: artifacts/${{ env.TOOLKIT_BUILD }} @@ -485,7 +485,7 @@ jobs: path: ${{ env.DOXYGEN_DIR }} - name: Download DOC_BUILD artifacts - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v3 with: name: ${{ env.DOC_BUILD }} path: artifacts/${{ env.DOC_BUILD }} diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml index 6a939e31a3a..2c337e47b39 100644 --- a/.github/workflows/python-ci-wheel.yml +++ b/.github/workflows/python-ci-wheel.yml @@ -256,7 +256,7 @@ jobs: #===============================================# # Prepare artifacts - name: Download artifacts - uses: actions/download-artifact@v4.1.7 # v4.1.7 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: path: bindings/python/artifacts/ From 7007d18a554d305f63f5fdde916798426698b70a Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Thu, 5 Sep 2024 00:37:27 -0700 Subject: [PATCH 361/383] Deg positioning lost below placement as a default. --- src/iohumdrum.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 15137e742f3..7eb039bc283 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -11105,10 +11105,7 @@ void HumdrumInput::addHarmFloatsForMeasure(int startline, int endline) place = "above"; } else { - int belowQ = token->getValueInt("auto", "below"); - if (belowQ) { - place = "below"; - } + place = "below"; } if (place.size() > 0) { setPlaceRelStaff(harm, place, false); From e49dce26b0c2991a24ef52f37568de2f61f888aa Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Thu, 5 Sep 2024 09:03:39 -0700 Subject: [PATCH 362/383] Added automatic identifcation EsAC input data. --- src/toolkit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 7b4edae8ea2..cfb0720caaf 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -298,11 +298,17 @@ FileFormat Toolkit::IdentifyInputFrom(const std::string &data) return UNKNOWN; } if (initial.find("\n!!") != std::string::npos) { + // Case where there are empty lines before content in Humdrum files. return HUMDRUM; } if (initial.find("\n**") != std::string::npos) { + // Case where there are empty lines before content in Humdrum files. return HUMDRUM; } + if (initial.find("\nCUT[") != std::string::npos) { + // Title record for a melody in EsAC format. + return ESAC; + } // Assume that the input is MEI if other input types were not detected. // This means that DARMS cannot be auto-detected. From 756b0384b05b9d5a08530c9ae364db07ef6a8e8b Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 22:01:36 +0200 Subject: [PATCH 363/383] Add override to Reset() --- include/vrv/divline.h | 2 +- include/vrv/liquescent.h | 2 +- include/vrv/object.h | 2 +- include/vrv/oriscus.h | 2 +- include/vrv/quilisma.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/vrv/divline.h b/include/vrv/divline.h index 98622e62d5f..2268aa95df7 100644 --- a/include/vrv/divline.h +++ b/include/vrv/divline.h @@ -38,7 +38,7 @@ class DivLine : public LayerElement, DivLine(); virtual ~DivLine(); virtual Object *Clone() const { return new DivLine(*this); } - virtual void Reset(); + void Reset() override; virtual std::string GetClassName() const { return "DivLine"; } ///@} diff --git a/include/vrv/liquescent.h b/include/vrv/liquescent.h index e64d63c15aa..930ca189559 100644 --- a/include/vrv/liquescent.h +++ b/include/vrv/liquescent.h @@ -30,7 +30,7 @@ class Liquescent : public LayerElement, public PitchInterface, public PositionIn Liquescent(); virtual ~Liquescent(); virtual Object *Clone() const { return new Liquescent(*this); } - virtual void Reset(); + void Reset() override; virtual std::string GetClassName() const { return "Liquescent"; } ///@} diff --git a/include/vrv/object.h b/include/vrv/object.h index 18b0b59b47b..20f07778e0d 100644 --- a/include/vrv/object.h +++ b/include/vrv/object.h @@ -165,7 +165,7 @@ class Object : public BoundingBox { const Resources *GetDocResources() const; /** - * Reset the object, that is 1) removing all childs and 2) resetting all attributes. + * Reset the object, that is 1) removing all children and 2) resetting all attributes. * The method is virtual, so _always_ call the parent in the method overriding it. */ virtual void Reset(); diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h index 391784a6e27..caafbee32e9 100644 --- a/include/vrv/oriscus.h +++ b/include/vrv/oriscus.h @@ -30,7 +30,7 @@ class Oriscus : public LayerElement, public PitchInterface, public PositionInter Oriscus(); virtual ~Oriscus(); virtual Object *Clone() const { return new Oriscus(*this); } - virtual void Reset(); + void Reset() override; virtual std::string GetClassName() const { return "Oriscus"; } ///@} diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h index 2ffbf576f94..79f92bc714d 100644 --- a/include/vrv/quilisma.h +++ b/include/vrv/quilisma.h @@ -30,7 +30,7 @@ class Quilisma : public LayerElement, public PitchInterface, public PositionInte Quilisma(); virtual ~Quilisma(); virtual Object *Clone() const { return new Quilisma(*this); } - virtual void Reset(); + void Reset() override; virtual std::string GetClassName() const { return "Quilisma"; } ///@} From 296fb95948e3f4046c7f68dff13d3b9df17118f1 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 22:10:59 +0200 Subject: [PATCH 364/383] Add override to Clone() --- include/vrv/divline.h | 4 ++-- include/vrv/liquescent.h | 2 +- include/vrv/object.h | 8 ++++---- include/vrv/oriscus.h | 2 +- include/vrv/quilisma.h | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/vrv/divline.h b/include/vrv/divline.h index 2268aa95df7..2a776a442dc 100644 --- a/include/vrv/divline.h +++ b/include/vrv/divline.h @@ -37,7 +37,7 @@ class DivLine : public LayerElement, ///@{ DivLine(); virtual ~DivLine(); - virtual Object *Clone() const { return new DivLine(*this); } + Object *Clone() const override { return new DivLine(*this); } void Reset() override; virtual std::string GetClassName() const { return "DivLine"; } ///@} @@ -84,7 +84,7 @@ class DivLineAttr : public DivLine { ///@{ DivLineAttr(); virtual ~DivLineAttr(); - virtual Object *Clone() const { return new DivLineAttr(*this); } + Object *Clone() const override { return new DivLineAttr(*this); } virtual std::string GetClassName() const { return "DivLineAttr"; } ///@} diff --git a/include/vrv/liquescent.h b/include/vrv/liquescent.h index 930ca189559..e9f9c6bc328 100644 --- a/include/vrv/liquescent.h +++ b/include/vrv/liquescent.h @@ -29,7 +29,7 @@ class Liquescent : public LayerElement, public PitchInterface, public PositionIn ///@{ Liquescent(); virtual ~Liquescent(); - virtual Object *Clone() const { return new Liquescent(*this); } + Object *Clone() const override { return new Liquescent(*this); } void Reset() override; virtual std::string GetClassName() const { return "Liquescent"; } ///@} diff --git a/include/vrv/object.h b/include/vrv/object.h index 20f07778e0d..b7bc09db95f 100644 --- a/include/vrv/object.h +++ b/include/vrv/object.h @@ -820,8 +820,8 @@ class Object : public BoundingBox { class ObjectListInterface { public: // constructors and destructors - ObjectListInterface(){}; - virtual ~ObjectListInterface(){}; + ObjectListInterface() {}; + virtual ~ObjectListInterface() {}; ObjectListInterface(const ObjectListInterface &listInterface); // copy constructor; ObjectListInterface &operator=(const ObjectListInterface &listInterface); // copy assignment; @@ -919,8 +919,8 @@ class ObjectListInterface { class TextListInterface : public ObjectListInterface { public: // constructors and destructors - TextListInterface(){}; - virtual ~TextListInterface(){}; + TextListInterface() {}; + virtual ~TextListInterface() {}; /** * Returns a contatenated version of all the text children diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h index caafbee32e9..826389057b9 100644 --- a/include/vrv/oriscus.h +++ b/include/vrv/oriscus.h @@ -29,7 +29,7 @@ class Oriscus : public LayerElement, public PitchInterface, public PositionInter ///@{ Oriscus(); virtual ~Oriscus(); - virtual Object *Clone() const { return new Oriscus(*this); } + Object *Clone() const override { return new Oriscus(*this); } void Reset() override; virtual std::string GetClassName() const { return "Oriscus"; } ///@} diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h index 79f92bc714d..0a22037fd1d 100644 --- a/include/vrv/quilisma.h +++ b/include/vrv/quilisma.h @@ -29,7 +29,7 @@ class Quilisma : public LayerElement, public PitchInterface, public PositionInte ///@{ Quilisma(); virtual ~Quilisma(); - virtual Object *Clone() const { return new Quilisma(*this); } + Object *Clone() const override { return new Quilisma(*this); } void Reset() override; virtual std::string GetClassName() const { return "Quilisma"; } ///@} From 0e260fd5dc5238693b064f03cd68f22fc979266c Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 22:16:21 +0200 Subject: [PATCH 365/383] Add override to GetClassName() --- include/vrv/divline.h | 4 ++-- include/vrv/liquescent.h | 2 +- include/vrv/oriscus.h | 2 +- include/vrv/quilisma.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/vrv/divline.h b/include/vrv/divline.h index 2a776a442dc..9206ccd86fb 100644 --- a/include/vrv/divline.h +++ b/include/vrv/divline.h @@ -39,7 +39,7 @@ class DivLine : public LayerElement, virtual ~DivLine(); Object *Clone() const override { return new DivLine(*this); } void Reset() override; - virtual std::string GetClassName() const { return "DivLine"; } + std::string GetClassName() const override { return "DivLine"; } ///@} /** Override the method since alignment is required */ @@ -85,7 +85,7 @@ class DivLineAttr : public DivLine { DivLineAttr(); virtual ~DivLineAttr(); Object *Clone() const override { return new DivLineAttr(*this); } - virtual std::string GetClassName() const { return "DivLineAttr"; } + std::string GetClassName() const override { return "DivLineAttr"; } ///@} // void SetLeft() { m_isLeft = true; } diff --git a/include/vrv/liquescent.h b/include/vrv/liquescent.h index e9f9c6bc328..ede3b1c785b 100644 --- a/include/vrv/liquescent.h +++ b/include/vrv/liquescent.h @@ -31,7 +31,7 @@ class Liquescent : public LayerElement, public PitchInterface, public PositionIn virtual ~Liquescent(); Object *Clone() const override { return new Liquescent(*this); } void Reset() override; - virtual std::string GetClassName() const { return "Liquescent"; } + std::string GetClassName() const override { return "Liquescent"; } ///@} /** diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h index 826389057b9..0de7a015f44 100644 --- a/include/vrv/oriscus.h +++ b/include/vrv/oriscus.h @@ -31,7 +31,7 @@ class Oriscus : public LayerElement, public PitchInterface, public PositionInter virtual ~Oriscus(); Object *Clone() const override { return new Oriscus(*this); } void Reset() override; - virtual std::string GetClassName() const { return "Oriscus"; } + std::string GetClassName() const override { return "Oriscus"; } ///@} /** diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h index 0a22037fd1d..5d6285c4d23 100644 --- a/include/vrv/quilisma.h +++ b/include/vrv/quilisma.h @@ -31,7 +31,7 @@ class Quilisma : public LayerElement, public PitchInterface, public PositionInte virtual ~Quilisma(); Object *Clone() const override { return new Quilisma(*this); } void Reset() override; - virtual std::string GetClassName() const { return "Quilisma"; } + std::string GetClassName() const override { return "Quilisma"; } ///@} /** From e5f01e485599bf61b7f90a800325d02ad2ee284e Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 22:28:41 +0200 Subject: [PATCH 366/383] Add override to HasToBeAligned() --- include/vrv/divline.h | 2 +- include/vrv/layerelement.h | 2 +- include/vrv/liquescent.h | 2 +- include/vrv/oriscus.h | 2 +- include/vrv/quilisma.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/vrv/divline.h b/include/vrv/divline.h index 9206ccd86fb..9ad45fee68c 100644 --- a/include/vrv/divline.h +++ b/include/vrv/divline.h @@ -43,7 +43,7 @@ class DivLine : public LayerElement, ///@} /** Override the method since alignment is required */ - virtual bool HasToBeAligned() const { return true; } + bool HasToBeAligned() const override { return true; } /** * Use to set the alignment for the Measure BarLine members. diff --git a/include/vrv/layerelement.h b/include/vrv/layerelement.h index ea8307a9da1..4740e64373b 100644 --- a/include/vrv/layerelement.h +++ b/include/vrv/layerelement.h @@ -80,7 +80,7 @@ class LayerElement : public Object, /** * Return true if the element has to be aligned horizontally - * It typically set to false for mRest, mRpt, etc. + * It is typically set to false for mRest, mRpt, etc. */ virtual bool HasToBeAligned() const { return false; } diff --git a/include/vrv/liquescent.h b/include/vrv/liquescent.h index ede3b1c785b..cdfd35076a5 100644 --- a/include/vrv/liquescent.h +++ b/include/vrv/liquescent.h @@ -42,7 +42,7 @@ class Liquescent : public LayerElement, public PitchInterface, public PositionIn ///@} /** Override the method since alignment is required */ - virtual bool HasToBeAligned() const { return true; } + bool HasToBeAligned() const override { return true; } private: // diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h index 0de7a015f44..a67fe1a6a18 100644 --- a/include/vrv/oriscus.h +++ b/include/vrv/oriscus.h @@ -42,7 +42,7 @@ class Oriscus : public LayerElement, public PitchInterface, public PositionInter ///@} /** Override the method since alignment is required */ - virtual bool HasToBeAligned() const { return true; } + bool HasToBeAligned() const override { return true; } private: // diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h index 5d6285c4d23..cb7b10f7b5c 100644 --- a/include/vrv/quilisma.h +++ b/include/vrv/quilisma.h @@ -42,7 +42,7 @@ class Quilisma : public LayerElement, public PitchInterface, public PositionInte ///@} /** Override the method since alignment is required */ - virtual bool HasToBeAligned() const { return true; } + bool HasToBeAligned() const override { return true; } private: // From d3b04d6b77010b504e7f5ac8de8116ab24116ea8 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 22:33:20 +0200 Subject: [PATCH 367/383] Add override to GetPitchInterface() --- include/vrv/liquescent.h | 2 +- include/vrv/oriscus.h | 2 +- include/vrv/quilisma.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/vrv/liquescent.h b/include/vrv/liquescent.h index cdfd35076a5..6a753ddb0d4 100644 --- a/include/vrv/liquescent.h +++ b/include/vrv/liquescent.h @@ -38,7 +38,7 @@ class Liquescent : public LayerElement, public PitchInterface, public PositionIn * @name Getter to interfaces */ ///@{ - virtual PitchInterface *GetPitchInterface() { return dynamic_cast(this); } + PitchInterface *GetPitchInterface() override { return dynamic_cast(this); } ///@} /** Override the method since alignment is required */ diff --git a/include/vrv/oriscus.h b/include/vrv/oriscus.h index a67fe1a6a18..775c8bb6c78 100644 --- a/include/vrv/oriscus.h +++ b/include/vrv/oriscus.h @@ -38,7 +38,7 @@ class Oriscus : public LayerElement, public PitchInterface, public PositionInter * @name Getter to interfaces */ ///@{ - virtual PitchInterface *GetPitchInterface() { return dynamic_cast(this); } + PitchInterface *GetPitchInterface() override { return dynamic_cast(this); } ///@} /** Override the method since alignment is required */ diff --git a/include/vrv/quilisma.h b/include/vrv/quilisma.h index cb7b10f7b5c..9933f505772 100644 --- a/include/vrv/quilisma.h +++ b/include/vrv/quilisma.h @@ -38,7 +38,7 @@ class Quilisma : public LayerElement, public PitchInterface, public PositionInte * @name Getter to interfaces */ ///@{ - virtual PitchInterface *GetPitchInterface() { return dynamic_cast(this); } + PitchInterface *GetPitchInterface() override { return dynamic_cast(this); } ///@} /** Override the method since alignment is required */ From 2542253e3c6b0282e92043a3345f510b5d1e0f83 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 22:50:13 +0200 Subject: [PATCH 368/383] Simplify LedgerLine class Remove virtual destructor and Reset method as they are not needed. --- include/vrv/staff.h | 7 +------ src/staff.cpp | 12 ------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/include/vrv/staff.h b/include/vrv/staff.h index 92a699a43bf..d49d48f0c8c 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -36,13 +36,8 @@ class LedgerLine { public: /** * @name Constructors, destructors, reset methods - * Reset method reset all attribute classes */ - ///@{ - LedgerLine(); - virtual ~LedgerLine(); - virtual void Reset(); - ///@} + LedgerLine() = default; /** * Add a dash to the ledger line object. diff --git a/src/staff.cpp b/src/staff.cpp index 9360300b579..2a71ace62d8 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -298,18 +298,6 @@ int Staff::GetNearestInterStaffPosition(int y, const Doc *doc, data_STAFFREL pla // LedgerLine //---------------------------------------------------------------------------- -LedgerLine::LedgerLine() -{ - this->Reset(); -} - -LedgerLine::~LedgerLine() {} - -void LedgerLine::Reset() -{ - m_dashes.clear(); -} - void LedgerLine::AddDash(int left, int right, int extension) { assert(left < right); From 24b3b889e7053f6539cfc927be4a2a321cd1fbe7 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 6 Sep 2024 23:23:30 +0200 Subject: [PATCH 369/383] Mark constructor/destructor as default --- include/vrv/object.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/vrv/object.h b/include/vrv/object.h index b7bc09db95f..7da3e2c1309 100644 --- a/include/vrv/object.h +++ b/include/vrv/object.h @@ -820,8 +820,8 @@ class Object : public BoundingBox { class ObjectListInterface { public: // constructors and destructors - ObjectListInterface() {}; - virtual ~ObjectListInterface() {}; + ObjectListInterface() = default; + virtual ~ObjectListInterface() = default; ObjectListInterface(const ObjectListInterface &listInterface); // copy constructor; ObjectListInterface &operator=(const ObjectListInterface &listInterface); // copy assignment; @@ -919,8 +919,8 @@ class ObjectListInterface { class TextListInterface : public ObjectListInterface { public: // constructors and destructors - TextListInterface() {}; - virtual ~TextListInterface() {}; + TextListInterface() = default; + virtual ~TextListInterface() = default; /** * Returns a contatenated version of all the text children From 6c4fdf3363108bee0fe68989b234a14ad40f4302 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sat, 7 Sep 2024 06:56:43 -0700 Subject: [PATCH 370/383] Humlib updates (esp. EsAC-to-Humdrum converter) --- include/hum/humlib.h | 332 +- src/hum/humlib.cpp | 66453 ++++++++++++++++++++++------------------- 2 files changed, 35983 insertions(+), 30802 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 5dfe597853f..374437551e4 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Aug 8 21:55:11 PDT 2024 +// Last Modified: Thu Sep 5 14:41:50 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -964,9 +964,9 @@ class HumInstrument { int setGM (const std::string& Hname, int aValue); private: - int index; - static std::vector<_HumInstrument> data; - static int classcount; + int m_index; + static std::vector<_HumInstrument> m_data; + static int m_classcount; protected: void initialize (void); @@ -2043,6 +2043,7 @@ class HumdrumFileBase : public HumHash { bool isRhythmAnalyzed (void); bool areStrandsAnalyzed (void); bool areStrophesAnalyzed (void); + void setFilenameFromSegment (void); template void initializeArray (std::vector>& array, TYPE value); @@ -7358,6 +7359,213 @@ class Tool_double : public HumTool { }; +class Tool_esac2hum : public HumTool { + public: + + class Note { + public: + std::vector m_errors; + std::string esac; + int m_dots = 0; + int m_underscores = 0; + int m_octave = 0; + int m_degree = 0; // scale degree (wrt major key) + int m_b40degree = 0; // scale degree as b40 interval + int m_alter = 0; // chromatic alteration of degree (flats/sharp from major scale degrees) + double m_ticks = 0.0; + bool m_tieBegin = false; + bool m_tieEnd = false; + bool m_phraseBegin = false; + bool m_phraseEnd = false; + std::string m_humdrum; // **kern conversion of EsAC note + int m_b40 = 0; // absolute b40 pitch (-1000 = rest); + int m_b12 = 0; // MIDI note number (-1000 = rest); + HumNum m_factor = 1; // for triplet which is 2/3 duration + + void calculateRhythms(int minrhy); + void calculatePitches(int tonic); + bool parseNote(const std::string& note, HumNum factor); + void generateHumdrum(int minrhy, int b40tonic); + bool isPitch(void); + bool isRest(void); + std::string getScaleDegree(void); + }; + + class Measure : public std::vector { + public: + std::vector m_errors; + std::string esac; + int m_barnum = -1000; // -1000 == unassigned bar number for this measure + // m_barnum = -1 == invisible barline (between two partial measures) + // m_barnum = 0 == pickup measure (partial measure at start of music) + double m_ticks = 0.0; + double m_tsticks = 0.0; + // m_measureTimeSignature is a **kern time signature + // (change) to display in the converted score. + std::string m_measureTimeSignature = ""; + bool m_partialBegin = false; // start of an incomplete measure + bool m_partialEnd = false; // end of an incomplete measure (pickup) + bool m_complete = false; // a complste measure + void calculateRhythms(int minrhy); + void calculatePitches(int tonic); + bool parseMeasure(const std::string& measure); + bool isUnassigned(void); + void setComplete(void); + bool isComplete(void); + void setPartialBegin(void); + bool isPartialBegin(void); + void setPartialEnd(void); + bool isPartialEnd(void); + }; + + class Phrase : public std::vector { + public: + std::vector m_errors; + double m_ticks = 0.0; + std::string esac; + void calculateRhythms(int minrhy); + void calculatePitches(int tonic); + bool parsePhrase(const std::string& phrase); + std::string getLastScaleDegree(); + void getNoteList(std::vector& notelist); + std::string getNO_REP(void); + int getFullMeasureCount(void); + }; + + class Score : public std::vector { + public: + int m_b40tonic = 0; + int m_minrhy = 0; + std::string m_clef; + std::string m_keysignature; + std::string m_keydesignation; + std::string m_timesig; + std::map m_params; + std::vector m_errors; + bool m_finalBarline = false; + bool hasFinalBarline(void) { return m_finalBarline; } + void calculateRhythms(int minrhy); + void calculatePitches(int tonic); + bool parseMel(const std::string& mel); + void analyzeTies(void); + void analyzePhrases(void); + void getNoteList(std::vector& notelist); + void getMeasureList(std::vector& measurelist); + void getPhraseNoteList(std::vector& notelist, int index); + void generateHumdrumNotes(void); + void calculateClef(void); + void calculateKeyInformation(void); + void calculateTimeSignatures(void); + void setAllTimesigTicks(double ticks); + void assignFreeMeasureNumbers(void); + void assignSingleMeasureNumbers(void); + void prepareMultipleTimeSignatures(const std::string& ts); + + void doAnalyses(void); + void analyzeMEL_SEM(void); + void analyzeMEL_RAW(void); + void analyzeNO_REP(void); + void analyzeRTM(void); + void analyzeSCL_DEG(void); + void analyzeSCL_SEM(void); + void analyzePHR_NO(void); + void analyzePHR_BARS(void); + void analyzePHR_CAD(void); + void analyzeACC(void); + }; + + Tool_esac2hum (void); + ~Tool_esac2hum () {}; + + bool convertFile (std::ostream& out, const std::string& filename); + bool convert (std::ostream& out, const std::string& input); + bool convert (std::ostream& out, std::istream& input); + + + protected: + void initialize (void); + + void convertEsacToHumdrum(std::ostream& output, std::istream& infile); + bool getSong (std::vector& song, std::istream& infile); + void convertSong (std::ostream& output, std::vector& infile); + static std::string trimSpaces (const std::string& input); + void printHeader (std::ostream& output); + void printFooter (std::ostream& output, std::vector& infile); + void printConversionDate (std::ostream& output); + void printPdfLinks (std::ostream& output); + void printParameters (void); + void printPageNumbers (std::ostream& output); + void getParameters (std::vector& infile); + void cleanText (std::string& buffer); + std::string createFilename (void); + void printBemComment (std::ostream& output); + void processSong (void); + void printScoreContents (std::ostream& output); + void embedAnalyses (std::ostream& output); + + private: + bool m_debugQ = false; // used with --debug option + bool m_verboseQ = false; // used with --verbose option + std::string m_verbose; // p = print EsAC phrases, m = print measures, n = print notes. + // t after p, m, or n means print tick info + bool m_embedEsacQ = true; // used with -E option + bool m_dwokQ = false; // true if source is Oskar Kolberg: Dzieła Wszystkie + // (Oskar Kolberg: Complete Works) + // determined automatically if header line or TRD source contains "DWOK" string. + bool m_analysisQ = false; // used with -a option + + int m_inputline = 0; // used to keep track if the EsAC input line. + + std::string m_filePrefix; + std::string m_filePostfix = ".krn"; + bool m_fileTitleQ = false; + + std::string m_prevline; + std::string m_cutline; + std::vector m_globalComments; + + int m_minrhy = 0; + + Tool_esac2hum::Score m_score; + + std::map m_bem_translation = { + {"Czwarta zwrotka pieśni Niech będzie Jezus Chrystus pochwalony", "Fourth verse of the song \"Let Jesus Christ be praised\""}, + {"Do oczepin, zakodować w A?", "For the unveiling, encode in A?"}, + {"Do oczepin", "For the unveiling"}, + {"Druga zwrotka poprzedniej pieśni", "Second verse of the previous song"}, + {"Gdy zdejmują wianek", "When they remove the wreath"}, + {"Jak do ślubu odjeżdżają (na wozie)", "As they depart for the wedding (on a wagon)"}, + {"Krakowiak", "Krakowiak"}, + {"Marsz (konfederatów Barskich) Przygrywka na trąbie", "March (of the Bar Confederates) Prelude on trumpet"}, + {"Marsz konfederatów Barskich", "March of the Bar Confederates"}, + {"Mazur", "Mazur"}, + {"Na przodziek", "At the front"}, + {"Owczarek gładki, szybko tańczony", "Smooth shepherd's dance, danced quickly"}, + {"Owczarek", "Shepherd's dance"}, + {"Piosenka żniwiarska", "Harvest song"}, + {"Polonez (pokutującego wojaka)", "Polonaise (of the penitent soldier)"}, + {"Polonez", "Polonaise"}, + {"Polski chodzony", "Polish walking dance"}, + {"Prawdopodobnie ośmiomiar 2+3+3", "Probably an eight-measure 2+3+3"}, + {"Prawdopodobnie rytm jambiczny", "Probably iambic rhythm"}, + {"Przy przenosinach", "During the moving"}, + {"Tańczy na przodziek (na weselu)", "Dances at the front (at the wedding)"}, + {"W sobotę gdy wianki wiją", "On Saturday when they weave the wreaths"}, + {"Wesele do ślubu", "Wedding to the church"}, + {"Wesele", "Wedding"}, + {"Weselna gdy zbierają składkę", "Wedding song when collecting contributions"}, + {"Weselna", "Wedding song"}, + {"Wielkanoc", "Easter"}, + {"Wieniec", "Wreath"}, + {"Zakodować w C?", "Encode in C?"}, + {"w t. 10 wpisany tryl dziadowski 43b21", "in measure 10, a beggar's trill written 43b21"}, + {"żniwiarska", "Harvest song"} + }; + + +}; + + #define ND_NOTE 0 /* notes or rests + text and phrase markings */ #define ND_BAR 1 /* explicit barlines */ @@ -7387,10 +7595,10 @@ class NoteData { -class Tool_esac2hum : public HumTool { +class Tool_esac2humold : public HumTool { public: - Tool_esac2hum (void); - ~Tool_esac2hum () {}; + Tool_esac2humold (void); + ~Tool_esac2humold () {}; bool convertFile (std::ostream& out, const std::string& filename); bool convert (std::ostream& out, const std::string& input); @@ -7438,16 +7646,18 @@ class Tool_esac2hum : public HumTool { void printHumdrumFooterInfo(std::ostream& out, std::vector& song); private: - int debugQ = 0; // used with --debug option - int verboseQ = 0; // used with -v option - int splitQ = 0; // used with -s option - int firstfilenum = 1; // used with -f option - std::vector header; // used with -h option - std::vector trailer; // used with -t option - std::string fileextension; // used with -x option - std::string namebase; // used with -s option - - std::vector chartable; // used printChars() & printSpecialChars() + int debugQ = 0; // used with --debug option + int verboseQ = 0; // used with -v option + int splitQ = 0; // used with -s option + int firstfilenum = 1; // used with -f option + std::vector header; // used with -h option + std::vector trailer; // used with -t option + std::string fileextension; // used with -x option + std::string namebase; // used with -s option + + // Modern ESaC files use UTF-8 characters, older ESaC files use + // ASCII encodings of non-UTF7 characters: + std::vector chartable; // used in printChars() & printSpecialChars() int inputline = 0; }; @@ -10939,6 +11149,94 @@ class Tool_tabber : public HumTool { +class Tool_tandeminfo : public HumTool { + public: + class Entry { + public: + HTp token = NULL; + std::string description; + int count = 0; + }; + + Tool_tandeminfo (void); + ~Tool_tandeminfo () {}; + + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const std::string& indata, std::ostream& out); + bool run (HumdrumFile& infile, std::ostream& out); + + + protected: + void initialize (void); + void processFile (HumdrumFile& infile); + void printEntries (HumdrumFile& infile); + void printEntriesHtml (HumdrumFile& infile); + void printEntriesText (HumdrumFile& infile); + void doCountAnalysis (void); + + std::string getDescription (HTp token); + std::string checkForKeySignature (const std::string& tok); + std::string checkForKeyDesignation (const std::string& tok); + std::string checkForInstrumentInfo (const std::string& tok); + std::string checkForLabelInfo (const std::string& tok); + std::string checkForTimeSignature (const std::string& tok); + std::string checkForMeter (const std::string& tok); + std::string checkForTempoMarking (const std::string& tok); + std::string checkForClef (const std::string& tok); + std::string checkForStaffPartGroup (const std::string& tok); + std::string checkForTuplet (const std::string& tok); + std::string checkForHands (const std::string& tok); + std::string checkForPosition (const std::string& tok); + std::string checkForCue (const std::string& tok); + std::string checkForFlip (const std::string& tok); + std::string checkForTremolo (const std::string& tok); + std::string checkForOttava (const std::string& tok); + std::string checkForPedal (const std::string& tok); + std::string checkForBracket (const std::string& tok); + std::string checkForRscale (const std::string& tok); + std::string checkForTimebase (const std::string& tok); + std::string checkForTransposition (const std::string& tok); + std::string checkForGrp (const std::string& tok); + std::string checkForStria (const std::string& tok); + std::string checkForFont (const std::string& tok); + std::string checkForVerseLabels (const std::string& tok); + std::string checkForLanguage (const std::string& tok); + std::string checkForStemInfo (const std::string& tok); + std::string checkForXywh (const std::string& tok); + std::string checkForCustos (const std::string& tok); + std::string checkForTextInterps (const std::string& tok); + std::string checkForRep (const std::string& tok); + std::string checkForPline (const std::string& tok); + std::string checkForTacet (const std::string& tok); + std::string checkForFb (const std::string& tok); + std::string checkForColor (const std::string& tok); + std::string checkForThru (const std::string& tok); + + private: + bool m_exclusiveQ = true; // used with -X option (don't print exclusive interpretation) + bool m_unknownQ = false; // used with -u option (print only unknown tandem interpretations) + bool m_filenameQ = false; // used with -f option (print only unknown tandem interpretations) + bool m_descriptionQ = false; // used with -m option (print description of interpretation) + bool m_locationQ = false; // used with -l option (print location of interpretation in file) + bool m_zeroQ = false; // used with -z option (location address by 0-index) + bool m_tableQ = false; // used with -t option (display results as HTML table) + bool m_closeQ = false; // used with --close option (HTML details closed by default) + bool m_sortQ = false; // used with -s (sort entries alphabetically by tandem interpretation) + bool m_headerOnlyQ = false; // used with -h option (process only header interpretations) + bool m_bodyOnlyQ = false; // used with -H option (process only body interpretations) + bool m_countQ = false; // used with -c option (only show unique list with counts); + bool m_sortByCountQ = false; // used with -c and -n options (sort from low to high count) + bool m_sortByReverseCountQ = false; // used with -c and -N options (sort from high to low count) + bool m_humdrumQ = false; // used with --humdrum option (output data formatted with Humdrum syntax) + + std::string m_unknown = "unknown"; + + std::vector m_entries; + std::map m_count; +}; + + class Tool_tassoize : public HumTool { public: Tool_tassoize (void); diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 1b5e14d1d75..0e37f29e2d9 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Aug 8 21:55:11 PDT 2024 +// Last Modified: Thu Sep 5 14:41:50 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -4255,721 +4255,721 @@ string Convert::getLanguageName(const string& abbreviation) { if (abbreviation[i] == '@') { continue; } - code.push_back(tolower(abbreviation[i])); + code.push_back(toupper(abbreviation[i])); } if (code.size() == 2) { // ISO 639-1 language codes - if (code == "aa") { return "Afar"; } - if (code == "ab") { return "Abkhazian"; } - if (code == "ae") { return "Avestan"; } - if (code == "af") { return "Afrikaans"; } - if (code == "ak") { return "Akan"; } - if (code == "am") { return "Amharic"; } - if (code == "an") { return "Aragonese"; } - if (code == "ar") { return "Arabic"; } - if (code == "as") { return "Assamese"; } - if (code == "av") { return "Avaric"; } - if (code == "ay") { return "Aymara"; } - if (code == "az") { return "Azerbaijani"; } - if (code == "ba") { return "Bashkir"; } - if (code == "be") { return "Belarusian"; } - if (code == "bg") { return "Bulgarian"; } - if (code == "bh") { return "Bihari languages"; } - if (code == "bi") { return "Bislama"; } - if (code == "bm") { return "Bambara"; } - if (code == "bn") { return "Bengali"; } - if (code == "bo") { return "Tibetan"; } - if (code == "br") { return "Breton"; } - if (code == "bs") { return "Bosnian"; } - if (code == "ca") { return "Catalan"; } - if (code == "ce") { return "Chechen"; } - if (code == "ch") { return "Chamorro"; } - if (code == "co") { return "Corsican"; } - if (code == "cr") { return "Cree"; } - if (code == "cs") { return "Czech"; } - if (code == "cs") { return "Czech"; } - if (code == "cu") { return "Church Slavic"; } - if (code == "cv") { return "Chuvash"; } - if (code == "cy") { return "Welsh"; } - if (code == "cy") { return "Welsh"; } - if (code == "da") { return "Danish"; } - if (code == "de") { return "German"; } - if (code == "dv") { return "Divehi"; } - if (code == "dz") { return "Dzongkha"; } - if (code == "ee") { return "Ewe"; } - if (code == "el") { return "Greek, Modern (1453-)"; } - if (code == "en") { return "English"; } - if (code == "eo") { return "Esperanto"; } - if (code == "es") { return "Spanish"; } - if (code == "et") { return "Estonian"; } - if (code == "eu") { return "Basque"; } - if (code == "eu") { return "Basque"; } - if (code == "fa") { return "Persian"; } - if (code == "ff") { return "Fulah"; } - if (code == "fi") { return "Finnish"; } - if (code == "fj") { return "Fijian"; } - if (code == "fo") { return "Faroese"; } - if (code == "fr") { return "French"; } - if (code == "fy") { return "Western Frisian"; } - if (code == "ga") { return "Irish"; } - if (code == "gd") { return "Gaelic"; } - if (code == "gl") { return "Galician"; } - if (code == "gn") { return "Guarani"; } - if (code == "gu") { return "Gujarati"; } - if (code == "gv") { return "Manx"; } - if (code == "ha") { return "Hausa"; } - if (code == "he") { return "Hebrew"; } - if (code == "hi") { return "Hindi"; } - if (code == "ho") { return "Hiri Motu"; } - if (code == "hr") { return "Croatian"; } - if (code == "ht") { return "Haitian"; } - if (code == "hu") { return "Hungarian"; } - if (code == "hy") { return "Armenian"; } - if (code == "hz") { return "Herero"; } - if (code == "ia") { return "Interlingua"; } - if (code == "id") { return "Indonesian"; } - if (code == "ie") { return "Interlingue"; } - if (code == "ig") { return "Igbo"; } - if (code == "ii") { return "Sichuan Yi"; } - if (code == "ik") { return "Inupiaq"; } - if (code == "io") { return "Ido"; } - if (code == "is") { return "Icelandic"; } - if (code == "it") { return "Italian"; } - if (code == "iu") { return "Inuktitut"; } - if (code == "ja") { return "Japanese"; } - if (code == "jv") { return "Javanese"; } - if (code == "ka") { return "Georgian"; } - if (code == "kg") { return "Kongo"; } - if (code == "ki") { return "Kikuyu"; } - if (code == "kj") { return "Kuanyama"; } - if (code == "kk") { return "Kazakh"; } - if (code == "kl") { return "Greenlandic"; } - if (code == "km") { return "Central Khmer"; } - if (code == "kn") { return "Kannada"; } - if (code == "ko") { return "Korean"; } - if (code == "kr") { return "Kanuri"; } - if (code == "ks") { return "Kashmiri"; } - if (code == "ku") { return "Kurdish"; } - if (code == "kv") { return "Komi"; } - if (code == "kw") { return "Cornish"; } - if (code == "ky") { return "Kirghiz"; } - if (code == "la") { return "Latin"; } - if (code == "lb") { return "Luxembourgish"; } - if (code == "lg") { return "Ganda"; } - if (code == "li") { return "Limburgan"; } - if (code == "ln") { return "Lingala"; } - if (code == "lo") { return "Lao"; } - if (code == "lt") { return "Lithuanian"; } - if (code == "lu") { return "Luba-Katanga"; } - if (code == "lv") { return "Latvian"; } - if (code == "mg") { return "Malagasy"; } - if (code == "mh") { return "Marshallese"; } - if (code == "mi") { return "Maori"; } - if (code == "mk") { return "Macedonian"; } - if (code == "mk") { return "Macedonian"; } - if (code == "ml") { return "Malayalam"; } - if (code == "mn") { return "Mongolian"; } - if (code == "mr") { return "Marathi"; } - if (code == "ms") { return "Malay"; } - if (code == "mt") { return "Maltese"; } - if (code == "my") { return "Burmese"; } - if (code == "my") { return "Burmese"; } - if (code == "na") { return "Nauru"; } - if (code == "nb") { return "Bokmål, Norwegian"; } - if (code == "nd") { return "Ndebele, North"; } - if (code == "ne") { return "Nepali"; } - if (code == "ng") { return "Ndonga"; } - if (code == "nl") { return "Dutch"; } - if (code == "nl") { return "Dutch"; } - if (code == "nn") { return "Norwegian Nynorsk"; } - if (code == "no") { return "Norwegian"; } - if (code == "nr") { return "Ndebele, South"; } - if (code == "nv") { return "Navajo"; } - if (code == "ny") { return "Chichewa"; } - if (code == "oc") { return "Occitan (post 1500)"; } - if (code == "oj") { return "Ojibwa"; } - if (code == "om") { return "Oromo"; } - if (code == "or") { return "Oriya"; } - if (code == "os") { return "Ossetian"; } - if (code == "pa") { return "Panjabi"; } - if (code == "pi") { return "Pali"; } - if (code == "pl") { return "Polish"; } - if (code == "ps") { return "Pushto"; } - if (code == "pt") { return "Portuguese"; } - if (code == "qu") { return "Quechua"; } - if (code == "rm") { return "Romansh"; } - if (code == "rn") { return "Rundi"; } - if (code == "ro") { return "Romanian"; } - if (code == "ru") { return "Russian"; } - if (code == "rw") { return "Kinyarwanda"; } - if (code == "sa") { return "Sanskrit"; } - if (code == "sc") { return "Sardinian"; } - if (code == "sd") { return "Sindhi"; } - if (code == "se") { return "Northern Sami"; } - if (code == "sg") { return "Sango"; } - if (code == "si") { return "Sinhala"; } - if (code == "sl") { return "Slovenian"; } - if (code == "sm") { return "Samoan"; } - if (code == "sn") { return "Shona"; } - if (code == "so") { return "Somali"; } - if (code == "sq") { return "Albanian"; } - if (code == "sr") { return "Serbian"; } - if (code == "ss") { return "Swati"; } - if (code == "st") { return "Sotho, Southern"; } - if (code == "su") { return "Sundanese"; } - if (code == "sv") { return "Swedish"; } - if (code == "sw") { return "Swahili"; } - if (code == "ta") { return "Tamil"; } - if (code == "te") { return "Telugu"; } - if (code == "tg") { return "Tajik"; } - if (code == "th") { return "Thai"; } - if (code == "ti") { return "Tigrinya"; } - if (code == "tk") { return "Turkmen"; } - if (code == "tl") { return "Tagalog"; } - if (code == "tn") { return "Tswana"; } - if (code == "to") { return "Tonga (Tonga Islands)"; } - if (code == "tr") { return "Turkish"; } - if (code == "ts") { return "Tsonga"; } - if (code == "tt") { return "Tatar"; } - if (code == "tw") { return "Twi"; } - if (code == "ty") { return "Tahitian"; } - if (code == "ug") { return "Uighur"; } - if (code == "uk") { return "Ukrainian"; } - if (code == "ur") { return "Urdu"; } - if (code == "uz") { return "Uzbek"; } - if (code == "ve") { return "Venda"; } - if (code == "vi") { return "Vietnamese"; } - if (code == "vo") { return "Volapük"; } - if (code == "wa") { return "Walloon"; } - if (code == "wo") { return "Wolof"; } - if (code == "xh") { return "Xhosa"; } - if (code == "yi") { return "Yiddish"; } - if (code == "yo") { return "Yoruba"; } - if (code == "za") { return "Zhuang"; } - if (code == "zh") { return "Chinese"; } - if (code == "zu") { return "Zulu"; } + if (code == "AA") { return "Afar"; } + if (code == "AB") { return "Abkhazian"; } + if (code == "AE") { return "Avestan"; } + if (code == "AF") { return "Afrikaans"; } + if (code == "AK") { return "Akan"; } + if (code == "AM") { return "Amharic"; } + if (code == "AN") { return "Aragonese"; } + if (code == "AR") { return "Arabic"; } + if (code == "AS") { return "Assamese"; } + if (code == "AV") { return "Avaric"; } + if (code == "AY") { return "Aymara"; } + if (code == "AZ") { return "Azerbaijani"; } + if (code == "BA") { return "Bashkir"; } + if (code == "BE") { return "Belarusian"; } + if (code == "BG") { return "Bulgarian"; } + if (code == "BH") { return "Bihari languages"; } + if (code == "BI") { return "Bislama"; } + if (code == "BM") { return "Bambara"; } + if (code == "BN") { return "Bengali"; } + if (code == "BO") { return "Tibetan"; } + if (code == "BR") { return "Breton"; } + if (code == "BS") { return "Bosnian"; } + if (code == "CA") { return "Catalan"; } + if (code == "CE") { return "Chechen"; } + if (code == "CH") { return "Chamorro"; } + if (code == "CO") { return "Corsican"; } + if (code == "CR") { return "Cree"; } + if (code == "CS") { return "Czech"; } + if (code == "CS") { return "Czech"; } + if (code == "CU") { return "Church Slavic"; } + if (code == "CV") { return "Chuvash"; } + if (code == "CY") { return "Welsh"; } + if (code == "CY") { return "Welsh"; } + if (code == "DA") { return "Danish"; } + if (code == "DE") { return "German"; } + if (code == "DV") { return "Divehi"; } + if (code == "DZ") { return "Dzongkha"; } + if (code == "EE") { return "Ewe"; } + if (code == "EL") { return "Greek, Modern (1453-)"; } + if (code == "EN") { return "English"; } + if (code == "EO") { return "Esperanto"; } + if (code == "ES") { return "Spanish"; } + if (code == "ET") { return "Estonian"; } + if (code == "EU") { return "Basque"; } + if (code == "EU") { return "Basque"; } + if (code == "FA") { return "Persian"; } + if (code == "FF") { return "Fulah"; } + if (code == "FI") { return "Finnish"; } + if (code == "FJ") { return "Fijian"; } + if (code == "FO") { return "Faroese"; } + if (code == "FR") { return "French"; } + if (code == "FY") { return "Western Frisian"; } + if (code == "GA") { return "Irish"; } + if (code == "GD") { return "Gaelic"; } + if (code == "GL") { return "Galician"; } + if (code == "GN") { return "Guarani"; } + if (code == "GU") { return "Gujarati"; } + if (code == "GV") { return "Manx"; } + if (code == "HA") { return "Hausa"; } + if (code == "HE") { return "Hebrew"; } + if (code == "HI") { return "Hindi"; } + if (code == "HO") { return "Hiri Motu"; } + if (code == "HR") { return "Croatian"; } + if (code == "HT") { return "Haitian"; } + if (code == "HU") { return "Hungarian"; } + if (code == "HY") { return "Armenian"; } + if (code == "HZ") { return "Herero"; } + if (code == "IA") { return "Interlingua"; } + if (code == "ID") { return "Indonesian"; } + if (code == "IE") { return "Interlingue"; } + if (code == "IG") { return "Igbo"; } + if (code == "II") { return "Sichuan Yi"; } + if (code == "IK") { return "Inupiaq"; } + if (code == "IO") { return "Ido"; } + if (code == "IS") { return "Icelandic"; } + if (code == "IT") { return "Italian"; } + if (code == "IU") { return "Inuktitut"; } + if (code == "JA") { return "Japanese"; } + if (code == "JV") { return "Javanese"; } + if (code == "KA") { return "Georgian"; } + if (code == "KG") { return "Kongo"; } + if (code == "KI") { return "Kikuyu"; } + if (code == "KJ") { return "Kuanyama"; } + if (code == "KK") { return "Kazakh"; } + if (code == "KL") { return "Greenlandic"; } + if (code == "KM") { return "Central Khmer"; } + if (code == "KN") { return "Kannada"; } + if (code == "KO") { return "Korean"; } + if (code == "KR") { return "Kanuri"; } + if (code == "KS") { return "Kashmiri"; } + if (code == "KU") { return "Kurdish"; } + if (code == "KV") { return "Komi"; } + if (code == "KW") { return "Cornish"; } + if (code == "KY") { return "Kirghiz"; } + if (code == "LA") { return "Latin"; } + if (code == "LB") { return "Luxembourgish"; } + if (code == "LG") { return "Ganda"; } + if (code == "LI") { return "Limburgan"; } + if (code == "LN") { return "Lingala"; } + if (code == "LO") { return "Lao"; } + if (code == "LT") { return "Lithuanian"; } + if (code == "LU") { return "Luba-Katanga"; } + if (code == "LV") { return "Latvian"; } + if (code == "MG") { return "Malagasy"; } + if (code == "MH") { return "Marshallese"; } + if (code == "MI") { return "Maori"; } + if (code == "MK") { return "Macedonian"; } + if (code == "MK") { return "Macedonian"; } + if (code == "ML") { return "Malayalam"; } + if (code == "MN") { return "Mongolian"; } + if (code == "MR") { return "Marathi"; } + if (code == "MS") { return "Malay"; } + if (code == "MT") { return "Maltese"; } + if (code == "MY") { return "Burmese"; } + if (code == "MY") { return "Burmese"; } + if (code == "NA") { return "Nauru"; } + if (code == "NB") { return "Bokmål, Norwegian"; } + if (code == "ND") { return "Ndebele, North"; } + if (code == "NE") { return "Nepali"; } + if (code == "NG") { return "Ndonga"; } + if (code == "NL") { return "Dutch"; } + if (code == "NL") { return "Dutch"; } + if (code == "NN") { return "Norwegian Nynorsk"; } + if (code == "NO") { return "Norwegian"; } + if (code == "NR") { return "Ndebele, South"; } + if (code == "NV") { return "Navajo"; } + if (code == "NY") { return "Chichewa"; } + if (code == "OC") { return "Occitan (post 1500)"; } + if (code == "OJ") { return "Ojibwa"; } + if (code == "OM") { return "Oromo"; } + if (code == "OR") { return "Oriya"; } + if (code == "OS") { return "Ossetian"; } + if (code == "PA") { return "Panjabi"; } + if (code == "PI") { return "Pali"; } + if (code == "PL") { return "Polish"; } + if (code == "PS") { return "Pushto"; } + if (code == "PT") { return "Portuguese"; } + if (code == "QU") { return "Quechua"; } + if (code == "RM") { return "Romansh"; } + if (code == "RN") { return "Rundi"; } + if (code == "RO") { return "Romanian"; } + if (code == "RU") { return "Russian"; } + if (code == "RW") { return "Kinyarwanda"; } + if (code == "SA") { return "Sanskrit"; } + if (code == "SC") { return "Sardinian"; } + if (code == "SD") { return "Sindhi"; } + if (code == "SE") { return "Northern Sami"; } + if (code == "SG") { return "Sango"; } + if (code == "SI") { return "Sinhala"; } + if (code == "SL") { return "Slovenian"; } + if (code == "SM") { return "Samoan"; } + if (code == "SN") { return "Shona"; } + if (code == "SO") { return "Somali"; } + if (code == "SQ") { return "Albanian"; } + if (code == "SR") { return "Serbian"; } + if (code == "SS") { return "Swati"; } + if (code == "ST") { return "Sotho, Southern"; } + if (code == "SU") { return "Sundanese"; } + if (code == "SV") { return "Swedish"; } + if (code == "SW") { return "Swahili"; } + if (code == "TA") { return "Tamil"; } + if (code == "TE") { return "Telugu"; } + if (code == "TG") { return "Tajik"; } + if (code == "TH") { return "Thai"; } + if (code == "TI") { return "Tigrinya"; } + if (code == "TK") { return "Turkmen"; } + if (code == "TL") { return "Tagalog"; } + if (code == "TN") { return "Tswana"; } + if (code == "TO") { return "Tonga (Tonga Islands)"; } + if (code == "TR") { return "Turkish"; } + if (code == "TS") { return "Tsonga"; } + if (code == "TT") { return "Tatar"; } + if (code == "TW") { return "Twi"; } + if (code == "TY") { return "Tahitian"; } + if (code == "UG") { return "Uighur"; } + if (code == "UK") { return "Ukrainian"; } + if (code == "UR") { return "Urdu"; } + if (code == "UZ") { return "Uzbek"; } + if (code == "VE") { return "Venda"; } + if (code == "VI") { return "Vietnamese"; } + if (code == "VO") { return "Volapük"; } + if (code == "WA") { return "Walloon"; } + if (code == "WO") { return "Wolof"; } + if (code == "XH") { return "Xhosa"; } + if (code == "YI") { return "Yiddish"; } + if (code == "YO") { return "Yoruba"; } + if (code == "ZA") { return "Zhuang"; } + if (code == "ZH") { return "Chinese"; } + if (code == "ZU") { return "Zulu"; } } else if (code.size() == 3) { // ISO 639-2 language codes - if (code == "aar") { return "Afar"; } - if (code == "abk") { return "Abkhazian"; } - if (code == "ace") { return "Achinese"; } - if (code == "ach") { return "Acoli"; } - if (code == "ada") { return "Adangme"; } - if (code == "ady") { return "Adyghe"; } - if (code == "afa") { return "Afro-Asiatic languages"; } - if (code == "afh") { return "Afrihili"; } - if (code == "afr") { return "Afrikaans"; } - if (code == "ain") { return "Ainu"; } - if (code == "aka") { return "Akan"; } - if (code == "akk") { return "Akkadian"; } - if (code == "alb") { return "Albanian"; } - if (code == "ale") { return "Aleut"; } - if (code == "alg") { return "Algonquian languages"; } - if (code == "alt") { return "Southern Altai"; } - if (code == "amh") { return "Amharic"; } - if (code == "ang") { return "English, Old (ca.450-1100)"; } - if (code == "anp") { return "Angika"; } - if (code == "apa") { return "Apache languages"; } - if (code == "ara") { return "Arabic"; } - if (code == "arc") { return "Aramaic (700-300 BCE)"; } - if (code == "arg") { return "Aragonese"; } - if (code == "arm") { return "Armenian"; } - if (code == "arn") { return "Mapudungun"; } - if (code == "arp") { return "Arapaho"; } - if (code == "art") { return "Artificial languages"; } - if (code == "arw") { return "Arawak"; } - if (code == "asm") { return "Assamese"; } - if (code == "ast") { return "Asturian"; } - if (code == "ath") { return "Athapascan languages"; } - if (code == "aus") { return "Australian languages"; } - if (code == "ava") { return "Avaric"; } - if (code == "ave") { return "Avestan"; } - if (code == "awa") { return "Awadhi"; } - if (code == "aym") { return "Aymara"; } - if (code == "aze") { return "Azerbaijani"; } - if (code == "bad") { return "Banda languages"; } - if (code == "bai") { return "Bamileke languages"; } - if (code == "bak") { return "Bashkir"; } - if (code == "bal") { return "Baluchi"; } - if (code == "bam") { return "Bambara"; } - if (code == "ban") { return "Balinese"; } - if (code == "baq") { return "Basque"; } - if (code == "baq") { return "Basque"; } - if (code == "bas") { return "Basa"; } - if (code == "bat") { return "Baltic languages"; } - if (code == "bej") { return "Beja"; } - if (code == "bel") { return "Belarusian"; } - if (code == "bem") { return "Bemba"; } - if (code == "ben") { return "Bengali"; } - if (code == "ber") { return "Berber languages"; } - if (code == "bho") { return "Bhojpuri"; } - if (code == "bih") { return "Bihari languages"; } - if (code == "bik") { return "Bikol"; } - if (code == "bin") { return "Bini"; } - if (code == "bis") { return "Bislama"; } - if (code == "bla") { return "Siksika"; } - if (code == "bnt") { return "Bantu languages"; } - if (code == "bod") { return "Tibetan"; } - if (code == "bos") { return "Bosnian"; } - if (code == "bra") { return "Braj"; } - if (code == "bre") { return "Breton"; } - if (code == "btk") { return "Batak languages"; } - if (code == "bua") { return "Buriat"; } - if (code == "bug") { return "Buginese"; } - if (code == "bul") { return "Bulgarian"; } - if (code == "bur") { return "Burmese"; } - if (code == "bur") { return "Burmese"; } - if (code == "byn") { return "Blin"; } - if (code == "cad") { return "Caddo"; } - if (code == "cai") { return "Central American Indian languages"; } - if (code == "car") { return "Galibi Carib"; } - if (code == "cat") { return "Catalan"; } - if (code == "cau") { return "Caucasian languages"; } - if (code == "ceb") { return "Cebuano"; } - if (code == "cel") { return "Celtic languages"; } - if (code == "ces") { return "Czech"; } - if (code == "ces") { return "Czech"; } - if (code == "cha") { return "Chamorro"; } - if (code == "chb") { return "Chibcha"; } - if (code == "che") { return "Chechen"; } - if (code == "chg") { return "Chagatai"; } - if (code == "chi") { return "Chinese"; } - if (code == "chk") { return "Chuukese"; } - if (code == "chm") { return "Mari"; } - if (code == "chn") { return "Chinook jargon"; } - if (code == "cho") { return "Choctaw"; } - if (code == "chp") { return "Chipewyan"; } - if (code == "chr") { return "Cherokee"; } - if (code == "chu") { return "Church Slavic"; } - if (code == "chv") { return "Chuvash"; } - if (code == "chy") { return "Cheyenne"; } - if (code == "cmc") { return "Chamic languages"; } - if (code == "cnr") { return "Montenegrin"; } - if (code == "cop") { return "Coptic"; } - if (code == "cor") { return "Cornish"; } - if (code == "cos") { return "Corsican"; } - if (code == "cpe") { return "Creoles and pidgins, English based"; } - if (code == "cpf") { return "Creoles and pidgins, French-based"; } - if (code == "cpp") { return "Creoles and pidgins, Portuguese-based"; } - if (code == "cre") { return "Cree"; } - if (code == "crh") { return "Crimean Tatar"; } - if (code == "crp") { return "Creoles and pidgins"; } - if (code == "csb") { return "Kashubian"; } - if (code == "cus") { return "Cushitic languages"; } - if (code == "cym") { return "Welsh"; } - if (code == "cym") { return "Welsh"; } - if (code == "cze") { return "Czech"; } - if (code == "cze") { return "Czech"; } - if (code == "dak") { return "Dakota"; } - if (code == "dan") { return "Danish"; } - if (code == "dar") { return "Dargwa"; } - if (code == "day") { return "Land Dayak languages"; } - if (code == "del") { return "Delaware"; } - if (code == "den") { return "Slave (Athapascan)"; } - if (code == "deu") { return "German"; } - if (code == "dgr") { return "Dogrib"; } - if (code == "din") { return "Dinka"; } - if (code == "div") { return "Divehi"; } - if (code == "doi") { return "Dogri"; } - if (code == "dra") { return "Dravidian languages"; } - if (code == "dsb") { return "Lower Sorbian"; } - if (code == "dua") { return "Duala"; } - if (code == "dum") { return "Dutch, Middle (ca.1050-1350)"; } - if (code == "dut") { return "Dutch"; } - if (code == "dut") { return "Dutch"; } - if (code == "dyu") { return "Dyula"; } - if (code == "dzo") { return "Dzongkha"; } - if (code == "efi") { return "Efik"; } - if (code == "egy") { return "Egyptian (Ancient)"; } - if (code == "eka") { return "Ekajuk"; } - if (code == "ell") { return "Greek, Modern (1453-)"; } - if (code == "elx") { return "Elamite"; } - if (code == "eng") { return "English"; } - if (code == "enm") { return "English, Middle (1100-1500)"; } - if (code == "epo") { return "Esperanto"; } - if (code == "est") { return "Estonian"; } - if (code == "eus") { return "Basque"; } - if (code == "eus") { return "Basque"; } - if (code == "ewe") { return "Ewe"; } - if (code == "ewo") { return "Ewondo"; } - if (code == "fan") { return "Fang"; } - if (code == "fao") { return "Faroese"; } - if (code == "fas") { return "Persian"; } - if (code == "fat") { return "Fanti"; } - if (code == "fij") { return "Fijian"; } - if (code == "fil") { return "Filipino"; } - if (code == "fin") { return "Finnish"; } - if (code == "fiu") { return "Finno-Ugrian languages"; } - if (code == "fon") { return "Fon"; } - if (code == "fra") { return "French"; } - if (code == "fre") { return "French"; } - if (code == "frm") { return "French, Middle (ca.1400-1600)"; } - if (code == "fro") { return "French, Old (842-ca.1400)"; } - if (code == "frr") { return "Northern Frisian"; } - if (code == "frs") { return "Eastern Frisian"; } - if (code == "fry") { return "Western Frisian"; } - if (code == "ful") { return "Fulah"; } - if (code == "fur") { return "Friulian"; } - if (code == "gaa") { return "Ga"; } - if (code == "gay") { return "Gayo"; } - if (code == "gba") { return "Gbaya"; } - if (code == "gem") { return "Germanic languages"; } - if (code == "geo") { return "Georgin"; } - if (code == "ger") { return "German"; } - if (code == "gez") { return "Geez"; } - if (code == "gil") { return "Gilbertese"; } - if (code == "gla") { return "Gaelic"; } - if (code == "gle") { return "Irish"; } - if (code == "glg") { return "Galician"; } - if (code == "glv") { return "Manx"; } - if (code == "gmh") { return "German, Middle High (ca.1050-1500)"; } - if (code == "goh") { return "German, Old High (ca.750-1050)"; } - if (code == "gon") { return "Gondi"; } - if (code == "gor") { return "Gorontalo"; } - if (code == "got") { return "Gothic"; } - if (code == "grb") { return "Grebo"; } - if (code == "grc") { return "Greek, Ancient (to 1453)"; } - if (code == "gre") { return "Greek"; } - if (code == "grn") { return "Guarani"; } - if (code == "gsw") { return "Swiss German"; } - if (code == "guj") { return "Gujarati"; } - if (code == "gwi") { return "Gwich'in"; } - if (code == "hai") { return "Haida"; } - if (code == "hat") { return "Haitian"; } - if (code == "hau") { return "Hausa"; } - if (code == "haw") { return "Hawaiian"; } - if (code == "heb") { return "Hebrew"; } - if (code == "her") { return "Herero"; } - if (code == "hil") { return "Hiligaynon"; } - if (code == "him") { return "Himachali languages"; } - if (code == "hin") { return "Hindi"; } - if (code == "hit") { return "Hittite"; } - if (code == "hmn") { return "Hmong"; } - if (code == "hmo") { return "Hiri Motu"; } - if (code == "hrv") { return "Croatian"; } - if (code == "hsb") { return "Upper Sorbian"; } - if (code == "hun") { return "Hungarian"; } - if (code == "hup") { return "Hupa"; } - if (code == "hye") { return "Armenian"; } - if (code == "iba") { return "Iban"; } - if (code == "ibo") { return "Igbo"; } - if (code == "ice") { return "Icelandic"; } - if (code == "ido") { return "Ido"; } - if (code == "iii") { return "Sichuan Yi"; } - if (code == "ijo") { return "Ijo languages"; } - if (code == "iku") { return "Inuktitut"; } - if (code == "ile") { return "Interlingue"; } - if (code == "ilo") { return "Iloko"; } - if (code == "ina") { return "Interlingua)"; } - if (code == "inc") { return "Indic languages"; } - if (code == "ind") { return "Indonesian"; } - if (code == "ine") { return "Indo-European languages"; } - if (code == "inh") { return "Ingush"; } - if (code == "ipk") { return "Inupiaq"; } - if (code == "ira") { return "Iranian languages"; } - if (code == "iro") { return "Iroquoian languages"; } - if (code == "isl") { return "Icelandic"; } - if (code == "ita") { return "Italian"; } - if (code == "jav") { return "Javanese"; } - if (code == "jbo") { return "Lojban"; } - if (code == "jpn") { return "Japanese"; } - if (code == "jpr") { return "Judeo-Persian"; } - if (code == "jrb") { return "Judeo-Arabic"; } - if (code == "kaa") { return "Kara-Kalpak"; } - if (code == "kab") { return "Kabyle"; } - if (code == "kac") { return "Kachin"; } - if (code == "kal") { return "Greenlandic"; } - if (code == "kam") { return "Kamba"; } - if (code == "kan") { return "Kannada"; } - if (code == "kar") { return "Karen languages"; } - if (code == "kas") { return "Kashmiri"; } - if (code == "kat") { return "Georgian"; } - if (code == "kau") { return "Kanuri"; } - if (code == "kaw") { return "Kawi"; } - if (code == "kaz") { return "Kazakh"; } - if (code == "kbd") { return "Kabardian"; } - if (code == "kha") { return "Khasi"; } - if (code == "khi") { return "Khoisan languages"; } - if (code == "khm") { return "Central Khmer"; } - if (code == "kho") { return "Khotanese"; } - if (code == "kik") { return "Kikuyu"; } - if (code == "kin") { return "Kinyarwanda"; } - if (code == "kir") { return "Kirghiz"; } - if (code == "kmb") { return "Kimbundu"; } - if (code == "kok") { return "Konkani"; } - if (code == "kom") { return "Komi"; } - if (code == "kon") { return "Kongo"; } - if (code == "kor") { return "Korean"; } - if (code == "kos") { return "Kosraean"; } - if (code == "kpe") { return "Kpelle"; } - if (code == "krc") { return "Karachay-Balkar"; } - if (code == "krl") { return "Karelian"; } - if (code == "kro") { return "Kru languages"; } - if (code == "kru") { return "Kurukh"; } - if (code == "kua") { return "Kuanyama"; } - if (code == "kum") { return "Kumyk"; } - if (code == "kur") { return "Kurdish"; } - if (code == "kut") { return "Kutenai"; } - if (code == "lad") { return "Ladino"; } - if (code == "lah") { return "Lahnda"; } - if (code == "lam") { return "Lamba"; } - if (code == "lao") { return "Lao"; } - if (code == "lat") { return "Latin"; } - if (code == "lav") { return "Latvian"; } - if (code == "lez") { return "Lezghian"; } - if (code == "lim") { return "Limburgan"; } - if (code == "lin") { return "Lingala"; } - if (code == "lit") { return "Lithuanian"; } - if (code == "lol") { return "Mongo"; } - if (code == "loz") { return "Lozi"; } - if (code == "ltz") { return "Luxembourgish"; } - if (code == "lua") { return "Luba-Lulua"; } - if (code == "lub") { return "Luba-Katanga"; } - if (code == "lug") { return "Ganda"; } - if (code == "lui") { return "Luiseno"; } - if (code == "lun") { return "Lunda"; } - if (code == "luo") { return "Luo (Kenya and Tanzania)"; } - if (code == "lus") { return "Lushai"; } - if (code == "mac") { return "Macedonian"; } - if (code == "mac") { return "Macedonian"; } - if (code == "mad") { return "Madurese"; } - if (code == "mag") { return "Magahi"; } - if (code == "mah") { return "Marshallese"; } - if (code == "mai") { return "Maithili"; } - if (code == "mak") { return "Makasar"; } - if (code == "mal") { return "Malayalam"; } - if (code == "man") { return "Mandingo"; } - if (code == "mao") { return "Maori"; } - if (code == "map") { return "Austronesian languages"; } - if (code == "mar") { return "Marathi"; } - if (code == "mas") { return "Masai"; } - if (code == "may") { return "Malay"; } - if (code == "mdf") { return "Moksha"; } - if (code == "mdr") { return "Mandar"; } - if (code == "men") { return "Mende"; } - if (code == "mga") { return "Irish, Middle (900-1200)"; } - if (code == "mic") { return "Mi'kmaq"; } - if (code == "min") { return "Minangkabau"; } - if (code == "mis") { return "Uncoded languages"; } - if (code == "mkd") { return "Macedonian"; } - if (code == "mkd") { return "Macedonian"; } - if (code == "mkh") { return "Mon-Khmer languages"; } - if (code == "mlg") { return "Malagasy"; } - if (code == "mlt") { return "Maltese"; } - if (code == "mnc") { return "Manchu"; } - if (code == "mni") { return "Manipuri"; } - if (code == "mno") { return "Manobo languages"; } - if (code == "moh") { return "Mohawk"; } - if (code == "mon") { return "Mongolian"; } - if (code == "mos") { return "Mossi"; } - if (code == "mri") { return "Maori"; } - if (code == "msa") { return "Malay"; } - if (code == "mul") { return "Multiple languages"; } - if (code == "mun") { return "Munda languages"; } - if (code == "mus") { return "Creek"; } - if (code == "mwl") { return "Mirandese"; } - if (code == "mwr") { return "Marwari"; } - if (code == "mya") { return "Burmese"; } - if (code == "mya") { return "Burmese"; } - if (code == "myn") { return "Mayan languages"; } - if (code == "myv") { return "Erzya"; } - if (code == "nah") { return "Nahuatl languages"; } - if (code == "nai") { return "North American Indian languages"; } - if (code == "nap") { return "Neapolitan"; } - if (code == "nau") { return "Nauru"; } - if (code == "nav") { return "Navajo"; } - if (code == "nbl") { return "Ndebele, South"; } - if (code == "nde") { return "Ndebele, North"; } - if (code == "ndo") { return "Ndonga"; } - if (code == "nds") { return "Low German"; } - if (code == "nep") { return "Nepali"; } - if (code == "new") { return "Nepal Bhasa"; } - if (code == "nia") { return "Nias"; } - if (code == "nic") { return "Niger-Kordofanian languages"; } - if (code == "niu") { return "Niuean"; } - if (code == "nld") { return "Dutch"; } - if (code == "nld") { return "Dutch"; } - if (code == "nno") { return "Norwegian Nynorsk"; } - if (code == "nob") { return "Bokmål, Norwegian"; } - if (code == "nog") { return "Nogai"; } - if (code == "non") { return "Norse, Old"; } - if (code == "nor") { return "Norwegian"; } - if (code == "nqo") { return "N'Ko"; } - if (code == "nso") { return "Pedi"; } - if (code == "nub") { return "Nubian languages"; } - if (code == "nwc") { return "Classical Newari"; } - if (code == "nya") { return "Chichewa"; } - if (code == "nym") { return "Nyamwezi"; } - if (code == "nyn") { return "Nyankole"; } - if (code == "nyo") { return "Nyoro"; } - if (code == "nzi") { return "Nzima"; } - if (code == "oci") { return "Occitan (post 1500)"; } - if (code == "oji") { return "Ojibwa"; } - if (code == "ori") { return "Oriya"; } - if (code == "orm") { return "Oromo"; } - if (code == "osa") { return "Osage"; } - if (code == "oss") { return "Ossetian"; } - if (code == "ota") { return "Turkish, Ottoman (1500-1928)"; } - if (code == "oto") { return "Otomian languages"; } - if (code == "paa") { return "Papuan languages"; } - if (code == "pag") { return "Pangasinan"; } - if (code == "pal") { return "Pahlavi"; } - if (code == "pam") { return "Pampanga"; } - if (code == "pan") { return "Panjabi"; } - if (code == "pap") { return "Papiamento"; } - if (code == "pau") { return "Palauan"; } - if (code == "peo") { return "Persian, Old (ca.600-400 B.C.)"; } - if (code == "per") { return "Persian"; } - if (code == "phi") { return "Philippine languages"; } - if (code == "phn") { return "Phoenician"; } - if (code == "pli") { return "Pali"; } - if (code == "pol") { return "Polish"; } - if (code == "pon") { return "Pohnpeian"; } - if (code == "por") { return "Portuguese"; } - if (code == "pra") { return "Prakrit languages"; } - if (code == "pro") { return "Provençal, Old (to 1500)"; } - if (code == "pus") { return "Pushto"; } - if (code == "que") { return "Quechua"; } - if (code == "raj") { return "Rajasthani"; } - if (code == "rap") { return "Rapanui"; } - if (code == "rar") { return "Rarotongan"; } - if (code == "roa") { return "Romance languages"; } - if (code == "roh") { return "Romansh"; } - if (code == "rom") { return "Romany"; } - if (code == "ron") { return "Romanian"; } - if (code == "rum") { return "Romanian"; } - if (code == "run") { return "Rundi"; } - if (code == "rup") { return "Aromanian"; } - if (code == "rus") { return "Russian"; } - if (code == "sad") { return "Sandawe"; } - if (code == "sag") { return "Sango"; } - if (code == "sah") { return "Yakut"; } - if (code == "sai") { return "South American Indian languages"; } - if (code == "sal") { return "Salishan languages"; } - if (code == "sam") { return "Samaritan Aramaic"; } - if (code == "san") { return "Sanskrit"; } - if (code == "sas") { return "Sasak"; } - if (code == "sat") { return "Santali"; } - if (code == "scn") { return "Sicilian"; } - if (code == "sco") { return "Scots"; } - if (code == "sel") { return "Selkup"; } - if (code == "sem") { return "Semitic languages"; } - if (code == "sga") { return "Irish, Old (to 900)"; } - if (code == "sgn") { return "Sign Languages"; } - if (code == "shn") { return "Shan"; } - if (code == "sid") { return "Sidamo"; } - if (code == "sin") { return "Sinhala"; } - if (code == "sio") { return "Siouan languages"; } - if (code == "sit") { return "Sino-Tibetan languages"; } - if (code == "sla") { return "Slavic languages"; } - if (code == "slo") { return "Slovak"; } - if (code == "slv") { return "Slovenian"; } - if (code == "sma") { return "Southern Sami"; } - if (code == "sme") { return "Northern Sami"; } - if (code == "smi") { return "Sami languages"; } - if (code == "smj") { return "Lule Sami"; } - if (code == "smn") { return "Inari Sami"; } - if (code == "smo") { return "Samoan"; } - if (code == "sms") { return "Skolt Sami"; } - if (code == "sna") { return "Shona"; } - if (code == "snd") { return "Sindhi"; } - if (code == "snk") { return "Soninke"; } - if (code == "sog") { return "Sogdian"; } - if (code == "som") { return "Somali"; } - if (code == "son") { return "Songhai languages"; } - if (code == "sot") { return "Sotho, Southern"; } - if (code == "spa") { return "Spanish"; } - if (code == "sqi") { return "Albanian"; } - if (code == "srd") { return "Sardinian"; } - if (code == "srn") { return "Sranan Tongo"; } - if (code == "srp") { return "Serbian"; } - if (code == "srr") { return "Serer"; } - if (code == "ssa") { return "Nilo-Saharan languages"; } - if (code == "ssw") { return "Swati"; } - if (code == "suk") { return "Sukuma"; } - if (code == "sun") { return "Sundanese"; } - if (code == "sus") { return "Susu"; } - if (code == "sux") { return "Sumerian"; } - if (code == "swa") { return "Swahili"; } - if (code == "swe") { return "Swedish"; } - if (code == "syc") { return "Classical Syriac"; } - if (code == "syr") { return "Syriac"; } - if (code == "tah") { return "Tahitian"; } - if (code == "tai") { return "Tai languages"; } - if (code == "tam") { return "Tamil"; } - if (code == "tat") { return "Tatar"; } - if (code == "tel") { return "Telugu"; } - if (code == "tem") { return "Timne"; } - if (code == "ter") { return "Tereno"; } - if (code == "tet") { return "Tetum"; } - if (code == "tgk") { return "Tajik"; } - if (code == "tgl") { return "Tagalog"; } - if (code == "tha") { return "Thai"; } - if (code == "tib") { return "Tibetian"; } - if (code == "tig") { return "Tigre"; } - if (code == "tir") { return "Tigrinya"; } - if (code == "tiv") { return "Tiv"; } - if (code == "tkl") { return "Tokelau"; } - if (code == "tlh") { return "Klingon"; } - if (code == "tli") { return "Tlingit"; } - if (code == "tmh") { return "Tamashek"; } - if (code == "tog") { return "Tonga (Nyasa)"; } - if (code == "ton") { return "Tonga (Tonga Islands)"; } - if (code == "tpi") { return "Tok Pisin"; } - if (code == "tsi") { return "Tsimshian"; } - if (code == "tsn") { return "Tswana"; } - if (code == "tso") { return "Tsonga"; } - if (code == "tuk") { return "Turkmen"; } - if (code == "tum") { return "Tumbuka"; } - if (code == "tup") { return "Tupi languages"; } - if (code == "tur") { return "Turkish"; } - if (code == "tut") { return "Altaic languages"; } - if (code == "tvl") { return "Tuvalu"; } - if (code == "twi") { return "Twi"; } - if (code == "tyv") { return "Tuvinian"; } - if (code == "udm") { return "Udmurt"; } - if (code == "uga") { return "Ugaritic"; } - if (code == "uig") { return "Uighur"; } - if (code == "ukr") { return "Ukrainian"; } - if (code == "umb") { return "Umbundu"; } - if (code == "und") { return "Undetermined"; } - if (code == "urd") { return "Urdu"; } - if (code == "uzb") { return "Uzbek"; } - if (code == "vai") { return "Vai"; } - if (code == "ven") { return "Venda"; } - if (code == "vie") { return "Vietnamese"; } - if (code == "vol") { return "Volapük"; } - if (code == "vot") { return "Votic"; } - if (code == "wak") { return "Wakashan languages"; } - if (code == "wal") { return "Wolaitta"; } - if (code == "war") { return "Waray"; } - if (code == "was") { return "Washo"; } - if (code == "wel") { return "Welsh"; } - if (code == "wel") { return "Welsh"; } - if (code == "wen") { return "Sorbian languages"; } - if (code == "wln") { return "Walloon"; } - if (code == "wol") { return "Wolof"; } - if (code == "xal") { return "Kalmyk"; } - if (code == "xho") { return "Xhosa"; } - if (code == "yao") { return "Yao"; } - if (code == "yap") { return "Yapese"; } - if (code == "yid") { return "Yiddish"; } - if (code == "yor") { return "Yoruba"; } - if (code == "ypk") { return "Yupik languages"; } - if (code == "zap") { return "Zapotec"; } - if (code == "zbl") { return "Blissymbols"; } - if (code == "zen") { return "Zenaga"; } - if (code == "zgh") { return "Moroccan"; } - if (code == "zha") { return "Zhuang"; } - if (code == "zho") { return "Chinese"; } - if (code == "znd") { return "Zande languages"; } - if (code == "zul") { return "Zulu"; } - if (code == "zun") { return "Zuni"; } - if (code == "zza") { return "Zaza"; } + if (code == "AAR") { return "Afar"; } + if (code == "ABK") { return "Abkhazian"; } + if (code == "ACE") { return "Achinese"; } + if (code == "ACH") { return "Acoli"; } + if (code == "ADA") { return "Adangme"; } + if (code == "ADY") { return "Adyghe"; } + if (code == "AFA") { return "Afro-Asiatic languages"; } + if (code == "AFH") { return "Afrihili"; } + if (code == "AFR") { return "Afrikaans"; } + if (code == "AIN") { return "Ainu"; } + if (code == "AKA") { return "Akan"; } + if (code == "AKK") { return "Akkadian"; } + if (code == "ALB") { return "Albanian"; } + if (code == "ALE") { return "Aleut"; } + if (code == "ALG") { return "Algonquian languages"; } + if (code == "ALT") { return "Southern Altai"; } + if (code == "AMH") { return "Amharic"; } + if (code == "ANG") { return "English, Old (ca.450-1100)"; } + if (code == "ANP") { return "Angika"; } + if (code == "APA") { return "Apache languages"; } + if (code == "ARA") { return "Arabic"; } + if (code == "ARC") { return "Aramaic (700-300 BCE)"; } + if (code == "ARG") { return "Aragonese"; } + if (code == "ARM") { return "Armenian"; } + if (code == "ARN") { return "Mapudungun"; } + if (code == "ARP") { return "Arapaho"; } + if (code == "ART") { return "Artificial languages"; } + if (code == "ARW") { return "Arawak"; } + if (code == "ASM") { return "Assamese"; } + if (code == "AST") { return "Asturian"; } + if (code == "ATH") { return "Athapascan languages"; } + if (code == "AUS") { return "Australian languages"; } + if (code == "AVA") { return "Avaric"; } + if (code == "AVE") { return "Avestan"; } + if (code == "AWA") { return "Awadhi"; } + if (code == "AYM") { return "Aymara"; } + if (code == "AZE") { return "Azerbaijani"; } + if (code == "BAD") { return "Banda languages"; } + if (code == "BAI") { return "Bamileke languages"; } + if (code == "BAK") { return "Bashkir"; } + if (code == "BAL") { return "Baluchi"; } + if (code == "BAM") { return "Bambara"; } + if (code == "BAN") { return "Balinese"; } + if (code == "BAQ") { return "Basque"; } + if (code == "BAQ") { return "Basque"; } + if (code == "BAS") { return "Basa"; } + if (code == "BAT") { return "Baltic languages"; } + if (code == "BEJ") { return "Beja"; } + if (code == "BEL") { return "Belarusian"; } + if (code == "BEM") { return "Bemba"; } + if (code == "BEN") { return "Bengali"; } + if (code == "BER") { return "Berber languages"; } + if (code == "BHO") { return "Bhojpuri"; } + if (code == "BIH") { return "Bihari languages"; } + if (code == "BIK") { return "Bikol"; } + if (code == "BIN") { return "Bini"; } + if (code == "BIS") { return "Bislama"; } + if (code == "BLA") { return "Siksika"; } + if (code == "BNT") { return "Bantu languages"; } + if (code == "BOD") { return "Tibetan"; } + if (code == "BOS") { return "Bosnian"; } + if (code == "BRA") { return "Braj"; } + if (code == "BRE") { return "Breton"; } + if (code == "BTK") { return "Batak languages"; } + if (code == "BUA") { return "Buriat"; } + if (code == "BUG") { return "Buginese"; } + if (code == "BUL") { return "Bulgarian"; } + if (code == "BUR") { return "Burmese"; } + if (code == "BUR") { return "Burmese"; } + if (code == "BYN") { return "Blin"; } + if (code == "CAD") { return "Caddo"; } + if (code == "CAI") { return "Central American Indian languages"; } + if (code == "CAR") { return "Galibi Carib"; } + if (code == "CAT") { return "Catalan"; } + if (code == "CAU") { return "Caucasian languages"; } + if (code == "CEB") { return "Cebuano"; } + if (code == "CEL") { return "Celtic languages"; } + if (code == "CES") { return "Czech"; } + if (code == "CES") { return "Czech"; } + if (code == "CHA") { return "Chamorro"; } + if (code == "CHB") { return "Chibcha"; } + if (code == "CHE") { return "Chechen"; } + if (code == "CHG") { return "Chagatai"; } + if (code == "CHI") { return "Chinese"; } + if (code == "CHK") { return "Chuukese"; } + if (code == "CHM") { return "Mari"; } + if (code == "CHN") { return "Chinook jargon"; } + if (code == "CHO") { return "Choctaw"; } + if (code == "CHP") { return "Chipewyan"; } + if (code == "CHR") { return "Cherokee"; } + if (code == "CHU") { return "Church Slavic"; } + if (code == "CHV") { return "Chuvash"; } + if (code == "CHY") { return "Cheyenne"; } + if (code == "CMC") { return "Chamic languages"; } + if (code == "CNR") { return "Montenegrin"; } + if (code == "COP") { return "Coptic"; } + if (code == "COR") { return "Cornish"; } + if (code == "COS") { return "Corsican"; } + if (code == "CPE") { return "Creoles and pidgins, English based"; } + if (code == "CPF") { return "Creoles and pidgins, French-based"; } + if (code == "CPP") { return "Creoles and pidgins, Portuguese-based"; } + if (code == "CRE") { return "Cree"; } + if (code == "CRH") { return "Crimean Tatar"; } + if (code == "CRP") { return "Creoles and pidgins"; } + if (code == "CSB") { return "Kashubian"; } + if (code == "CUS") { return "Cushitic languages"; } + if (code == "CYM") { return "Welsh"; } + if (code == "CYM") { return "Welsh"; } + if (code == "CZE") { return "Czech"; } + if (code == "CZE") { return "Czech"; } + if (code == "DAK") { return "Dakota"; } + if (code == "DAN") { return "Danish"; } + if (code == "DAR") { return "Dargwa"; } + if (code == "DAY") { return "Land Dayak languages"; } + if (code == "DEL") { return "Delaware"; } + if (code == "DEN") { return "Slave (Athapascan)"; } + if (code == "DEU") { return "German"; } + if (code == "DGR") { return "Dogrib"; } + if (code == "DIN") { return "Dinka"; } + if (code == "DIV") { return "Divehi"; } + if (code == "DOI") { return "Dogri"; } + if (code == "DRA") { return "Dravidian languages"; } + if (code == "DSB") { return "Lower Sorbian"; } + if (code == "DUA") { return "Duala"; } + if (code == "DUM") { return "Dutch, Middle (ca.1050-1350)"; } + if (code == "DUT") { return "Dutch"; } + if (code == "DUT") { return "Dutch"; } + if (code == "DYU") { return "Dyula"; } + if (code == "DZO") { return "Dzongkha"; } + if (code == "EFI") { return "Efik"; } + if (code == "EGY") { return "Egyptian (Ancient)"; } + if (code == "EKA") { return "Ekajuk"; } + if (code == "ELL") { return "Greek, Modern (1453-)"; } + if (code == "ELX") { return "Elamite"; } + if (code == "ENG") { return "English"; } + if (code == "ENM") { return "English, Middle (1100-1500)"; } + if (code == "EPO") { return "Esperanto"; } + if (code == "EST") { return "Estonian"; } + if (code == "EUS") { return "Basque"; } + if (code == "EUS") { return "Basque"; } + if (code == "EWE") { return "Ewe"; } + if (code == "EWO") { return "Ewondo"; } + if (code == "FAN") { return "Fang"; } + if (code == "FAO") { return "Faroese"; } + if (code == "FAS") { return "Persian"; } + if (code == "FAT") { return "Fanti"; } + if (code == "FIJ") { return "Fijian"; } + if (code == "FIL") { return "Filipino"; } + if (code == "FIN") { return "Finnish"; } + if (code == "FIU") { return "Finno-Ugrian languages"; } + if (code == "FON") { return "Fon"; } + if (code == "FRA") { return "French"; } + if (code == "FRE") { return "French"; } + if (code == "FRM") { return "French, Middle (ca.1400-1600)"; } + if (code == "FRO") { return "French, Old (842-ca.1400)"; } + if (code == "FRR") { return "Northern Frisian"; } + if (code == "FRS") { return "Eastern Frisian"; } + if (code == "FRY") { return "Western Frisian"; } + if (code == "FUL") { return "Fulah"; } + if (code == "FUR") { return "Friulian"; } + if (code == "GAA") { return "Ga"; } + if (code == "GAY") { return "Gayo"; } + if (code == "GBA") { return "Gbaya"; } + if (code == "GEM") { return "Germanic languages"; } + if (code == "GEO") { return "Georgin"; } + if (code == "GER") { return "German"; } + if (code == "GEZ") { return "Geez"; } + if (code == "GIL") { return "Gilbertese"; } + if (code == "GLA") { return "Gaelic"; } + if (code == "GLE") { return "Irish"; } + if (code == "GLG") { return "Galician"; } + if (code == "GLV") { return "Manx"; } + if (code == "GMH") { return "German, Middle High (ca.1050-1500)"; } + if (code == "GOH") { return "German, Old High (ca.750-1050)"; } + if (code == "GON") { return "Gondi"; } + if (code == "GOR") { return "Gorontalo"; } + if (code == "GOT") { return "Gothic"; } + if (code == "GRB") { return "Grebo"; } + if (code == "GRC") { return "Greek, Ancient (to 1453)"; } + if (code == "GRE") { return "Greek"; } + if (code == "GRN") { return "Guarani"; } + if (code == "GSW") { return "Swiss German"; } + if (code == "GUJ") { return "Gujarati"; } + if (code == "GWI") { return "Gwich'in"; } + if (code == "HAI") { return "Haida"; } + if (code == "HAT") { return "Haitian"; } + if (code == "HAU") { return "Hausa"; } + if (code == "HAW") { return "Hawaiian"; } + if (code == "HEB") { return "Hebrew"; } + if (code == "HER") { return "Herero"; } + if (code == "HIL") { return "Hiligaynon"; } + if (code == "HIM") { return "Himachali languages"; } + if (code == "HIN") { return "Hindi"; } + if (code == "HIT") { return "Hittite"; } + if (code == "HMN") { return "Hmong"; } + if (code == "HMO") { return "Hiri Motu"; } + if (code == "HRV") { return "Croatian"; } + if (code == "HSB") { return "Upper Sorbian"; } + if (code == "HUN") { return "Hungarian"; } + if (code == "HUP") { return "Hupa"; } + if (code == "HYE") { return "Armenian"; } + if (code == "IBA") { return "Iban"; } + if (code == "IBO") { return "Igbo"; } + if (code == "ICE") { return "Icelandic"; } + if (code == "IDO") { return "Ido"; } + if (code == "III") { return "Sichuan Yi"; } + if (code == "IJO") { return "Ijo languages"; } + if (code == "IKU") { return "Inuktitut"; } + if (code == "ILE") { return "Interlingue"; } + if (code == "ILO") { return "Iloko"; } + if (code == "INA") { return "Interlingua)"; } + if (code == "INC") { return "Indic languages"; } + if (code == "IND") { return "Indonesian"; } + if (code == "INE") { return "Indo-European languages"; } + if (code == "INH") { return "Ingush"; } + if (code == "IPK") { return "Inupiaq"; } + if (code == "IRA") { return "Iranian languages"; } + if (code == "IRO") { return "Iroquoian languages"; } + if (code == "ISL") { return "Icelandic"; } + if (code == "ITA") { return "Italian"; } + if (code == "JAV") { return "Javanese"; } + if (code == "JBO") { return "Lojban"; } + if (code == "JPN") { return "Japanese"; } + if (code == "JPR") { return "Judeo-Persian"; } + if (code == "JRB") { return "Judeo-Arabic"; } + if (code == "KAA") { return "Kara-Kalpak"; } + if (code == "KAB") { return "Kabyle"; } + if (code == "KAC") { return "Kachin"; } + if (code == "KAL") { return "Greenlandic"; } + if (code == "KAM") { return "Kamba"; } + if (code == "KAN") { return "Kannada"; } + if (code == "KAR") { return "Karen languages"; } + if (code == "KAS") { return "Kashmiri"; } + if (code == "KAT") { return "Georgian"; } + if (code == "KAU") { return "Kanuri"; } + if (code == "KAW") { return "Kawi"; } + if (code == "KAZ") { return "Kazakh"; } + if (code == "KBD") { return "Kabardian"; } + if (code == "KHA") { return "Khasi"; } + if (code == "KHI") { return "Khoisan languages"; } + if (code == "KHM") { return "Central Khmer"; } + if (code == "KHO") { return "Khotanese"; } + if (code == "KIK") { return "Kikuyu"; } + if (code == "KIN") { return "Kinyarwanda"; } + if (code == "KIR") { return "Kirghiz"; } + if (code == "KMB") { return "Kimbundu"; } + if (code == "KOK") { return "Konkani"; } + if (code == "KOM") { return "Komi"; } + if (code == "KON") { return "Kongo"; } + if (code == "KOR") { return "Korean"; } + if (code == "KOS") { return "Kosraean"; } + if (code == "KPE") { return "Kpelle"; } + if (code == "KRC") { return "Karachay-Balkar"; } + if (code == "KRL") { return "Karelian"; } + if (code == "KRO") { return "Kru languages"; } + if (code == "KRU") { return "Kurukh"; } + if (code == "KUA") { return "Kuanyama"; } + if (code == "KUM") { return "Kumyk"; } + if (code == "KUR") { return "Kurdish"; } + if (code == "KUT") { return "Kutenai"; } + if (code == "LAD") { return "Ladino"; } + if (code == "LAH") { return "Lahnda"; } + if (code == "LAM") { return "Lamba"; } + if (code == "LAO") { return "Lao"; } + if (code == "LAT") { return "Latin"; } + if (code == "LAV") { return "Latvian"; } + if (code == "LEZ") { return "Lezghian"; } + if (code == "LIM") { return "Limburgan"; } + if (code == "LIN") { return "Lingala"; } + if (code == "LIT") { return "Lithuanian"; } + if (code == "LOL") { return "Mongo"; } + if (code == "LOZ") { return "Lozi"; } + if (code == "LTZ") { return "Luxembourgish"; } + if (code == "LUA") { return "Luba-Lulua"; } + if (code == "LUB") { return "Luba-Katanga"; } + if (code == "LUG") { return "Ganda"; } + if (code == "LUI") { return "Luiseno"; } + if (code == "LUN") { return "Lunda"; } + if (code == "LUO") { return "Luo (Kenya and Tanzania)"; } + if (code == "LUS") { return "Lushai"; } + if (code == "MAC") { return "Macedonian"; } + if (code == "MAC") { return "Macedonian"; } + if (code == "MAD") { return "Madurese"; } + if (code == "MAG") { return "Magahi"; } + if (code == "MAH") { return "Marshallese"; } + if (code == "MAI") { return "Maithili"; } + if (code == "MAK") { return "Makasar"; } + if (code == "MAL") { return "Malayalam"; } + if (code == "MAN") { return "Mandingo"; } + if (code == "MAO") { return "Maori"; } + if (code == "MAP") { return "Austronesian languages"; } + if (code == "MAR") { return "Marathi"; } + if (code == "MAS") { return "Masai"; } + if (code == "MAY") { return "Malay"; } + if (code == "MDF") { return "Moksha"; } + if (code == "MDR") { return "Mandar"; } + if (code == "MEN") { return "Mende"; } + if (code == "MGA") { return "Irish, Middle (900-1200)"; } + if (code == "MIC") { return "Mi'kmaq"; } + if (code == "MIN") { return "Minangkabau"; } + if (code == "MIS") { return "Uncoded languages"; } + if (code == "MKD") { return "Macedonian"; } + if (code == "MKD") { return "Macedonian"; } + if (code == "MKH") { return "Mon-Khmer languages"; } + if (code == "MLG") { return "Malagasy"; } + if (code == "MLT") { return "Maltese"; } + if (code == "MNC") { return "Manchu"; } + if (code == "MNI") { return "Manipuri"; } + if (code == "MNO") { return "Manobo languages"; } + if (code == "MOH") { return "Mohawk"; } + if (code == "MON") { return "Mongolian"; } + if (code == "MOS") { return "Mossi"; } + if (code == "MRI") { return "Maori"; } + if (code == "MSA") { return "Malay"; } + if (code == "MUL") { return "Multiple languages"; } + if (code == "MUN") { return "Munda languages"; } + if (code == "MUS") { return "Creek"; } + if (code == "MWL") { return "Mirandese"; } + if (code == "MWR") { return "Marwari"; } + if (code == "MYA") { return "Burmese"; } + if (code == "MYA") { return "Burmese"; } + if (code == "MYN") { return "Mayan languages"; } + if (code == "MYV") { return "Erzya"; } + if (code == "NAH") { return "Nahuatl languages"; } + if (code == "NAI") { return "North American Indian languages"; } + if (code == "NAP") { return "Neapolitan"; } + if (code == "NAU") { return "Nauru"; } + if (code == "NAV") { return "Navajo"; } + if (code == "NBL") { return "Ndebele, South"; } + if (code == "NDE") { return "Ndebele, North"; } + if (code == "NDO") { return "Ndonga"; } + if (code == "NDS") { return "Low German"; } + if (code == "NEP") { return "Nepali"; } + if (code == "NEW") { return "Nepal Bhasa"; } + if (code == "NIA") { return "Nias"; } + if (code == "NIC") { return "Niger-Kordofanian languages"; } + if (code == "NIU") { return "Niuean"; } + if (code == "NLD") { return "Dutch"; } + if (code == "NLD") { return "Dutch"; } + if (code == "NNO") { return "Norwegian Nynorsk"; } + if (code == "NOB") { return "Bokmål, Norwegian"; } + if (code == "NOG") { return "Nogai"; } + if (code == "NON") { return "Norse, Old"; } + if (code == "NOR") { return "Norwegian"; } + if (code == "NQO") { return "N'Ko"; } + if (code == "NSO") { return "Pedi"; } + if (code == "NUB") { return "Nubian languages"; } + if (code == "NWC") { return "Classical Newari"; } + if (code == "NYA") { return "Chichewa"; } + if (code == "NYM") { return "Nyamwezi"; } + if (code == "NYN") { return "Nyankole"; } + if (code == "NYO") { return "Nyoro"; } + if (code == "NZI") { return "Nzima"; } + if (code == "OCI") { return "Occitan (post 1500)"; } + if (code == "OJI") { return "Ojibwa"; } + if (code == "ORI") { return "Oriya"; } + if (code == "ORM") { return "Oromo"; } + if (code == "OSA") { return "Osage"; } + if (code == "OSS") { return "Ossetian"; } + if (code == "OTA") { return "Turkish, Ottoman (1500-1928)"; } + if (code == "OTO") { return "Otomian languages"; } + if (code == "PAA") { return "Papuan languages"; } + if (code == "PAG") { return "Pangasinan"; } + if (code == "PAL") { return "Pahlavi"; } + if (code == "PAM") { return "Pampanga"; } + if (code == "PAN") { return "Panjabi"; } + if (code == "PAP") { return "Papiamento"; } + if (code == "PAU") { return "Palauan"; } + if (code == "PEO") { return "Persian, Old (ca.600-400 B.C.)"; } + if (code == "PER") { return "Persian"; } + if (code == "PHI") { return "Philippine languages"; } + if (code == "PHN") { return "Phoenician"; } + if (code == "PLI") { return "Pali"; } + if (code == "POL") { return "Polish"; } + if (code == "PON") { return "Pohnpeian"; } + if (code == "POR") { return "Portuguese"; } + if (code == "PRA") { return "Prakrit languages"; } + if (code == "PRO") { return "Provençal, Old (to 1500)"; } + if (code == "PUS") { return "Pushto"; } + if (code == "QUE") { return "Quechua"; } + if (code == "RAJ") { return "Rajasthani"; } + if (code == "RAP") { return "Rapanui"; } + if (code == "RAR") { return "Rarotongan"; } + if (code == "ROA") { return "Romance languages"; } + if (code == "ROH") { return "Romansh"; } + if (code == "ROM") { return "Romany"; } + if (code == "RON") { return "Romanian"; } + if (code == "RUM") { return "Romanian"; } + if (code == "RUN") { return "Rundi"; } + if (code == "RUP") { return "Aromanian"; } + if (code == "RUS") { return "Russian"; } + if (code == "SAD") { return "Sandawe"; } + if (code == "SAG") { return "Sango"; } + if (code == "SAH") { return "Yakut"; } + if (code == "SAI") { return "South American Indian languages"; } + if (code == "SAL") { return "Salishan languages"; } + if (code == "SAM") { return "Samaritan Aramaic"; } + if (code == "SAN") { return "Sanskrit"; } + if (code == "SAS") { return "Sasak"; } + if (code == "SAT") { return "Santali"; } + if (code == "SCN") { return "Sicilian"; } + if (code == "SCO") { return "Scots"; } + if (code == "SEL") { return "Selkup"; } + if (code == "SEM") { return "Semitic languages"; } + if (code == "SGA") { return "Irish, Old (to 900)"; } + if (code == "SGN") { return "Sign Languages"; } + if (code == "SHN") { return "Shan"; } + if (code == "SID") { return "Sidamo"; } + if (code == "SIN") { return "Sinhala"; } + if (code == "SIO") { return "Siouan languages"; } + if (code == "SIT") { return "Sino-Tibetan languages"; } + if (code == "SLA") { return "Slavic languages"; } + if (code == "SLO") { return "Slovak"; } + if (code == "SLV") { return "Slovenian"; } + if (code == "SMA") { return "Southern Sami"; } + if (code == "SME") { return "Northern Sami"; } + if (code == "SMI") { return "Sami languages"; } + if (code == "SMJ") { return "Lule Sami"; } + if (code == "SMN") { return "Inari Sami"; } + if (code == "SMO") { return "Samoan"; } + if (code == "SMS") { return "Skolt Sami"; } + if (code == "SNA") { return "Shona"; } + if (code == "SND") { return "Sindhi"; } + if (code == "SNK") { return "Soninke"; } + if (code == "SOG") { return "Sogdian"; } + if (code == "SOM") { return "Somali"; } + if (code == "SON") { return "Songhai languages"; } + if (code == "SOT") { return "Sotho, Southern"; } + if (code == "SPA") { return "Spanish"; } + if (code == "SQI") { return "Albanian"; } + if (code == "SRD") { return "Sardinian"; } + if (code == "SRN") { return "Sranan Tongo"; } + if (code == "SRP") { return "Serbian"; } + if (code == "SRR") { return "Serer"; } + if (code == "SSA") { return "Nilo-Saharan languages"; } + if (code == "SSW") { return "Swati"; } + if (code == "SUK") { return "Sukuma"; } + if (code == "SUN") { return "Sundanese"; } + if (code == "SUS") { return "Susu"; } + if (code == "SUX") { return "Sumerian"; } + if (code == "SWA") { return "Swahili"; } + if (code == "SWE") { return "Swedish"; } + if (code == "SYC") { return "Classical Syriac"; } + if (code == "SYR") { return "Syriac"; } + if (code == "TAH") { return "Tahitian"; } + if (code == "TAI") { return "Tai languages"; } + if (code == "TAM") { return "Tamil"; } + if (code == "TAT") { return "Tatar"; } + if (code == "TEL") { return "Telugu"; } + if (code == "TEM") { return "Timne"; } + if (code == "TER") { return "Tereno"; } + if (code == "TET") { return "Tetum"; } + if (code == "TGK") { return "Tajik"; } + if (code == "TGL") { return "Tagalog"; } + if (code == "THA") { return "Thai"; } + if (code == "TIB") { return "Tibetian"; } + if (code == "TIG") { return "Tigre"; } + if (code == "TIR") { return "Tigrinya"; } + if (code == "TIV") { return "Tiv"; } + if (code == "TKL") { return "Tokelau"; } + if (code == "TLH") { return "Klingon"; } + if (code == "TLI") { return "Tlingit"; } + if (code == "TMH") { return "Tamashek"; } + if (code == "TOG") { return "Tonga (Nyasa)"; } + if (code == "TON") { return "Tonga (Tonga Islands)"; } + if (code == "TPI") { return "Tok Pisin"; } + if (code == "TSI") { return "Tsimshian"; } + if (code == "TSN") { return "Tswana"; } + if (code == "TSO") { return "Tsonga"; } + if (code == "TUK") { return "Turkmen"; } + if (code == "TUM") { return "Tumbuka"; } + if (code == "TUP") { return "Tupi languages"; } + if (code == "TUR") { return "Turkish"; } + if (code == "TUT") { return "Altaic languages"; } + if (code == "TVL") { return "Tuvalu"; } + if (code == "TWI") { return "Twi"; } + if (code == "TYV") { return "Tuvinian"; } + if (code == "UDM") { return "Udmurt"; } + if (code == "UGA") { return "Ugaritic"; } + if (code == "UIG") { return "Uighur"; } + if (code == "UKR") { return "Ukrainian"; } + if (code == "UMB") { return "Umbundu"; } + if (code == "UND") { return "Undetermined"; } + if (code == "URD") { return "Urdu"; } + if (code == "UZB") { return "Uzbek"; } + if (code == "VAI") { return "Vai"; } + if (code == "VEN") { return "Venda"; } + if (code == "VIE") { return "Vietnamese"; } + if (code == "VOL") { return "Volapük"; } + if (code == "VOT") { return "Votic"; } + if (code == "WAK") { return "Wakashan languages"; } + if (code == "WAL") { return "Wolaitta"; } + if (code == "WAR") { return "Waray"; } + if (code == "WAS") { return "Washo"; } + if (code == "WEL") { return "Welsh"; } + if (code == "WEL") { return "Welsh"; } + if (code == "WEN") { return "Sorbian languages"; } + if (code == "WLN") { return "Walloon"; } + if (code == "WOL") { return "Wolof"; } + if (code == "XAL") { return "Kalmyk"; } + if (code == "XHO") { return "Xhosa"; } + if (code == "YAO") { return "Yao"; } + if (code == "YAP") { return "Yapese"; } + if (code == "YID") { return "Yiddish"; } + if (code == "YOR") { return "Yoruba"; } + if (code == "YPK") { return "Yupik languages"; } + if (code == "ZAP") { return "Zapotec"; } + if (code == "ZBL") { return "Blissymbols"; } + if (code == "ZEN") { return "Zenaga"; } + if (code == "ZGH") { return "Moroccan"; } + if (code == "ZHA") { return "Zhuang"; } + if (code == "ZHO") { return "Chinese"; } + if (code == "ZND") { return "Zande languages"; } + if (code == "ZUL") { return "Zulu"; } + if (code == "ZUN") { return "Zuni"; } + if (code == "ZZA") { return "Zaza"; } } return code; } @@ -14507,8 +14507,8 @@ ostream& operator<<(ostream& out, HumHash* hash) { typedef unsigned long long TEMP64BITFIX; // declare static variables -vector<_HumInstrument> HumInstrument::data; -int HumInstrument::classcount = 0; +vector<_HumInstrument> HumInstrument::m_data; +int HumInstrument::m_classcount = 0; ////////////////////////////// @@ -14517,11 +14517,11 @@ int HumInstrument::classcount = 0; // HumInstrument::HumInstrument(void) { - if (classcount == 0) { + if (m_classcount == 0) { initialize(); } - classcount++; - index = -1; + m_classcount++; + m_index = -1; } @@ -14532,11 +14532,11 @@ HumInstrument::HumInstrument(void) { // HumInstrument::HumInstrument(const string& Hname) { - if (classcount == 0) { + if (m_classcount == 0) { initialize(); } - index = find(Hname); + m_index = find(Hname); } @@ -14547,7 +14547,7 @@ HumInstrument::HumInstrument(const string& Hname) { // HumInstrument::~HumInstrument() { - index = -1; + m_index = -1; } @@ -14558,8 +14558,8 @@ HumInstrument::~HumInstrument() { // int HumInstrument::getGM(void) { - if (index > 0) { - return data[index].gm; + if (m_index > 0) { + return m_data[m_index].gm; } else { return -1; } @@ -14581,7 +14581,7 @@ int HumInstrument::getGM(const string& Hname) { } if (tindex > 0) { - return data[tindex].gm; + return m_data[tindex].gm; } else { return -1; } @@ -14595,8 +14595,8 @@ int HumInstrument::getGM(const string& Hname) { // string HumInstrument::getName(void) { - if (index > 0) { - return data[index].name; + if (m_index > 0) { + return m_data[m_index].name; } else { return ""; } @@ -14617,7 +14617,7 @@ string HumInstrument::getName(const string& Hname) { tindex = find(Hname); } if (tindex > 0) { - return data[tindex].name; + return m_data[tindex].name; } else { return ""; } @@ -14631,8 +14631,8 @@ string HumInstrument::getName(const string& Hname) { // string HumInstrument::getHumdrum(void) { - if (index > 0) { - return data[index].humdrum; + if (m_index > 0) { + return m_data[m_index].humdrum; } else { return ""; } @@ -14651,7 +14651,7 @@ int HumInstrument::setGM(const string& Hname, int aValue) { } int rindex = find(Hname); if (rindex > 0) { - data[rindex].gm = aValue; + m_data[rindex].gm = aValue; } else { afi(Hname.c_str(), aValue, Hname.c_str()); sortData(); @@ -14668,11 +14668,11 @@ int HumInstrument::setGM(const string& Hname, int aValue) { void HumInstrument::setHumdrum(const string& Hname) { if (Hname.compare(0, 2, "*I") == 0) { - index = find(Hname.substr(2)); + m_index = find(Hname.substr(2)); } else { - index = find(Hname); + m_index = find(Hname); } -} + } @@ -14686,171 +14686,223 @@ void HumInstrument::setHumdrum(const string& Hname) { // // HumInstrument::initialize -- // - void HumInstrument::initialize(void) { - data.reserve(500); - afi("accor", GM_ACCORDION, "accordion"); - afi("alto", GM_RECORDER, "alto"); - afi("archl", GM_ACOUSTIC_GUITAR_NYLON, "archlute"); - afi("armon", GM_HARMONICA, "harmonica"); - afi("arpa", GM_ORCHESTRAL_HARP, "harp"); - afi("bagpI", GM_BAGPIPE, "bagpipe (Irish)"); - afi("bagpS", GM_BAGPIPE, "bagpipe (Scottish)"); - afi("banjo", GM_BANJO, "banjo"); - afi("barit", GM_CHOIR_AAHS, "baritone"); - afi("baset", GM_CLARINET, "bassett horn"); - afi("bass", GM_CHOIR_AAHS, "bass"); - afi("bdrum", GM_TAIKO_DRUM, "bass drum (kit)"); - afi("bguit", GM_ELECTRIC_BASS_FINGER, "electric bass guitar"); - afi("biwa", GM_FLUTE, "biwa"); - afi("bscan", GM_CHOIR_AAHS, "basso cantante"); - afi("bspro", GM_CHOIR_AAHS, "basso profondo"); - afi("calam", GM_OBOE, "chalumeau"); - afi("calpe", GM_LEAD_CALLIOPE, "calliope"); - afi("calto", GM_CHOIR_AAHS, "contralto"); - afi("campn", GM_TUBULAR_BELLS, "bell"); - afi("cangl", GM_ENGLISH_HORN, "english horn"); - afi("caril", GM_TUBULAR_BELLS, "carillon"); - afi("castr", GM_CHOIR_AAHS, "castrato"); - afi("casts", GM_WOODBLOCKS, "castanets"); - afi("cbass", GM_CONTRABASS, "contrabass"); - afi("cello", GM_CELLO, "violoncello"); - afi("cemba", GM_HARPSICHORD, "harpsichord"); - afi("cetra", GM_VIOLIN, "cittern"); - afi("chime", GM_TUBULAR_BELLS, "chimes"); - afi("chlma", GM_BASSOON, "alto shawm"); - afi("chlms", GM_BASSOON, "soprano shawm"); - afi("chlmt", GM_BASSOON, "tenor shawm"); - afi("clara", GM_CLARINET, "alto clarinet (in E-flat)"); - afi("clarb", GM_CLARINET, "bass clarinet (in B-flat)"); - afi("clarp", GM_CLARINET, "piccolo clarinet"); - afi("clars", GM_CLARINET, "soprano clarinet"); - afi("clavi", GM_CLAVI, "clavichord"); - afi("clest", GM_CELESTA, "celesta"); - afi("colsp", GM_FLUTE, "coloratura soprano"); - afi("cor", GM_FRENCH_HORN, "horn"); - afi("cornm", GM_BAGPIPE, "French bagpipe"); - afi("corno", GM_TRUMPET, "cornett"); - afi("cornt", GM_TRUMPET, "cornet"); - afi("crshc", GM_REVERSE_CYMBAL, "crash cymbal (kit)"); - afi("ctenor", GM_CHOIR_AAHS, "counter-tenor"); - afi("ctina", GM_ACCORDION, "concertina"); - afi("drmsp", GM_FLUTE, "dramatic soprano"); - afi("dulc", GM_DULCIMER, "dulcimer"); - afi("eguit", GM_ELECTRIC_GUITAR_CLEAN, "electric guitar"); - afi("fag_c", GM_BASSOON, "contrabassoon"); - afi("fagot", GM_BASSOON, "bassoon"); - afi("false", GM_RECORDER, "falsetto"); - afi("feme", GM_CHOIR_AAHS, "female voice"); - afi("fife", GM_BLOWN_BOTTLE, "fife"); - afi("fingc", GM_REVERSE_CYMBAL, "finger cymbal"); - afi("flt", GM_FLUTE, "flute"); - afi("flt_a", GM_FLUTE, "alto flute"); - afi("flt_b", GM_FLUTE, "bass flute"); - afi("fltda", GM_RECORDER, "alto recorder"); - afi("fltdb", GM_RECORDER, "bass recorder"); - afi("fltdn", GM_RECORDER, "sopranino recorder"); - afi("fltds", GM_RECORDER, "soprano recorder"); - afi("fltdt", GM_RECORDER, "tenor recorder"); - afi("flugh", GM_FRENCH_HORN, "flugelhorn"); - afi("forte", GM_HONKYTONK_PIANO, "fortepiano"); - afi("glock", GM_GLOCKENSPIEL, "glockenspiel"); - afi("gong", GM_STEEL_DRUMS, "gong"); - afi("guitr", GM_ACOUSTIC_GUITAR_NYLON, "guitar"); - afi("hammd", GM_DRAWBAR_ORGAN, "Hammond electronic organ"); - afi("heltn", GM_CHOIR_AAHS, "Heldentenor"); - afi("hichi", GM_OBOE, "hichiriki"); - afi("hurdy", GM_LEAD_CALLIOPE, "hurdy-gurdy"); - afi("kit", GM_SYNTH_DRUM, "drum kit"); - afi("kokyu", GM_FIDDLE, "kokyu (Japanese spike fiddle)"); - afi("komun", GM_KOTO, "komun'go (Korean long zither)"); - afi("koto", GM_KOTO, "koto (Japanese long zither)"); - afi("kruma", GM_TRUMPET, "alto crumhorn"); - afi("krumb", GM_TRUMPET, "bass crumhorn"); - afi("krums", GM_TRUMPET, "soprano crumhorn"); - afi("krumt", GM_TRUMPET, "tenor crumhorn"); - afi("liuto", GM_ACOUSTIC_GUITAR_NYLON, "lute"); - afi("lyrsp", GM_FLUTE, "lyric soprano"); - afi("lyrtn", GM_FRENCH_HORN, "lyric tenor"); - afi("male", GM_CHOIR_AAHS, "male voice"); - afi("mando", GM_ACOUSTIC_GUITAR_NYLON, "mandolin"); - afi("marac", GM_AGOGO, "maracas"); - afi("marim", GM_MARIMBA, "marimba"); - afi("mezzo", GM_CHOIR_AAHS, "mezzo soprano"); - afi("nfant", GM_CHOIR_AAHS, "child's voice"); - afi("nokan", GM_SHAKUHACHI, "nokan (a Japanese flute)"); - afi("oboeD", GM_ENGLISH_HORN, "oboe d'amore"); - afi("oboe", GM_OBOE, "oboe"); - afi("ocari", GM_OCARINA, "ocarina"); - afi("organ", GM_CHURCH_ORGAN, "pipe organ"); - afi("panpi", GM_PAN_FLUTE, "panpipe"); - afi("piano", GM_ACOUSTIC_GRAND_PIANO, "pianoforte"); - afi("piatt", GM_REVERSE_CYMBAL, "cymbals"); - afi("picco", GM_PICCOLO, "piccolo"); - afi("pipa", GM_ACOUSTIC_GUITAR_NYLON, "Chinese lute"); - afi("porta", GM_TANGO_ACCORDION, "portative organ"); - afi("psalt", GM_CLAVI, "psaltery (box zither)"); - afi("qin", GM_CLAVI, "qin, ch'in (Chinese zither)"); - afi("quitr", GM_ACOUSTIC_GUITAR_NYLON, "gittern"); - afi("rackt", GM_TRUMPET, "racket"); - afi("rebec", GM_ACOUSTIC_GUITAR_NYLON, "rebec"); - afi("recit", GM_CHOIR_AAHS, "recitativo"); - afi("reedo", GM_REED_ORGAN, "reed organ"); - afi("rhode", GM_ELECTRIC_PIANO_1, "Fender-Rhodes electric piano"); - afi("ridec", GM_REVERSE_CYMBAL, "ride cymbal (kit)"); - afi("sarod", GM_SITAR, "sarod"); - afi("sarus", GM_TUBA, "sarrusophone"); - afi("saxA", GM_ALTO_SAX, "E-flat alto saxophone"); - afi("saxB", GM_BARITONE_SAX, "B-flat bass saxophone"); - afi("saxC", GM_BARITONE_SAX, "E-flat contrabass saxophone"); - afi("saxN", GM_SOPRANO_SAX, "E-flat sopranino saxophone"); - afi("saxR", GM_BARITONE_SAX, "E-flat baritone saxophone"); - afi("saxS", GM_SOPRANO_SAX, "B-flat soprano saxophone"); - afi("saxT", GM_TENOR_SAX, "B-flat tenor saxophone"); - afi("sdrum", GM_SYNTH_DRUM, "snare drum (kit)"); - afi("shaku", GM_SHAKUHACHI, "shakuhachi"); - afi("shami", GM_SHAMISEN, "shamisen (Japanese fretless lute)"); - afi("sheng", GM_SHANAI, "mouth organ (Chinese)"); - afi("sho", GM_SHANAI, "mouth organ (Japanese)"); - afi("sitar", GM_SITAR, "sitar"); - afi("soprn", GM_CHOIR_AAHS, "soprano"); - afi("spshc", GM_REVERSE_CYMBAL, "splash cymbal (kit)"); - afi("steel", GM_STEEL_DRUMS, "steel-drum"); - afi("sxhA", GM_ALTO_SAX, "E-flat alto saxhorn"); - afi("sxhB", GM_BARITONE_SAX, "B-flat bass saxhorn"); - afi("sxhC", GM_BARITONE_SAX, "E-flat contrabass saxhorn"); - afi("sxhR", GM_BARITONE_SAX, "E-flat baritone saxhorn"); - afi("sxhS", GM_SOPRANO_SAX, "B-flat soprano saxhorn"); - afi("sxhT", GM_TENOR_SAX, "B-flat tenor saxhorn"); - afi("synth", GM_ELECTRIC_PIANO_2, "keyboard synthesizer"); - afi("tabla", GM_MELODIC_DRUM, "tabla"); - afi("tambn", GM_TINKLE_BELL, "tambourine"); - afi("tambu", GM_MELODIC_DRUM, "tambura"); - afi("tanbr", GM_MELODIC_DRUM, "tanbur"); - afi("tenor", GM_CHOIR_AAHS, "tenor"); - afi("timpa", GM_MELODIC_DRUM, "timpani"); - afi("tiorb", GM_ACOUSTIC_GUITAR_NYLON, "theorbo"); - afi("tom", GM_TAIKO_DRUM, "tom-tom drum"); - afi("trngl", GM_TINKLE_BELL, "triangle"); - afi("tromb", GM_TROMBONE, "bass trombone"); - afi("tromp", GM_TRUMPET, "trumpet"); - afi("tromt", GM_TROMBONE, "tenor trombone"); - afi("tuba", GM_TUBA, "tuba"); - afi("ud", GM_ACOUSTIC_GUITAR_NYLON, "ud"); - afi("ukule", GM_ACOUSTIC_GUITAR_NYLON, "ukulele"); - afi("vibra", GM_VIBRAPHONE, "vibraphone"); - afi("vina", GM_SITAR, "vina"); - afi("viola", GM_VIOLA, "viola"); - afi("violb", GM_CONTRABASS, "bass viola da gamba"); - afi("viold", GM_VIOLA, "viola d'amore"); - afi("violn", GM_VIOLIN, "violin"); - afi("violp", GM_VIOLIN, "piccolo violin"); - afi("viols", GM_VIOLIN, "treble viola da gamba"); - afi("violt", GM_CELLO, "tenor viola da gamba"); - afi("vox", GM_CHOIR_AAHS, "generic voice"); - afi("xylo", GM_XYLOPHONE, "xylophone"); - afi("zithr", GM_CLAVI, "zither"); - afi("zurna", GM_ACOUSTIC_GUITAR_NYLON, "zurna"); + m_data.reserve(500); + + // List has to be sorted by first parameter. Maybe put in map. + afi("accor", GM_ACCORDION, "accordion"); + afi("alto", GM_RECORDER, "alto"); + afi("anvil", GM_TINKLE_BELL, "anvil"); + afi("archl", GM_ACOUSTIC_GUITAR_NYLON, "archlute"); + afi("armon", GM_HARMONICA, "harmonica"); + afi("arpa", GM_ORCHESTRAL_HARP, "harp"); + afi("bagpI", GM_BAGPIPE, "bagpipe (Irish)"); + afi("bagpS", GM_BAGPIPE, "bagpipe (Scottish)"); + afi("banjo", GM_BANJO, "banjo"); + afi("bansu", GM_FLUTE, "bansuri"); + afi("barit", GM_CHOIR_AAHS, "baritone"); + afi("baset", GM_CLARINET, "bassett horn"); + afi("bass", GM_CHOIR_AAHS, "bass"); + afi("bdrum", GM_TAIKO_DRUM, "bass drum"); + afi("bguit", GM_ELECTRIC_BASS_FINGER, "electric bass guitar"); + afi("biwa", GM_FLUTE, "biwa"); + afi("bongo", GM_TAIKO_DRUM, "bongo"); + afi("brush", GM_BREATH_NOISE, "brush"); + afi("bscan", GM_CHOIR_AAHS, "basso cantante"); + afi("bspro", GM_CHOIR_AAHS, "basso profondo"); + afi("bugle", GM_TRUMPET, "bugle"); + afi("calam", GM_OBOE, "chalumeau"); + afi("calpe", GM_LEAD_CALLIOPE, "calliope"); + afi("calto", GM_CHOIR_AAHS, "contralto"); + afi("campn", GM_TUBULAR_BELLS, "bell"); + afi("cangl", GM_ENGLISH_HORN, "english horn"); + afi("canto", GM_CHOIR_AAHS, "canto"); + afi("caril", GM_TUBULAR_BELLS, "carillon"); + afi("castr", GM_CHOIR_AAHS, "castrato"); + afi("casts", GM_WOODBLOCKS, "castanets"); + afi("cbass", GM_CONTRABASS, "contrabass"); + afi("cello", GM_CELLO, "violoncello"); + afi("cemba", GM_HARPSICHORD, "harpsichord"); + afi("cetra", GM_VIOLIN, "cittern"); + afi("chain", GM_TINKLE_BELL, "chains"); + afi("chcym", GM_REVERSE_CYMBAL, "China cymbal"); + afi("chime", GM_TUBULAR_BELLS, "chimes"); + afi("chlma", GM_BASSOON, "alto shawm"); + afi("chlms", GM_BASSOON, "soprano shawm"); + afi("chlmt", GM_BASSOON, "tenor shawm"); + afi("clap", GM_GUNSHOT, "hand clapping"); + afi("clara", GM_CLARINET, "alto clarinet"); + afi("clarb", GM_CLARINET, "bass clarinet"); + afi("clarp", GM_CLARINET, "piccolo clarinet"); + afi("clars", GM_CLARINET, "clarinet"); + afi("clave", GM_AGOGO, "claves"); + afi("clavi", GM_CLAVI, "clavichord"); + afi("clest", GM_CELESTA, "celesta"); + afi("clrno", GM_TRUMPET, "clarino"); + afi("colsp", GM_FLUTE, "coloratura soprano"); + afi("conga", GM_TAIKO_DRUM, "conga"); + afi("cor", GM_FRENCH_HORN, "horn"); + afi("cornm", GM_BAGPIPE, "French bagpipe"); + afi("corno", GM_TRUMPET, "cornett"); + afi("cornt", GM_TRUMPET, "cornet"); + afi("coro", GM_CHOIR_AAHS, "chorus"); + afi("crshc", GM_REVERSE_CYMBAL, "crash cymbal"); + afi("ctenor", GM_CHOIR_AAHS, "counter-tenor"); + afi("ctina", GM_ACCORDION, "concertina"); + afi("drmsp", GM_FLUTE, "dramatic soprano"); + afi("drum", GM_SYNTH_DRUM, "drum"); + afi("drumP", GM_SYNTH_DRUM, "small drum"); + afi("dulc", GM_DULCIMER, "dulcimer"); + afi("eguit", GM_ELECTRIC_GUITAR_CLEAN, "electric guitar"); + afi("fag_c", GM_BASSOON, "contrabassoon"); + afi("fagot", GM_BASSOON, "bassoon"); + afi("false", GM_RECORDER, "falsetto"); + afi("fdrum", GM_TAIKO_DRUM, "frame drum"); + afi("feme", GM_CHOIR_AAHS, "female voice"); + afi("fife", GM_BLOWN_BOTTLE, "fife"); + afi("fingc", GM_REVERSE_CYMBAL, "finger cymbal"); + afi("flt", GM_FLUTE, "flute"); + afi("flt_a", GM_FLUTE, "alto flute"); + afi("flt_b", GM_FLUTE, "bass flute"); + afi("fltda", GM_RECORDER, "alto recorder"); + afi("fltdb", GM_RECORDER, "bass recorder"); + afi("fltdn", GM_RECORDER, "sopranino recorder"); + afi("fltds", GM_RECORDER, "soprano recorder"); + afi("fltdt", GM_RECORDER, "tenor recorder"); + afi("flugh", GM_FRENCH_HORN, "flugelhorn"); + afi("forte", GM_HONKYTONK_PIANO, "fortepiano"); + afi("gen", GM_ACOUSTIC_GRAND_PIANO, "generic instrument"); + afi("genB", GM_ACOUSTIC_GRAND_PIANO, "generic bass instrument"); + afi("genT", GM_ACOUSTIC_GRAND_PIANO, "generic treble instrument"); + afi("glock", GM_GLOCKENSPIEL, "glockenspiel"); + afi("gong", GM_REVERSE_CYMBAL, "gong"); + afi("guitr", GM_ACOUSTIC_GUITAR_NYLON, "guitar"); + afi("hammd", GM_DRAWBAR_ORGAN, "Hammond electronic organ"); + afi("hbell", GM_TINKLE_BELL, "handbell"); + afi("hbell", GM_TINKLE_BELL, "handbell"); + afi("heck", GM_BASSOON, "heckelphone"); + afi("heltn", GM_CHOIR_AAHS, "Heldentenor"); + afi("hichi", GM_OBOE, "hichiriki"); + afi("hurdy", GM_LEAD_CALLIOPE, "hurdy-gurdy"); + afi("kitv", GM_VIOLIN, "kit violin"); + afi("klav", GM_ACOUSTIC_GRAND_PIANO, "keyboard"); + afi("kokyu", GM_FIDDLE, "kokyu"); + afi("komun", GM_KOTO, "komun'go"); + afi("koto", GM_KOTO, "koto"); + afi("kruma", GM_TRUMPET, "alto crumhorn"); + afi("krumb", GM_TRUMPET, "bass crumhorn"); + afi("krums", GM_TRUMPET, "soprano crumhorn"); + afi("krumt", GM_TRUMPET, "tenor crumhorn"); + afi("lion", GM_AGOGO, "lion's roar"); + afi("liuto", GM_ACOUSTIC_GUITAR_NYLON, "lute"); + afi("lyrsp", GM_FLUTE, "lyric soprano"); + afi("lyrtn", GM_FRENCH_HORN, "lyric tenor"); + afi("male", GM_CHOIR_AAHS, "male voice"); + afi("mando", GM_ACOUSTIC_GUITAR_NYLON, "mandolin"); + afi("marac", GM_AGOGO, "maracas"); + afi("marim", GM_MARIMBA, "marimba"); + afi("mbari", GM_CHOIR_AAHS, "high baritone"); + afi("mezzo", GM_CHOIR_AAHS, "mezzo soprano"); + afi("nfant", GM_CHOIR_AAHS, "child's voice"); + afi("nokan", GM_SHAKUHACHI, "nokan"); + afi("oboe", GM_OBOE, "oboe"); + afi("oboeD", GM_ENGLISH_HORN, "oboe d'amore"); + afi("ocari", GM_OCARINA, "ocarina"); + afi("ondes", GM_PAD_SWEEP, "ondes Martenot"); + afi("ophic", GM_TUBA, "ophicleide"); + afi("organ", GM_CHURCH_ORGAN, "pipe organ"); + afi("oud", GM_ACOUSTIC_GUITAR_NYLON, "oud"); + afi("paila", GM_AGOGO, "timbales"); + afi("panpi", GM_PAN_FLUTE, "panpipe"); + afi("pbell", GM_TUBULAR_BELLS, "bell plate"); + afi("pguit", GM_ACOUSTIC_GUITAR_NYLON, "Portuguese guitar"); + afi("physh", GM_REED_ORGAN, "physharmonica"); + afi("piano", GM_ACOUSTIC_GRAND_PIANO, "pianoforte"); + afi("piatt", GM_REVERSE_CYMBAL, "cymbals"); + afi("picco", GM_PICCOLO, "piccolo"); + afi("pipa", GM_ACOUSTIC_GUITAR_NYLON, "Chinese lute"); + afi("porta", GM_TANGO_ACCORDION, "portative organ"); + afi("psalt", GM_CLAVI, "psaltery"); + afi("qin", GM_CLAVI, "qin"); + afi("quinto", GM_CHOIR_AAHS, "quinto"); + afi("quitr", GM_ACOUSTIC_GUITAR_NYLON, "gittern"); + afi("rackt", GM_TRUMPET, "racket"); + afi("ratl", GM_WOODBLOCKS, "rattle"); + afi("rebec", GM_ACOUSTIC_GUITAR_NYLON, "rebec"); + afi("recit", GM_CHOIR_AAHS, "recitativo"); + afi("reedo", GM_REED_ORGAN, "reed organ"); + afi("rhode", GM_ELECTRIC_PIANO_1, "Fender-Rhodes electric piano"); + afi("ridec", GM_REVERSE_CYMBAL, "ride cymbal"); + afi("sarod", GM_SITAR, "sarod"); + afi("sarus", GM_TUBA, "sarrusophone"); + afi("saxA", GM_ALTO_SAX, "alto saxophone"); + afi("saxB", GM_BARITONE_SAX, "bass saxophone"); + afi("saxC", GM_BARITONE_SAX, "contrabass saxophone"); + afi("saxN", GM_SOPRANO_SAX, "sopranino saxophone"); + afi("saxR", GM_BARITONE_SAX, "baritone saxophone"); + afi("saxS", GM_SOPRANO_SAX, "soprano saxophone"); + afi("saxT", GM_TENOR_SAX, "tenor saxophone"); + afi("sbell", GM_TINKLE_BELL, "sleigh bells"); + afi("sdrum", GM_SYNTH_DRUM, "snare drum (kit)"); + afi("shaku", GM_SHAKUHACHI, "shakuhachi"); + afi("shami", GM_SHAMISEN, "shamisen"); + afi("sheng", GM_SHANAI, "sheng"); + afi("sho", GM_SHANAI, "sho"); + afi("siren", GM_FX_SCI_FI, "siren"); + afi("sitar", GM_SITAR, "sitar"); + afi("slap", GM_GUNSHOT, "slapstick"); + afi("soprn", GM_CHOIR_AAHS, "soprano"); + afi("spshc", GM_REVERSE_CYMBAL, "splash cymbal"); + afi("steel", GM_STEEL_DRUMS, "steel-drum"); + afi("stim", GM_SEASHORE, "Sprechstimme"); + afi("stimA", GM_SEASHORE, "Sprechstimme, alto"); + afi("stimB", GM_SEASHORE, "Sprechstimme, bass"); + afi("stimC", GM_SEASHORE, "Sprechstimme, contralto"); + afi("stimR", GM_SEASHORE, "Sprechstimme, baritone"); + afi("stimS", GM_SEASHORE, "Sprechstimme, soprano"); + afi("strdr", GM_AGOGO, "string drum"); + afi("sxhA", GM_ALTO_SAX, "alto saxhorn"); + afi("sxhB", GM_BARITONE_SAX, "bass saxhorn"); + afi("sxhC", GM_BARITONE_SAX, "contrabass saxhorn"); + afi("sxhR", GM_BARITONE_SAX, "baritone saxhorn"); + afi("sxhS", GM_SOPRANO_SAX, "soprano saxhorn"); + afi("sxhT", GM_TENOR_SAX, "tenor saxhorn"); + afi("synth", GM_ELECTRIC_PIANO_2, "keyboard synthesizer"); + afi("tabla", GM_MELODIC_DRUM, "tabla"); + afi("tambn", GM_TINKLE_BELL, "tambourine"); + afi("tambu", GM_MELODIC_DRUM, "tambura"); + afi("tanbr", GM_MELODIC_DRUM, "tanbur"); + afi("tblok", GM_WOODBLOCKS, "temple blocks"); + afi("tdrum", GM_SYNTH_DRUM, "tenor drum"); + afi("tenor", GM_CHOIR_AAHS, "tenor"); + afi("timpa", GM_MELODIC_DRUM, "timpani"); + afi("tiorb", GM_ACOUSTIC_GUITAR_NYLON, "theorbo"); + afi("tom", GM_TAIKO_DRUM, "tom-tom drum"); + afi("trngl", GM_TINKLE_BELL, "triangle"); + afi("tromb", GM_TROMBONE, "bass trombone"); + afi("tromp", GM_TRUMPET, "trumpet"); + afi("tromt", GM_TROMBONE, "tenor trombone"); + afi("tuba", GM_TUBA, "tuba"); + afi("tubaB", GM_TUBA, "bass tuba"); + afi("tubaC", GM_TUBA, "contrabass tuba"); + afi("tubaT", GM_TUBA, "tenor tuba"); + afi("tubaU", GM_TUBA, "subcontra tuba"); + afi("ukule", GM_ACOUSTIC_GUITAR_NYLON, "ukulele"); + afi("vibra", GM_VIBRAPHONE, "vibraphone"); + afi("vina", GM_SITAR, "vina"); + afi("viola", GM_VIOLA, "viola"); + afi("violb", GM_CONTRABASS, "bass viola da gamba"); + afi("viold", GM_VIOLA, "viola d'amore"); + afi("violn", GM_VIOLIN, "violin"); + afi("violp", GM_VIOLIN, "piccolo violin"); + afi("viols", GM_VIOLIN, "treble viola da gamba"); + afi("violt", GM_CELLO, "tenor viola da gamba"); + afi("vox", GM_CHOIR_AAHS, "generic voice"); + afi("wblok", GM_WOODBLOCKS, "woodblock"); + afi("xylo", GM_XYLOPHONE, "xylophone"); + afi("zithr", GM_CLAVI, "zither"); + afi("zurna", GM_ACOUSTIC_GUITAR_NYLON, "zurna"); + } @@ -14867,7 +14919,7 @@ void HumInstrument::afi(const char* humdrum_name, int midinum, x.humdrum = humdrum_name; x.gm = midinum; - data.push_back(x); + m_data.push_back(x); } @@ -14884,14 +14936,14 @@ int HumInstrument::find(const string& Hname) { key.name = ""; key.gm = 0; - searchResult = bsearch(&key, data.data(), - data.size(), sizeof(_HumInstrument), + searchResult = bsearch(&key, m_data.data(), + m_data.size(), sizeof(_HumInstrument), &data_compare_by_humdrum_name); if (searchResult == NULL) { return -1; } else { - return (int)(((TEMP64BITFIX)(searchResult)) - ((TEMP64BITFIX)(data.data())))/ + return (int)(((TEMP64BITFIX)(searchResult)) - ((TEMP64BITFIX)(m_data.data())))/ sizeof(_HumInstrument); } } @@ -14917,7 +14969,7 @@ int HumInstrument::data_compare_by_humdrum_name(const void* a, // void HumInstrument::sortData(void) { - qsort(data.data(), data.size(), sizeof(_HumInstrument), + qsort(m_data.data(), m_data.size(), sizeof(_HumInstrument), &HumInstrument::data_compare_by_humdrum_name); } @@ -20488,24 +20540,16 @@ bool HumdrumFileBase::read(const char* filename) { bool HumdrumFileBase::read(istream& contents) { - clear(); - m_displayError = true; - char buffer[123123] = {0}; - HLp s; - while (contents.getline(buffer, sizeof(buffer), '\n')) { - s = new HumdrumLine(buffer); - s->setOwner(this); - m_lines.push_back(s); - } - return analyzeBaseFromLines(); -/* - if (!analyzeTokens()) { return isValid(); } - if (!analyzeLines() ) { return isValid(); } - if (!analyzeSpines()) { return isValid(); } - if (!analyzeLinks() ) { return isValid(); } - if (!analyzeTracks()) { return isValid(); } - return isValid(); -*/ + clear(); + m_displayError = true; + std::string buffer; + HLp s; + while (std::getline(contents, buffer)) { + s = new HumdrumLine(buffer); + s->setOwner(this); + m_lines.push_back(s); + } + return analyzeBaseFromLines(); } @@ -20569,6 +20613,39 @@ bool HumdrumFileBase::analyzeBaseFromLines(void) { +////////////////////////////// +// +// HumdrumFileBase::setFilenameFromSegment -- Update filename based on any +// !!!!SEGMENT: line at the top of the file. +// + +void HumdrumFileBase::setFilenameFromSegment(void) { + HumdrumFileBase& infile = *this; + for (int i=0; i 0) && - (m_curfile < (int)m_filelist.size()-1)) { + (m_curfile < (int)m_filelist.size()-1)) { m_curfile++; if (m_instream.is_open()) { m_instream.close(); } - if (strstr(m_filelist[m_curfile].c_str(), "://") != NULL) { + if (m_filelist[m_curfile].find("://") != string::npos) { // The next file to read is a URL/URI, so buffer the // data from the internet and start reading that instead // of reading from a file on the hard disk. - fillUrlBuffer(m_urlbuffer, m_filelist[m_curfile].c_str()); - infile.setFilename(m_filelist[m_curfile].c_str()); + fillUrlBuffer(m_urlbuffer, m_filelist[m_curfile]); + infile.setFilename(m_filelist[m_curfile]); goto restarting; } - m_instream.open(m_filelist[m_curfile].c_str()); - infile.setFilename(m_filelist[m_curfile].c_str()); + m_instream.open(m_filelist[m_curfile]); + infile.setFilename(m_filelist[m_curfile]); if (!m_instream.is_open()) { // file does not exist or cannot be opened close // the file and try luck with next file in the list @@ -27671,17 +27755,16 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { if (m_newfilebuffer.size() > 0) { // store the filename for the current HumdrumFile being read: HumRegex hre; - if (hre.search(m_newfilebuffer, - R"(^!!!!SEGMENT\s*([+-]?\d+)?\s*:\s*(.*)\s*$)")) { + if (hre.search(m_newfilebuffer, R"(^!!!!SEGMENT\s*([+-]?\d+)?\s*:\s*(.*)\s*$)")) { if (hre.getMatchLength(1) > 0) { - infile.setSegmentLevel(atoi(hre.getMatch(1).c_str())); + infile.setSegmentLevel(hre.getMatchInt(1)); } else { infile.setSegmentLevel(0); } infile.setFilename(hre.getMatch(2)); } else if ((m_curfile >=0) && (m_curfile < (int)m_filelist.size()) - && (m_filelist.size() > 0)) { - infile.setFilename(m_filelist[m_curfile].c_str()); + && (m_filelist.size() > 0)) { + infile.setFilename(m_filelist[m_curfile]); } else { // reading from standard input, but no name. } @@ -27692,7 +27775,6 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { return 0; } - stringstream buffer; int foundUniversalQ = 0; // Start reading the input stream. If !!!!SEGMENT: universal comment @@ -27700,15 +27782,13 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { // newly read HumdrumFile. If other universal comments are found, then // overwrite the old universal comments here. - int addedFilename = 0; - //int searchName = 0; + // int addedFilename = 0; int dataFoundQ = 0; int starstarFoundQ = 0; int starminusFoundQ = 0; if (m_newfilebuffer.size() < 4) { //searchName = 1; } - char templine[123123] = {0}; if (newinput->eof()) { if (m_curfile < (int)m_filelist.size()-1) { @@ -27723,85 +27803,49 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { // if the previous line from the last read starts with "**" // then treat it as part of the current file. - if ((m_newfilebuffer.size() > 1) && - (strncmp(m_newfilebuffer.c_str(), "**", 2)) == 0) { + if ((m_newfilebuffer.size() > 1) && (m_newfilebuffer.compare(0, 2, "**") == 0)) { buffer << m_newfilebuffer << "\n"; m_newfilebuffer = ""; starstarFoundQ = 1; } while (!input.eof()) { - input.getline(templine, 123123, '\n'); - if ((!dataFoundQ) && - (strncmp(templine, "!!!!SEGMENT", strlen("!!!!SEGMENT")) == 0)) { - string tempstring; - tempstring = templine; - HumRegex hre; - if (hre.search(tempstring, - "^!!!!SEGMENT\\s*([+-]?\\d+)?\\s*:\\s*(.*)\\s*$")) { - if (hre.getMatchLength(1) > 0) { - infile.setSegmentLevel(atoi(hre.getMatch(1).c_str())); - } else { - infile.setSegmentLevel(0); - } - infile.setFilename(hre.getMatch(2)); + getline(input, templine); + if (templine.compare(0, strlen("!!!!SEGMENT"), "!!!!SEGMENT") == 0) { + // Store the current segment line in the buffer before breaking. + if (!buffer.str().empty()) { + m_newfilebuffer = templine; + break; } + m_newfilebuffer = templine; } - if (strncmp(templine, "**", 2) == 0) { + if (templine.compare(0, 2, "**") == 0) { if (starstarFoundQ == 1) { m_newfilebuffer = templine; // already found a **, so this one is defined as a file // segment. Exit from the loop and process the previous - // content, waiting until the next read for to start with + // content, waiting until the next read to start with // this line. break; } starstarFoundQ = 1; } - if (input.eof() && (strcmp(templine, "") == 0)) { + if (input.eof() && templine.empty()) { // No more data coming from current stream, so this is // the end of the HumdrumFile. Break from the while loop // and then store the read contents of the stream in the // HumdrumFile. break; } - // (1) Does the line start with "!!!!SEGMENT"? If so, then - // this is either the name of the current or next file to process. - // (1a) this is the name of the current file to process if no - // data has yet been found, - // (1b) or a name is being actively searched for. - if (strncmp(templine, "!!!!SEGMENT", strlen("!!!!SEGMENT")) == 0) { - m_newfilebuffer = templine; - if (dataFoundQ) { - // this new filename is for the next chunk to process in the - // current file stream, not this one, so stop reading the - // HumdrumFile content and send what has already been read back - // out with new contents. - } else { - // !!!!SEGMENT: came before any real data was read, so - // it is most likely the name of the current file - // (i.e., it comes at the start of the file stream and - // is the name of the first HumdrumFile in the stream). - HumRegex hre; - if (hre.search(m_newfilebuffer, - R"(^!!!!SEGMENT\s*([+-]?\d+)?\s:\s*(.*)\s*$)")) { - if (hre.getMatchLength(1) > 0) { - infile.setSegmentLevel(atoi(hre.getMatch(1).c_str())); - } else { - infile.setSegmentLevel(0); - } - infile.setFilename(hre.getMatch(2)); - } - } - } - int len = (int)strlen(templine); - if ((len > 4) && (strncmp(templine, "!!!!", 4) == 0) && - (templine[4] != '!') && - (dataFoundQ == 0) && - (strncmp(templine, "!!!!filter:", strlen("!!!!filter:")) != 0) && - (strncmp(templine, "!!!!SEGMENT:", strlen("!!!!SEGMENT:")) != 0)) { + + int len = templine.length(); + if ((len > 4) && (templine.compare(0, 4, "!!!!") == 0) && + (templine[4] != '!') && + (dataFoundQ == 0) && + (templine.compare(0, strlen("!!!!filter:"), "!!!!filter:") != 0) && + (templine.compare(0, strlen("!!!!SEGMENT:"), "!!!!SEGMENT:") != 0)) { // This is a universal comment. Should it be appended // to the list or should the current list be erased and // this record placed into the first entry? @@ -27819,39 +27863,33 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { continue; } - if (strncmp(templine, "*-", 2) == 0) { + if (templine.compare(0, 2, "*-") == 0) { starminusFoundQ = 1; } - // if before first ** in a data file or after *-, and the line - // does not start with '!' or '*', then assume that it is a file - // name which should be added to the file list to read. - if (((starminusFoundQ == 1) || (starstarFoundQ == 0)) - && (templine[0] != '*') && (templine[0] != '!')) { - if ((templine[0] != '\0') && (templine[0] != ' ')) { - // The file can only be added once in this manner - // so that infinite loops are prevented. + if (((starminusFoundQ == 1) || (starstarFoundQ == 0)) && (templine[0] != '*') && (templine[0] != '!')) { + if ((!templine.empty()) && (templine[0] != ' ')) { int found = 0; - for (int mm=0; mm<(int)m_filelist.size(); mm++) { - if (strcmp(m_filelist[mm].c_str(), templine) == 0) { + for (int mm = 0; mm < (int)m_filelist.size(); mm++) { + if (m_filelist[mm] == templine) { found = 1; } } if (!found) { m_filelist.push_back(templine); - addedFilename = 1; + // addedFilename = 1; } continue; } } dataFoundQ = 1; // found something other than universal comments - // should empty lines be treated somewhat as universal comments? // store the data line for later parsing into HumdrumFile record: buffer << templine << "\n"; } +/* if (dataFoundQ == 0) { // never found anything for some strange reason. if (addedFilename) { @@ -27859,6 +27897,7 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { } return 0; } +*/ // Arriving here means that reading of the data stream is complete. // The string stream variable "buffer" contains the HumdrumFile @@ -27870,29 +27909,33 @@ int HumdrumFileStream::getFile(HumdrumFile& infile) { contents.str(""); // empty any contents in buffer contents.clear(); // reset error flags in buffer - for (int i=0; i<(int)m_universals.size(); i++) { - // Convert universals reference records to globals, but do not demote !!!!filter: + for (int i=0; i < (int)m_universals.size(); i++) { if (m_universals[i].compare(0, 11, "!!!!filter:") == 0) { continue; } contents << &(m_universals[i][1]) << "\n"; } + contents << buffer.str(); - string filename = infile.getFilename(); + string oldfilename = infile.getFilename(); infile.readNoRhythm(contents); - if (!filename.empty()) { - infile.setFilename(filename); + string newfilename = infile.getFilename(); + if (newfilename.empty() && !oldfilename.empty()) { + infile.setFilename(oldfilename); } + infile.setFilenameFromSegment(); + return 1; } + + ////////////////////////////// // // HumdrumFileStream::fillUrlBuffer -- // - void HumdrumFileStream::fillUrlBuffer(stringstream& uribuffer, const string& uriname) { #ifdef USING_URI @@ -31327,7 +31370,7 @@ int HumdrumLine::createTokensFromLine(void) { token = new HumdrumToken(); token->setOwner(this); m_tokens.push_back(token); - m_tokens.push_back(0); + m_tabs.push_back(0); } else if (this->compare(0, 2, "!!") == 0) { token = new HumdrumToken(this->c_str()); token->setOwner(this); @@ -66283,13 +66326,14 @@ void Tool_composite::addCoincidenceMarks(HumdrumFile& infile) { if (token->isNull()) { continue; } - if (token->isRest()) { - continue; - } - if (token->isNoteAttack()) { - string text = *token; - text += m_coinMark; - token->setText(text); + for (int i=0; igetSubtokenCount(); i++) { + string subtok = token->getSubtoken(i); + if (subtok.find("r") != string::npos) { + continue; + } + subtok += m_coinMark; + token->replaceSubtoken(i, subtok); + // Maybe highlight only if note attack? } } } @@ -66689,6 +66733,11 @@ void Tool_composite::getAnalysisOutputLine(ostream& output, HumdrumFile& infile, tempout << "/"; } } + if (m_coinMarkQ) { + if (value.find("R") != string::npos) { + tempout << m_coinMark; + } + } if (processedQ) { tempout << "\t"; } @@ -68965,7 +69014,7 @@ void Tool_composite::addMeterSignatureChanges(HumdrumFile& output, HumdrumFile& ////////////////////////////// // -// adjustBadCoincidenceRests -- Sometimes coincidence rests are not so great, particularly +// adjustBadCoincidenceRests -- Sometimes coincidence rests are not so great, particularly // when they are long and there is a small note that will add to it to fill in a measure // (such as a 5 eighth-note rest in 6/8). Try to simplify such case in this function // (more can be added on a case-by-case basis). @@ -72909,7 +72958,7 @@ Tool_deg::Tool_deg(void) { define("kern=b", "prefix composite rhythm **kern spine with -I option"); define("k|kern-tracks=s", "process only the specified kern spines"); define("kd|dk|key-default|default-key=s", "default (initial) key if none specified in data"); - define("kf|fk|key-force|force-key=s", "use the given key for analysing deg data (ignore modulations)"); + define("kf|fk|key-force|force-key|forced-key=s", "use the given key for analysing deg data (ignore modulations)"); define("o|octave|octaves|degree=b", "encode octave information int **degree spines"); define("r|recip=b", "prefix output data with **recip spine with -I option"); define("t|ties=b", "include scale degrees for tied notes"); @@ -78192,17 +78241,10 @@ void Tool_double::doubleRhythms(HumdrumFile& infile) { // Tool_esac2hum::Tool_esac2hum(void) { - define("debug=b", "print debug information"); - define("v|verbose=b", "verbose output"); - define("h|header=s:", "header filename for placement in output"); - define("t|trailer=s:", "trailer filename for placement in output"); - define("s|split=s:file", "split song info into separate files"); - define("x|extension=s:.krn", "split filename extension"); - define("f|first=i:1", "number of first split filename"); - define("author=b", "author of program"); - define("version=b", "compilation info"); - define("example=b", "example usages"); - define("help=b", "short description"); + define("debug=b", "Print debugging statements"); + define("v|verbose=s", "Print verbose messages"); + define("e|embed-esac=b", "Eembed EsAC data in output"); + define("a|analyses|analysis=b", "Generate EsAC analysis fields"); } @@ -78214,13 +78256,12 @@ Tool_esac2hum::Tool_esac2hum(void) { // bool Tool_esac2hum::convertFile(ostream& out, const string& filename) { - ifstream file(filename); - stringstream s; - if (file) { - s << file.rdbuf(); - file.close(); - } - return convert(out, s.str()); + initialize(); + ifstream file(filename); + if (file) { + return convert(out, file); + } + return false; } @@ -78238,87 +78279,57 @@ bool Tool_esac2hum::convert(ostream& out, const string& input) { } - - ////////////////////////////// // // Tool_esac2hum::initialize -- // -bool Tool_esac2hum::initialize(void) { - // handle basic options: - if (getBoolean("author")) { - cerr << "Written by Craig Stuart Sapp, " - << "craig@ccrma.stanford.edu, March 2002" << endl; - return false; - } else if (getBoolean("version")) { - cerr << getCommand() << ", version: 6 June 2017" << endl; - cerr << "compiled: " << __DATE__ << endl; - return false; - } else if (getBoolean("help")) { - usage(getCommand()); - return false; - } else if (getBoolean("example")) { - example(); - return false; - } - - debugQ = getBoolean("debug"); - verboseQ = getBoolean("verbose"); - - if (getBoolean("header")) { - if (!getFileContents(header, getString("header"))) { - return false; - } - } else { - header.resize(0); - } - if (getBoolean("trailer")) { - if (!getFileContents(trailer, getString("trailer"))) { - return false; - } - } else { - trailer.resize(0); - } - - if (getBoolean("split")) { - splitQ = 1; +void Tool_esac2hum::initialize(void) { + m_debugQ = getBoolean("debug"); // print debugging information + m_verboseQ = getBoolean("verbose"); // print input EsAC MEL[] data when true + m_verbose = getString("verbose"); // p = phrase, m=measure, n=note + m_embedEsacQ = getBoolean("embed-esac"); // don't print input EsAC data + m_analysisQ = getBoolean("analyses"); // embed analysis in EsAC data + if (m_analysisQ) { + m_embedEsacQ = true; } - namebase = getString("split"); - fileextension = getString("extension"); - firstfilenum = getInteger("first"); - return true; } -////////////////////////////////////////////////////////////////////////// - - ////////////////////////////// // // Tool_esac2hum::convertEsacToHumdrum -- // void Tool_esac2hum::convertEsacToHumdrum(ostream& output, istream& infile) { - initialize(); - vector song; - song.reserve(400); - int init = 0; - // int filecounter = firstfilenum; - string outfilename; - string numberstring; - // ofstream outfile; + m_inputline = 0; + m_prevline = ""; + + vector song; // contents of one EsAC song, extracted from input stream + song.reserve(1000); + while (!infile.eof()) { - if (debugQ) { + if (m_debugQ) { cerr << "Getting a song..." << endl; } - getSong(song, infile, init); - if (debugQ) { + bool status = getSong(song, infile); + if (!status) { + cerr << "Error getting a song" << endl; + continue; + } + if (m_debugQ) { cerr << "Got a song ..." << endl; } - init = 1; - convertSong(song, output); + if (song.empty()) { + cerr << "Song is empty" << endl; + continue; + } + if (song.size() < 4) { + cerr << "Song is too short" << endl; + continue; + } + convertSong(output, song); } } @@ -78326,49 +78337,127 @@ void Tool_esac2hum::convertEsacToHumdrum(ostream& output, istream& infile) { ////////////////////////////// // -// Tool_esac2hum::getSong -- get a song from the EsAC file +// Tool_esac2hum::getSong -- get a song from a multiple-song EsAC file. +// Search for a CUT[] line which indicates the first line of the data. +// There will/can be some text above the CUT[] line. The CUT[] field +// may contain newlnes, so searching only for CUT[ to also handle these +// cases. // -bool Tool_esac2hum::getSong(vector& song, istream& infile, int init) { - string holdbuffer; +bool Tool_esac2hum::getSong(vector& song, istream& infile) { song.resize(0); - if (init) { - // do nothing holdbuffer has the CUT[] information - } else { - while (!infile.eof() && holdbuffer.compare(0, 4, "CUT[") != 0) { - getline(infile, holdbuffer); - if (verboseQ) { - cerr << "Contents: " << holdbuffer << endl; + m_globalComments.clear(); + + HumRegex hre; + string buffer; + + // First find the next CUT[] line in the input which indcates + // the start of a song. There typically is a non-empty line just above CUT[] + // containing information about the collection. + if (m_cutline.empty()) { + while (!infile.eof()) { + getline(infile, buffer); + + if (hre.search(buffer, "^[!#]{2,}")) { + hre.search(buffer, "^([!#]{2,})(.*)$"); + string prefix = hre.getMatch(1); + string postfix = hre.getMatch(2); + hre.replaceDestructive(prefix, "!", "#", "g"); + string comment = prefix + postfix; + m_globalComments.push_back(comment); + continue; } - if (holdbuffer.compare(0, 2, "!!") == 0) { - song.push_back(holdbuffer); + + cleanText(buffer); + m_inputline++; + if (buffer.compare(0, 4, "CUT[") == 0) { + m_cutline = buffer; + break; + } else { + m_prevline = buffer; + continue; } } - if (infile.eof()) { - return false; - } } - if (!infile.eof()) { - song.push_back(holdbuffer); - } else { + if (m_cutline.empty()) { return false; } - getline(infile, holdbuffer); - chopExtraInfo(holdbuffer); - inputline++; - if (verboseQ) { - cerr << "READ LINE: " << holdbuffer << endl; + if (infile.eof()) { + return false; } - while (!infile.eof() && (holdbuffer.compare(0, 4, "CUT[", 4) != 0)) { - song.push_back(holdbuffer); - getline(infile, holdbuffer); - chopExtraInfo(holdbuffer); - inputline++; - if (verboseQ) { - cerr << "READ ANOTHER LINE: " << holdbuffer << endl; + + if (!hre.search(m_prevline, "^\\s*$")) { + song.push_back(m_prevline); + } + song.push_back(m_cutline); + + m_prevline.clear(); + m_cutline.clear(); + + bool expectingCloseQ = false; + + while (!infile.eof()) { + getline(infile, buffer); + + if (hre.search(buffer, "^#{2,}")) { + hre.search(buffer, "^(#{2,})(.*)$"); + string prefix = hre.getMatch(1); + string postfix = hre.getMatch(2); + hre.replaceDestructive(prefix, "!", "#", "g"); + string comment = prefix + postfix; + m_globalComments.push_back(comment); + continue; + } + + cleanText(buffer); + m_inputline++; + if (m_debugQ) { + cerr << "READ LINE: " << buffer << endl; + } + if (expectingCloseQ) { + if (buffer.find("[") != string::npos) { + cerr << "Strange error on line " << m_inputline << ": " << buffer << endl; + continue; + } else if (!hre.search(buffer, "[\\[\\]]")) { + // intermediate parameter line (not starting or ending) + song.push_back(buffer); + continue; + } + + if (hre.search(buffer, "^[^\\]]*\\]\\s*$")) { + // closing bracket + expectingCloseQ = 0; + song.push_back(buffer); + continue; + } else { + cerr << "STRANGE CASE HERE " << buffer << endl; + } + continue; + } + + if (hre.search(buffer, "^\\s*$")) { + continue; + } + + if (hre.search(buffer, "^[A-Za-z][^\\[\\]]*$")) { + // collection line + m_prevline = buffer; + continue; + } + + if (hre.search(buffer, "^[A-Za-z]+\\s*\\[[^\\]]*\\s*$")) { + // parameter with opening [ + expectingCloseQ = true; + } else { } + + song.push_back(buffer); + } + + if (expectingCloseQ) { + cerr << "Strange case: expecting closing of a song parameter around line " << m_inputline++ << endl; } return true; @@ -78378,945 +78467,1244 @@ bool Tool_esac2hum::getSong(vector& song, istream& infile, int init) { ////////////////////////////// // -// Tool_esac2hum::chopExtraInfo -- remove phrase number information from Luxembourg data. +// Tool_esac2hum::cleanText -- remove \x88 and \x98 bytes from string (should not affect UTF-8 encodings) +// since those bytes do not seem to be involved with any UTF-8 characters. // -void Tool_esac2hum::chopExtraInfo(string& buffer) { +void Tool_esac2hum::cleanText(std::string& buffer) { HumRegex hre; - hre.replaceDestructive(buffer, "", "^\\s+"); - hre.replaceDestructive(buffer, "", "\\s+$"); + + // Fix UTF-8 double encodings (related to editing with Windows-1252 or ISO-8859-2 programs): + + // Ą: c3 84 c2 84 - c4 84 + hre.replaceDestructive(buffer, "\xc4\x84", "\xc3\x84\xc2\x84", "g"); + + // ą: c3 84 c2 85 - c4 85 + hre.replaceDestructive(buffer, "\xc4\x85", "\xc3\x84\xc2\x85", "g"); + + // Ć: c3 84 c2 86 -> c4 86 + hre.replaceDestructive(buffer, "\xc4\x86", "\xc3\x84\xc2\x86", "g"); + + // ć: c3 84 c2 87 -> c4 87 + hre.replaceDestructive(buffer, "\xc4\x87", "\xc3\x84\xc2\x87", "g"); + + // Ę: c3 84 c2 98 -> c4 98 + hre.replaceDestructive(buffer, "\xc4\x98", "\xc3\x84\xc2\x98", "g"); + + // ę: c3 84 c2 99 -> c4 99 + hre.replaceDestructive(buffer, "\xc4\x99", "\xc3\x84\xc2\x99", "g"); + + // Ł: c4 b9 c2 81 -> c5 81 + hre.replaceDestructive(buffer, "\xc5\x81", "\xc4\xb9\xc2\x81", "g"); + + // ł: c4 b9 c2 82 -> c5 82 + hre.replaceDestructive(buffer, "\xc5\x82", "\xc4\xb9\xc2\x82", "g"); + + // Ń: c4 b9 c2 83 -> c5 83 + hre.replaceDestructive(buffer, "\xc5\x83", "\xc4\xb9\xc2\x83", "g"); + + // ń: c4 b9 c2 84 -> c5 84 + hre.replaceDestructive(buffer, "\xc5\x84", "\xc4\xb9\xc2\x84", "g"); + + // Ó: c4 82 c5 93 -> c3 93 (note: not sequential with ó) + hre.replaceDestructive(buffer, "\xc3\x93", "\xc4\x82\xc5\x93", "g"); + + // ó: c4 82 c5 82 -> c3 b3 (note: not sequential with Ó) + hre.replaceDestructive(buffer, "\xc3\xb3", "\xc4\x82\xc5\x82", "g"); + + // Ś: c4 b9 c2 9a -> c5 9a + hre.replaceDestructive(buffer, "\xc5\x9a", "\xc4\xb9\xc2\x9b", "g"); + + // ś: c4 b9 c2 9b -> c5 9b + hre.replaceDestructive(buffer, "\xc5\x9b", "\xc4\xb9\xc2\x9b", "g"); + + // Ź: c4 b9 c5 9a -> c5 b9 + hre.replaceDestructive(buffer, "\xc5\xb9", "\xc4\xb9\xc5\x9a", "g"); + + // ź: c4 b9 c5 9f -> c5 ba + hre.replaceDestructive(buffer, "\xc5\xba", "\xc4\xb9\xc5\x9f", "g"); + + // Ż: c4 b9 c5 a5 -> c5 bb + hre.replaceDestructive(buffer, "\xc5\xbb", "\xc4\xb9\xc5\xa5", "g"); + + // ż: c4 b9 c5 ba -> c5 bc + hre.replaceDestructive(buffer, "\xc5\xbc", "\xc4\xb9\xc5\xba", "g"); + + + // Random leftover characters from some character conversion: + hre.replaceDestructive(buffer, "", "[\x88\x98]", "g"); + + // Remove MS-DOS newline character at ends of lines: + if (!buffer.empty()) { + if (buffer.back() == 0x0d) { + // windows newline piece + buffer.resize(buffer.size() - 1); + } + } + // In VHV, when saving content to the local computer in EsAC mode, the 0x0d character should be added back. } ////////////////////////////// // -// Tool_esac2hum::printHumdrumHeaderInfo -- +// Tool_esac2hum::trimSpaces -- remove any trailing or leading spaces. // -void Tool_esac2hum::printHumdrumHeaderInfo(ostream& out, vector& song) { - for (int i=0; i<(int)song.size(); i++) { - if (song[i].size() == 0) { - continue; - } - if (song[i].compare(0, 2, "!!") == 0) { - out << song[i] << "\n"; - continue; - } - if ((song[i][0] == ' ') || (song[i][0] == '\t')) { - continue; - } - break; - } +string Tool_esac2hum::trimSpaces(const string& input) { + string output = input; + HumRegex hre; + hre.replaceDestructive(output, "", "^\\s+"); + hre.replaceDestructive(output, "", "\\s+$"); + return output; } ////////////////////////////// // -// Tool_esac2hum::printHumdrumFooterInfo -- +// Tool_esac2hum::convertSong -- // -void Tool_esac2hum::printHumdrumFooterInfo(ostream& out, vector& song) { - int i = 0; - for (i=0; i<(int)song.size(); i++) { - if (song[i].size() == 0) { - continue; - } - if (song[i].compare(0, 2, "!!") == 0) { - continue; - } - if ((song[i][0] == ' ') || (song[i][0] == '\t')) { - continue; - } - break; - } - int j = i; - for (j=i; j<(int)song.size(); j++) { - if (song[j].compare(0, 2, "!!") == 0) { - out << song[j] << "\n"; - } - } +void Tool_esac2hum::convertSong(ostream& output, vector& infile) { + getParameters(infile); + processSong(); + // printParameters(); + printHeader(output); + printScoreContents(output); + printFooter(output, infile); } ////////////////////////////// // -// Tool_esac2hum::convertSong -- +// Tool_esac2hum::processSong -- parse and preliminary conversion to Humdrum. // -void Tool_esac2hum::convertSong(vector& song, ostream& out) { +void Tool_esac2hum::processSong(void) { + string mel = m_score.m_params["MEL"]; + m_score.parseMel(mel); +} - int i; - if (verboseQ) { - for (i=0; i<(int)song.size(); i++) { - out << song[i] << "\n"; + + +////////////////////////////// +// +// Tool_esac2hum::printScoreContents -- +// + +void Tool_esac2hum::printScoreContents(ostream& output) { + + vector& errors = m_score.m_errors; + if (!errors.empty()) { + for (int z=0; z<(int)errors.size(); z++) { + output << "!!" << errors.at(z) << endl; } } - printHumdrumHeaderInfo(out, song); + if (!m_score.m_clef.empty()) { + output << m_score.m_clef << endl; + } + if (!m_score.m_keysignature.empty()) { + output << m_score.m_keysignature << endl; + } + if (!m_score.m_keydesignation.empty()) { + output << m_score.m_keydesignation << endl; + } + if (!m_score.m_timesig.empty()) { + output << m_score.m_timesig << endl; + } - string key; - double mindur = 1.0; - string meter; - int tonic = 0; - getKeyInfo(song, key, mindur, tonic, meter, out); + for (int i=0; i<(int)m_score.size(); i++) { + Tool_esac2hum::Phrase& phrase = m_score.at(i); + if (m_verbose.find("p") != string::npos) { + output << "!!esac-phrase: " << phrase.esac; + if (m_verbose.find("pi") != string::npos) { + output << " ["; + output << "ticks:" << phrase.m_ticks; + output << "]"; + } + vector& errors = phrase.m_errors; + if (!errors.empty()) { + for (int z=0; z<(int)errors.size(); z++) { + output << "!!" << errors.at(z) << endl; + } + } + output << endl; + } - vector songdata; - songdata.resize(0); - songdata.reserve(1000); - getNoteList(song, songdata, mindur, tonic); - placeLyrics(song, songdata); + for (int j=0; j<(int)phrase.size(); j++) { - vector numerator; - vector denominator; - getMeterInfo(meter, numerator, denominator); + Tool_esac2hum::Measure& measure = phrase.at(j); + if ((j == 0) && (i > 0)) { + output << "!!LO:LB:g=esac" << endl; + } + if (measure.m_barnum != 0) { // don't print barline if first is pickup + output << "="; + if (measure.m_barnum > 0) { + output << measure.m_barnum; + } else if (measure.m_barnum == -1) { + output << "-"; // "non-controlling" barline. + } else { + // visible barline, but not assigned a measure + // number (probably need more analysis to assign + // a measure number to this barline). + } + output << endl; + } + if (m_verbose.find("m") != string::npos) { + output << "!!esac-measure: " << measure.esac; + if (m_verbose.find("mi") != string::npos) { + output << " ["; + output << "ticks:" << measure.m_ticks; + if (measure.isComplete()) { + output << "; CM"; + } + if (measure.isPartialBegin()) { + output << "; PB"; + } + if (measure.isPartialEnd()) { + output << "; PE"; + } + if (measure.isUnassigned()) { + output << "; UN"; + } + output << "]"; + } + output << endl; + } + vector& errors = measure.m_errors; + if (!errors.empty()) { + for (int z=0; z<(int)errors.size(); z++) { + output << "!!" << errors.at(z) << endl; + } + } - postProcessSongData(songdata, numerator, denominator); + // print time signature change + if (!measure.m_measureTimeSignature.empty()) { + output << measure.m_measureTimeSignature << endl; + } - printTitleInfo(song, out); - out << "!!!id: " << key << "\n"; + for (int k=0; k<(int)measure.size(); k++) { - // check for presence of lyrics - int textQ = 0; - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].text != "") { - textQ = 1; - break; + Tool_esac2hum::Note& note = measure.at(k); + if (m_verbose.find("n") != string::npos) { + output << "!!esac-note: " << note.esac; + if (m_verbose.find("ni") != string::npos) { + output << " ["; + output << "ticks:" << note.m_ticks; + output << ", deg:" << note.m_degree; + output << ", alt:" << note.m_alter; + output << ", oct:" << note.m_octave; + output << "]"; + } + vector& errors = note.m_errors; + if (!errors.empty()) { + for (int z=0; z<(int)errors.size(); z++) { + output << "!!" << errors.at(z) << endl; + } + } + output << endl; + } + output << note.m_humdrum << endl; + + } } } - for (i=0; i<(int)header.size(); i++) { - out << header[i] << "\n"; + if (m_score.hasFinalBarline()) { + output << "==" << endl; + } else { + output << "=" << endl; } +} - out << "**kern"; - if (textQ) { - out << "\t**text"; - } - out << "\n"; - printKeyInfo(songdata, tonic, textQ, out); - for (i=0; i<(int)songdata.size(); i++) { - printNoteData(songdata[i], textQ, out); - } - out << "*-"; - if (textQ) { - out << "\t*-"; + +////////////////////////////// +// +// Tool_esac2hum::Score::parseMel -- +// + +bool Tool_esac2hum::Score::parseMel(const string& mel) { + clear(); + reserve(100); + + HumRegex hre; + if (hre.search(mel, "^\\s*$")) { + // no data; + cerr << "ERROR: MEL parameter is empty or non-existent" << endl; + return false; } - out << "\n"; - out << "!!!minrhy: "; - out << Convert::durationFloatToRecip(mindur)<<"\n"; - out << "!!!meter"; - if (numerator.size() > 1) { - out << "s"; + vector lines; + string line; + + stringstream linestream; + linestream << mel; + + int lineNumber = 0; + while (std::getline(linestream, line)) { + lineNumber++; + if (hre.search(line, "^\\s*$")) { + // Skip blank lines + continue; + } + string unknown = line; + hre.replaceDestructive(unknown, "", "[\\^0-9b\\s/._#()+-]+", "g"); + if (!unknown.empty()) { + cerr << "Unknown characters " << ">>" << unknown << "<< " << " on mel line " << lineNumber << ": " << line << endl; + } + line = Tool_esac2hum::trimSpaces(line); + lines.push_back(line); } - out << ": " << meter; - if ((meter == "frei") || (meter == "Frei")) { - out << " [unmetered]"; - } else if (meter.find('/') == string::npos) { - out << " interpreted as ["; - for (i=0; i<(int)numerator.size(); i++) { - out << numerator[i] << "/" << denominator[i]; - if (i < (int)numerator.size()-1) { - out << ", "; + + m_finalBarline = false; + for (int i=0; i<(int)lines.size(); i++) { + string line = lines[i]; + if (i == (int)lines.size() - 1) { + if (hre.search(line, "^(.*)\\s*//\\s*$")) { + m_finalBarline = true; + lines.back() = hre.getMatch(1); } } - out << "]"; } - out << "\n"; - - printBibInfo(song, out); - printSpecialChars(out); - - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].lyricerr) { - out << "!!!RWG: Lyric placement mismatch " - << "in phrase (too many syllables) " << songdata[i].phnum << " [" - << key << "]\n"; - break; + // remove the last line if it is only "//": + if (!lines.empty()) { + if (hre.search(lines.back(), "^\\s*$")) { + lines.resize(lines.size() - 1); } } + if (lines.empty()) { + cerr << "ERROR: No notes in MEL data" << endl; + return false; + } - for (i=0; i<(int)trailer.size(); i++) { - out << trailer[i] << "\n"; + for (int i=0; i<(int)lines.size(); i++) { + resize(size() + 1); + back().parsePhrase(lines[i]); } - printHumdrumFooterInfo(out, song); + analyzeTies(); + analyzePhrases(); + generateHumdrumNotes(); + calculateClef(); + calculateKeyInformation(); + calculateTimeSignatures(); -/* - if (!splitQ) { - out << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + return true; +} + + + +////////////////////////////// +// +// Tool_esac2hum::Score::assignFreeMeasureNumbers -- The time signature +// is "FREI", so assign a measure number to eavery barline, not checking +// for pickup or partial measures. +// + +void Tool_esac2hum::Score::assignFreeMeasureNumbers(void) { + vector measurelist; + getMeasureList(measurelist); + + int barnum = 1; + for (int i=0; i<(int)measurelist.size(); i++) { + measurelist[i]->m_barnum = barnum++; + measurelist[i]->m_partialBegin = false; + measurelist[i]->m_partialEnd = false; + measurelist[i]->m_complete = true; } -*/ } ////////////////////////////// // -// Tool_esac2hum::placeLyrics -- extract lyrics (if any) and place on correct notes +// Tool_esac2hum::Score::assignSingleMeasureNumbers -- There is a +// single time signature for the entire melody, so identify full +// and unfull measures, marking full that match the time signature +// duration as complete, and then try to pair measures and look +// for a pickup measure at the start of the music. +// The Measure::tsticks is the expected duration of the measure +// according to the time signature. // -bool Tool_esac2hum::placeLyrics(vector& song, vector& songdata) { - int start = -1; - int stop = -1; - getLineRange(song, "TXT", start, stop); - if (start < 0) { - // no TXT[] field, so don't do anything - return true; +void Tool_esac2hum::Score::assignSingleMeasureNumbers(void) { + vector measurelist; + getMeasureList(measurelist); + + if (measurelist.empty()) { + // strange error: no measures; + return; } - int line = 0; - vector lyrics; - string buffer; - for (line=0; line<=stop-start; line++) { - if (song[line+start].size() <= 4) { - cerr << "Error: lyric line is too short!: " - << song[line+start] << endl; - return false; + + // first identify complete measures: + for (int i=0; i<(int)measurelist.size(); i++) { + if (measurelist[i]->m_tsticks == measurelist[i]->m_ticks) { + measurelist[i]->setComplete(); } - buffer = song[line+start].substr(4); - if (line == stop - start) { - auto loc = buffer.rfind(']'); - if (loc != string::npos) { - buffer.resize(loc); - } + } + + // check for pickup measure at beginning of music + if (measurelist[0]->m_ticks < measurelist[0]->m_tsticks) { + measurelist[0]->setPartialEnd(); + // check for partial measure at end that matches end measure + if (measurelist.back()->m_ticks < measurelist.back()->m_tsticks) { + measurelist.back()->setPartialBegin(); } - if (buffer == "") { + } + + // search for pairs of partial measures + for (int i=1; i<(int)measurelist.size(); i++) { + if (!measurelist[i]->isUnassigned()) { continue; } - getLyrics(lyrics, buffer); - cleanupLyrics(lyrics); - placeLyricPhrase(songdata, lyrics, line); + if (!measurelist[i-1]->isUnassigned()) { + continue; + } + double ticks1 = measurelist[i-1]->m_ticks; + double ticks2 = measurelist[i]->m_ticks; + double tsticks1 = measurelist[i-1]->m_tsticks; + double tsticks2 = measurelist[i]->m_tsticks; + if (tsticks1 != tsticks2) { + // strange error; + continue; + } + if (ticks1 + ticks2 == tsticks2) { + measurelist[i-1]->setPartialBegin(); + measurelist[i]->setPartialEnd(); + } } - return true; + // Now assign barlines to measures. that are complete or + // partial starts. + int barnum = 1; + for (int i=0; i<(int)measurelist.size(); i++) { + if (measurelist[i]->isComplete()) { + measurelist[i]->m_barnum = barnum++; + } else if (measurelist[i]->isPartialBegin()) { + measurelist[i]->m_barnum = barnum++; + } else if (measurelist[i]->isPartialEnd()) { + measurelist[i]->m_barnum = -1; + } + } + if (measurelist[0]->isPartialEnd()) { + measurelist[0]->m_barnum = 0; // pickup: don't add barline on first measure + } } ////////////////////////////// // -// Tool_esac2hum::cleanupLyrics -- add preceeding dashes, avoid starting *'s if any, -// and convert _'s to spaces. +// Tool_esac2hum::Measure::isUnassigned -- // -void Tool_esac2hum::cleanupLyrics(vector& lyrics) { - int length; - int length2; - int i, j, m; - int lastsyl = 0; - for (i=0; i<(int)lyrics.size(); i++) { - length = (int)lyrics[i].size(); - for (j=0; j 0) { - if ((lyrics[i] != ".") && - (lyrics[i] != "") && - (lyrics[i] != "%") && - (lyrics[i] != "^") && - (lyrics[i] != "|") && - (lyrics[i] != " ")) { - lastsyl = -1; - for (m=i-1; m>=0; m--) { - if ((lyrics[m] != ".") && - (lyrics[m] != "") && - (lyrics[m] != "%") && - (lyrics[i] != "^") && - (lyrics[m] != "|") && - (lyrics[m] != " ")) { - lastsyl = m; - break; - } - } - if (lastsyl >= 0) { - length2 = (int)lyrics[lastsyl].size(); - if (lyrics[lastsyl][length2-1] == '-') { - for (j=0; j<=length; j++) { - lyrics[i][length - j + 1] = lyrics[i][length - j]; - } - lyrics[i][0] = '-'; - } - } - } - } - // avoid *'s on the start of lyrics by placing a space before - // them if they exist. - if (lyrics[i][0] == '*') { - length = (int)lyrics[i].size(); - for (j=0; j<=length; j++) { - lyrics[i][length - j + 1] = lyrics[i][length - j]; - } - lyrics[i][0] = ' '; - } +////////////////////////////// +// +// Tool_esac2hum::Measure::setComplete -- +// - // avoid !'s on the start of lyrics by placing a space before - // them if they exist. - if (lyrics[i][0] == '!') { - length = (int)lyrics[i].size(); - for (j=0; j<=length; j++) { - lyrics[i][length - j + 1] = lyrics[i][length - j]; - } - lyrics[i][0] = ' '; - } +void Tool_esac2hum::Measure::setComplete(void) { + m_complete = true; + m_partialBegin = false; + m_partialEnd = false; +} - } + +////////////////////////////// +// +// Tool_esac2hum::Measure::isComplete -- +// + +bool Tool_esac2hum::Measure::isComplete(void) { + return m_complete; } -/////////////////////////////// +////////////////////////////// // -// Tool_esac2hum::getLyrics -- extract the lyrics from the text string. +// Tool_esac2hum::Measure::setPartialBegin -- // -void Tool_esac2hum::getLyrics(vector& lyrics, const string& buffer) { - lyrics.resize(0); - int zero1 = 0; - string current; - int zero2 = 0; - zero2 = zero1 + zero2; +void Tool_esac2hum::Measure::setPartialBegin(void) { + m_complete = false; + m_partialBegin = true; + m_partialEnd = false; +} - int length = (int)buffer.size(); - int i; - i = 0; - while (i& songdata, vector& lyrics, int line) { - int i = 0; - int start = 0; - int found = 0; +void Tool_esac2hum::Measure::setPartialEnd(void) { + m_complete = false; + m_partialBegin = false; + m_partialEnd = true; +} - if (lyrics.empty()) { - return true; + + +////////////////////////////// +// +// Tool_esac2hum::Measure::isPartialEnd -- +// + +bool Tool_esac2hum::Measure::isPartialEnd(void) { + return m_partialEnd; +} + + + +////////////////////////////// +// +// Tool_esac2hum::Score::calculateTimeSignatures -- +// + +void Tool_esac2hum::Score::calculateTimeSignatures(void) { + string ts = m_params["_time"]; + ts = trimSpaces(ts); + if (ts.find("FREI") != string::npos) { + m_timesig = "*MX"; + setAllTimesigTicks(0.0); + assignFreeMeasureNumbers(); + return; } - // find the phrase to which the lyrics belongs - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].phnum == line) { - found = 1; - break; + + HumRegex hre; + if (hre.search(ts, "^(\\d+)/(\\d+)$")) { + m_timesig = "*M" + ts; + int top = hre.getMatchInt(1); + int bot = hre.getMatchInt(2); + // check if bot is a power of two? + double tsticks = top * m_minrhy / bot; + setAllTimesigTicks(tsticks); + assignSingleMeasureNumbers(); + return; + } else if (hre.search(ts, "^(\\d+/\\d+(?:\\s+|$)){2,}$")) { + prepareMultipleTimeSignatures(ts); + } + + // Complicated case where the time signature changes + vector timesigs; + hre.split(timesigs, ts, "\\s+"); + if (timesigs.size() < 2) { + m_errors.push_back("ERROR: strange format for time signatures."); + return; + } + +/* ggg + vector bticks(timesigs.size(), 0); + for (int i=0; i<(int)bticks +*/ + + +} + + +////////////////////////////// +// +// Tool_esac2hum::Score::prepareMultipleTimeSignatures -- +// N.B.: Will have problems when the duration of time siganture +// in a list are the same such as "4/4 2/2". +// + +void Tool_esac2hum::Score::prepareMultipleTimeSignatures(const string& ts) { + vector tss; + HumRegex hre; + string timesigs = ts; + hre.split(tss, timesigs, "\\s+"); + if (tss.size() < 2) { + cerr << "Time sigs: " << ts << " needs to have at least two time signatures" << endl; + } + + // Calculate tick duration of time signature in list: + vector tsticks(tss.size(), 0); + for (int i=0; i<(int)tss.size(); i++) { + if (!hre.search(tss[i], "^(\\d+)/(\\d+)$")) { + continue; } + int top = hre.getMatchInt(1); + int bot = hre.getMatchInt(2); + double ticks = top * m_minrhy / bot; + tsticks[i] = ticks; } - start = i; - if (!found) { - cerr << "Error: cannot find music for lyrics line " << line << endl; - cerr << "Error near input data line: " << inputline << endl; - return false; + //cerr << "\nMultiple time signatures in melody: " << endl; + //for (int i=0; i<(int)tss.size(); i++) { + // cerr << "(" << i+1 << "): " << tss[i] << "\tticks:" << tsticks[i] << endl; + //} + //cerr << endl; + + // First assign a time signature to every inner measure in a phrase, which + // is presumed to be a complete measure: + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + for (int j=1; j<(int)phrase.size()-1; j++) { + Tool_esac2hum::Measure& measure = phrase.at(j); + for (int k=0; k<(int)tss.size(); k++) { + if (tsticks[k] == measure.m_ticks) { + measure.m_measureTimeSignature = "*M" + tss[k]; + measure.setComplete(); + } + } + + } } - for (i=0; i<(int)lyrics.size() && i+start < (int)songdata.size(); i++) { - if ((lyrics[i] == " ") || (lyrics[i] == ".") || (lyrics[i] == "")) { - if (songdata[i+start].pitch < 0) { - lyrics[i] = "%"; - } else { - lyrics[i] = "|"; + // Now check if the measure at the end and beginning + // of the next phrase are both complete. If not then + // calculate partial measure pairs. + for (int i=0; i<(int)size()-1; i++) { + Tool_esac2hum::Phrase& phrase = at(i); + Tool_esac2hum::Phrase& nextphrase = at(i+1); + if (phrase.size() < 2) { + // deal with phrases with a single measure later + continue; + } + if (nextphrase.size() < 2) { + // deal with phrases with a single measure later + continue; + } + Tool_esac2hum::Measure& measure = phrase.back(); + Tool_esac2hum::Measure& nextmeasure = nextphrase.at(0); + + int mticks = measure.m_ticks; + int nmticks = nextmeasure.m_ticks; + + int found1 = -1; + int found2 = -1; + + for (int j=(int)tss.size() - 1; j>=0; j--) { + if (tsticks.at(j) == mticks) { + found1 = j; + } + if (tsticks.at(j) == nmticks) { + found2 = j; } - // lyrics[i] = "."; } - songdata[i+start].text = lyrics[i]; - songdata[i+start].lyricnum = line; - if (line != songdata[i+start].phnum) { - songdata[i+start].lyricerr = 1; // lyric does not line up with music + if ((found1 >= 0) && (found2 >= 0)) { + // The two measures are complete + measure.m_measureTimeSignature = "*M" + tss[found1]; + nextmeasure.m_measureTimeSignature = "*M" + tss[found2]; + measure.setComplete(); + nextmeasure.setComplete(); + } else { + // See if the sum of the two measures match + // a listed time signature. if so, then they + // form two partial measures. + int ticksum = mticks + nmticks; + for (int z=0; z<(int)tsticks.size(); z++) { + if (tsticks.at(z) == ticksum) { + nextmeasure.m_barnum = -1; + measure.m_measureTimeSignature = "*M" + tss.at(z); + nextmeasure.m_measureTimeSignature = "*M" + tss.at(z); + measure.setPartialBegin(); + nextmeasure.setPartialEnd(); + } + } } } - return true; -} + // Check if the first measure is a complete time signature in duration. + // If not then mark as pickup measure. If incomplete and last measure + // is incomplete, then merge into a single measure (partial start for + // last measure and partial end for first measure. + if (empty()) { + // no data + } else if ((size() == 1) && (at(0).size() <= 1)) { + // single measure in melody + } else { + Tool_esac2hum::Measure& firstmeasure = at(0).at(0); + Tool_esac2hum::Measure& lastmeasure = back().back(); + double firstticks = firstmeasure.m_ticks; + double lastticks = lastmeasure.m_ticks; + int foundfirst = -1; + int foundlast = -1; -////////////////////////////// -// -// Tool_esac2hum::printSpecialChars -- print high ASCII character table -// + for (int i=(int)tss.size() - 1; i>=0; i--) { + if (tsticks.at(i) == firstticks) { + foundfirst = i; + } + if (tsticks.at(i) == lastticks) { + foundlast = i; + } + } -void Tool_esac2hum::printSpecialChars(ostream& out) { - int i; - for (i=0; i<(int)chartable.size(); i++) { - if (chartable[i]) { - switch (i) { - case 129: out << "!!!RNB" << ": symbol: ü = u umlaut (UTF-8: " - << (char)0xc3 << (char)0xb3 << ")\n"; break; - case 130: out << "!!!RNB" << ": symbol: é= e acute (UTF-8: " - << (char)0xc3 << (char)0xa9 << ")\n"; break; - case 132: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: " - << (char)0xc3 << (char)0xa4 << ")\n"; break; - case 134: out << "!!!RNB" << ": symbol: $c = c acute (UTF-8: " - << (char)0xc4 << (char)0x87 << ")\n"; break; - case 136: out << "!!!RNB" << ": symbol: $l = l slash (UTF-8: " - << (char)0xc5 << (char)0x82 << ")\n"; break; - case 140: out << "!!!RNB" << ": symbol: î = i circumflex (UTF-8: " - << (char)0xc3 << (char)0xaf << ")\n"; break; - case 141: out << "!!!RNB" << ": symbol: $X = Z acute (UTF-8: " - << (char)0xc5 << (char)0xb9 << ")\n"; break; - case 142: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: " - << (char)0xc3 << (char)0xa4 << ")\n"; break; - case 143: out << "!!!RNB" << ": symbol: $C = C acute (UTF-8: " - << (char)0xc4 << (char)0x86 << ")\n"; break; - case 148: out << "!!!RNB" << ": symbol: ö = o umlaut (UTF-8: " - << (char)0xc3 << (char)0xb6 << ")\n"; break; - case 151: out << "!!!RNB" << ": symbol: $S = S acute (UTF-8: " - << (char)0xc5 << (char)0x9a << ")\n"; break; - case 152: out << "!!!RNB" << ": symbol: $s = s acute (UTF-8: " - << (char)0xc5 << (char)0x9b << ")\n"; break; - case 156: out << "!!!RNB" << ": symbol: $s = s acute (UTF-8: " - << (char)0xc5 << (char)0x9b << ")\n"; break; - case 157: out << "!!!RNB" << ": symbol: $L = L slash (UTF-8: " - << (char)0xc5 << (char)0x81 << ")\n"; break; - case 159: out << "!!!RNB" << ": symbol: $vc = c hachek (UTF-8: " - << (char)0xc4 << (char)0x8d << ")\n"; break; - case 162: out << "!!!RNB" << ": symbol: ó= o acute (UTF-8: " - << (char)0xc3 << (char)0xb3 << ")\n"; break; - case 163: out << "!!!RNB" << ": symbol: ú= u acute (UTF-8: " - << (char)0xc3 << (char)0xba << ")\n"; break; - case 165: out << "!!!RNB" << ": symbol: $a = a hook (UTF-8: " - << (char)0xc4 << (char)0x85 << ")\n"; break; - case 169: out << "!!!RNB" << ": symbol: $e = e hook (UTF-8: " - << (char)0xc4 << (char)0x99 << ")\n"; break; - case 171: out << "!!!RNB" << ": symbol: $y = z acute (UTF-8: " - << (char)0xc5 << (char)0xba << ")\n"; break; - case 175: out << "!!!RNB" << ": symbol: $Z = Z dot (UTF-8: " - << (char)0xc5 << (char)0xbb << ")\n"; break; - case 179: out << "!!!RNB" << ": symbol: $l = l slash (UTF-8: " - << (char)0xc5 << (char)0x82 << ")\n"; break; - case 185: out << "!!!RNB" << ": symbol: $a = a hook (UTF-8: " - << (char)0xc4 << (char)0x85 << ")\n"; break; - case 189: out << "!!!RNB" << ": symbol: $Z = Z dot (UTF-8: " - << (char)0xc5 << (char)0xbb << ")\n"; break; - case 190: out << "!!!RNB" << ": symbol: $z = z dot (UTF-8: " - << (char)0xc5 << (char)0xbc << ")\n"; break; - case 191: out << "!!!RNB" << ": symbol: $z = z dot (UTF-8: " - << (char)0xc5 << (char)0xbc << ")\n"; break; - case 224: out << "!!!RNB" << ": symbol: Ó= O acute (UTF-8: " - << (char)0xc3 << (char)0x93 << ")\n"; break; - case 225: out << "!!!RNB" << ": symbol: ß = sz ligature (UTF-8: " - << (char)0xc3 << (char)0x9f << ")\n"; break; - case 0xdf: out << "!!!RNB" << ": symbol: ß = sz ligature (UTF-8: " - << (char)0xc3 << (char)0x9f << ")\n"; break; -// Polish version: -// case 228: out << "!!!RNB" << ": symbol: $n = n acute (UTF-8: " -// << (char)0xc5 << (char)0x84 << ")\n"; break; -// Luxembourg version for some reason...: - case 228: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: " - << (char)0xc5 << (char)0x84 << ")\n"; break; - case 230: out << "!!!RNB" << ": symbol: c = c\n"; break; - case 231: out << "!!!RNB" << ": symbol: $vs = s hachek (UTF-8: " - << (char)0xc5 << (char)0xa1 << ")\n"; break; - case 234: out << "!!!RNB" << ": symbol: $e = e hook (UTF-8: " - << (char)0xc4 << (char)0x99 << ")\n"; break; - case 241: out << "!!!RNB" << ": symbol: $n = n acute (UTF-8: " - << (char)0xc5 << (char)0x84 << ")\n"; break; - case 243: out << "!!!RNB" << ": symbol: ó= o acute (UTF-8: " - << (char)0xc3 << (char)0xb3 << ")\n"; break; - case 252: out << "!!!RNB" << ": symbol: ü = u umlaut (UTF-8: " - << (char)0xc3 << (char)0xbc << ")\n"; break; -// default: + if ((foundfirst >= 0) && (foundlast >= 0)) { + // first and last measures are both complete + firstmeasure.m_measureTimeSignature = "*M" + tss.at(foundfirst); + lastmeasure.m_measureTimeSignature = "*M" + tss.at(foundlast); + firstmeasure.setComplete(); + lastmeasure.setComplete(); + } else { + // if both sum to a time signature than assigned that time signature to both + double sumticks = firstticks + lastticks; + int sumfound = -1; + for (int i=0; i<(int)tsticks.size(); i++) { + if (tsticks[i] == sumticks) { + sumfound = i; + break; + } + } + if (sumfound >= 0) { + // First and last meatures match a time signture, so + // use that time signture for both, mark firt measure + // last pickup (barnum -> 0), and mark last as partial + // measure start + firstmeasure.m_measureTimeSignature = "*M" + tss.at(sumfound); + lastmeasure.m_measureTimeSignature = "*M" + tss.at(sumfound); + firstmeasure.m_barnum = 0; + firstmeasure.setPartialEnd(); + lastmeasure.setPartialBegin(); + } else if ((foundfirst >= 0) && (foundlast < 0)) { + firstmeasure.setComplete(); + lastmeasure.setPartialBegin(); + } else if ((foundfirst < 0) && (foundlast >= 0)) { + firstmeasure.setPartialEnd(); + lastmeasure.setComplete(); + } + } + } + + + // Now assign bar numbers + // First probalby check for pairs of uncategorized measure durations (deal with that later). + vector measurelist; + getMeasureList(measurelist); + int barnum = 1; + for (int i=0; i<(int)measurelist.size(); i++) { + if ((i == 0) && measurelist.at(i)->isPartialEnd()) { + measurelist.at(i)->m_barnum = 0; + continue; } + if (measurelist.at(i)->isComplete()) { + measurelist.at(i)->m_barnum = barnum++; + } else if (measurelist.at(i)->isPartialBegin()) { + measurelist.at(i)->m_barnum = barnum++; + } else if (measurelist.at(i)->isPartialEnd()) { + measurelist.at(i)->m_barnum = -1; + } else { + measurelist.at(i)->m_errors.push_back("UNCATEGORIZED MEASURE"); + } + } + + // Now remove duplicate time signatures + string current = ""; + for (int i=0; i<(int)measurelist.size(); i++) { + if (measurelist.at(i)->m_measureTimeSignature == current) { + measurelist.at(i)->m_measureTimeSignature = ""; + } else { + current = measurelist.at(i)->m_measureTimeSignature; } - chartable[i] = 0; } + } ////////////////////////////// // -// Tool_esac2hum::printTitleInfo -- print the first line of the CUT[] field. +// Tool_esac2hum::Score::setAllTimeSigTicks -- Used for calculating bar numbers; // -bool Tool_esac2hum::printTitleInfo(vector& song, ostream& out) { - int start = -1; - int stop = -1; - getLineRange(song, "CUT", start, stop); - if (start == -1) { - cerr << "Error: cannot find CUT[] field in song: " << song[0] << endl; - return false; - } - - string buffer; - buffer = song[start].substr(4); - if (buffer.back() == ']') { - buffer.resize((int)buffer.size() - 1); - } +void Tool_esac2hum::Score::setAllTimesigTicks(double ticks) { + vector measurelist; + getMeasureList(measurelist); - out << "!!!OTL: "; - for (int i=0; i<(int)buffer.size(); i++) { - printChar(buffer[i], out); + for (int i=0; i<(int)measurelist.size(); i++) { + measurelist[i]->m_tsticks = ticks; } - out << "\n"; - - return true; } ////////////////////////////// // -// Tool_esac2hum::printChar -- print text characters, translating high-bit data -// if required. +// Tool_esac2hum::Score::calculateKeyInformation -- // -void Tool_esac2hum::printChar(unsigned char c, ostream& out) { - out << c; -/* - if (c < 128) { - out << c; - } else { - chartable[c]++; - switch (c) { - case 129: out << "ü"; break; - case 130: out << "é"; break; - case 132: out << "ä"; break; - case 134: out << "$c"; break; - case 136: out << "$l"; break; - case 140: out << "î"; break; - case 141: out << "$X"; break; // Z acute - case 142: out << "ä"; break; // ? - case 143: out << "$C"; break; - case 148: out << "ö"; break; - case 151: out << "$S"; break; - case 152: out << "$s"; break; - case 156: out << "$s"; break; // 1250 encoding - case 157: out << "$L"; break; - case 159: out << "$vc"; break; // Cech c with v accent - case 162: out << "ó"; break; - case 163: out << "ú"; break; - case 165: out << "$a"; break; - case 169: out << "$e"; break; - case 171: out << "$y"; break; - case 175: out << "$Z"; break; // 1250 encoding - case 179: out << "$l"; break; // 1250 encoding - case 185: out << "$a"; break; // 1250 encoding - case 189: out << "$Z"; break; // Z dot - case 190: out << "$z"; break; // z dot - case 191: out << "$z"; break; // 1250 encoding - case 224: out << "Ó"; break; - case 225: out << "ß"; break; - case 0xdf: out << "ß"; break; - // Polish version: - // case 228: out << "$n"; break; - // Luxembourg version (for some reason...) - case 228: out << "ä"; break; - case 230: out << "c"; break; // ? - case 231: out << "$vs"; break; // Cech s with v accent - case 234: out << "$e"; break; // 1250 encoding - case 241: out << "$n"; break; // 1250 encoding - case 243: out << "ó"; break; // 1250 encoding - case 252: out << "ü"; break; - default: out << c; +void Tool_esac2hum::Score::calculateKeyInformation(void) { + vector notelist; + getNoteList(notelist); + + vector b40pcs(40, 0); + for (int i=0; i<(int)notelist.size(); i++) { + int pc = notelist[i]->m_b40degree; + if ((pc >= 0) && (pc < 40)) { + b40pcs.at(pc)++; + } + } + + string tonic = m_params["_tonic"]; + if (tonic.empty()) { + // no tonic for some strange reason + // error will be reported when calculating Humdrum pitches. + return; + } + char letter = std::toupper(tonic[0]); + + // Compare counts of third and sixth pitch classes: + int majorsum = b40pcs.at(12) + b40pcs.at(29); + int minorsum = b40pcs.at(11) + b40pcs.at(28); + if (minorsum > majorsum) { + letter = std::tolower(letter); + } + string flats; + string sharps; + for (int i=1; i<(int)tonic.size(); i++) { + if (tonic[i] == 'b') { + flats += "-"; + } else if (tonic[i] == '#') { + sharps += "#"; + } + } + + m_keydesignation = "*"; + m_keydesignation += letter; + + if (!flats.empty() && !sharps.empty()) { + m_errors.push_back("ERROR: tonic note cannot include both sharps and flats."); + } + if (!flats.empty()) { + m_keydesignation += flats; + } else { + m_keydesignation += sharps; + } + m_keydesignation += ":"; + + if (std::isupper(letter)) { + + // major key signature + if (m_keydesignation == "*C:") { + m_keysignature = "*k[]"; + } else if (m_keydesignation == "*G:") { + m_keysignature = "*k[f#]"; + } else if (m_keydesignation == "*D:") { + m_keysignature = "*k[f#c#]"; + } else if (m_keydesignation == "*A:") { + m_keysignature = "*k[f#c#g#]"; + } else if (m_keydesignation == "*E:") { + m_keysignature = "*k[f#c#g#d#]"; + } else if (m_keydesignation == "*B:") { + m_keysignature = "*k[f#c#g#d#a#]"; + } else if (m_keydesignation == "*F#:") { + m_keysignature = "*k[f#c#g#d#a#e#]"; + } else if (m_keydesignation == "*C#:") { + m_keysignature = "*k[f#c#g#d#a#e#b#]"; + } else if (m_keydesignation == "*F:") { + m_keysignature = "*k[b-]"; + } else if (m_keydesignation == "*B-:") { + m_keysignature = "*k[b-e-]"; + } else if (m_keydesignation == "*E-:") { + m_keysignature = "*k[b-e-a-]"; + } else if (m_keydesignation == "*A-:") { + m_keysignature = "*k[b-e-a-d-]"; + } else if (m_keydesignation == "*D-:") { + m_keysignature = "*k[b-e-a-d-g-]"; + } else if (m_keydesignation == "*G-:") { + m_keysignature = "*k[b-e-a-d-g-c-]"; + } else if (m_keydesignation == "*C-:") { + m_keysignature = "*k[b-e-a-d-g-f-]"; + } else { + m_errors.push_back("ERROR: invalid/exotic key signature required."); + } + + } else { + + // minor key signature + if (m_keydesignation == "*a:") { + m_keysignature = "*k[]"; + } else if (m_keydesignation == "*e:") { + m_keysignature = "*k[f#]"; + } else if (m_keydesignation == "*b:") { + m_keysignature = "*k[f#c#]"; + } else if (m_keydesignation == "*f#:") { + m_keysignature = "*k[f#c#g$]"; + } else if (m_keydesignation == "*c#:") { + m_keysignature = "*k[f#c#g$d#]"; + } else if (m_keydesignation == "*g#:") { + m_keysignature = "*k[f#c#g$d#a#]"; + } else if (m_keydesignation == "*d#:") { + m_keysignature = "*k[f#c#g$d#a#e#]"; + } else if (m_keydesignation == "*a#:") { + m_keysignature = "*k[f#c#g$d#a#e#b#]"; + } else if (m_keydesignation == "*d:") { + m_keysignature = "*k[b-]"; + } else if (m_keydesignation == "*g:") { + m_keysignature = "*k[b-e-]"; + } else if (m_keydesignation == "*c:") { + m_keysignature = "*k[b-e-a-]"; + } else if (m_keydesignation == "*f:") { + m_keysignature = "*k[b-e-a-d-]"; + } else if (m_keydesignation == "*b-:") { + m_keysignature = "*k[b-e-a-d-g-]"; + } else if (m_keydesignation == "*e-:") { + m_keysignature = "*k[b-e-a-d-g-c-]"; + } else if (m_keydesignation == "*a-:") { + m_keysignature = "*k[b-e-a-d-g-f-]"; + } else { + m_errors.push_back("ERROR: invalid/exotic key signature required."); } } -*/ + } ////////////////////////////// // -// Tool_esac2hum::printKeyInfo -- +// Tool_esac2hum::Score::calculateClef -- // -void Tool_esac2hum::printKeyInfo(vector& songdata, int tonic, int textQ, - ostream& out) { - vector pitches(40, 0); - int pitchsum = 0; - int pitchcount = 0; - int i; - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].pitch >= 0) { - pitches[songdata[i].pitch % 40]++; - pitchsum += Convert::base40ToMidiNoteNumber(songdata[i].pitch); - pitchcount++; +void Tool_esac2hum::Score::calculateClef(void) { + vector notelist; + getNoteList(notelist); + + double sum = 0; + double count = 0; + int min12 = 1000; + int max12 = -1000; + + for (int i=0; i<(int)notelist.size(); i++) { + int b12 = notelist[i]->m_b12; + if (b12 > 0) { + sum += b12; + count++; + if (b12 < min12) { + min12 = b12; + } + if (b12 > max12) { + max12 = b12; + } } } + double average = sum / count; - // generate a clef, choosing either treble or bass clef depending - // on the average pitch. - double averagepitch = pitchsum * 1.0 / pitchcount; - if (averagepitch > 60.0) { - out << "*clefG2"; - if (textQ) { - out << "\t*clefG2"; - } - out << "\n"; + + if ((min12 > 54) && (average >= 60.0)) { + m_clef = "*clefG2"; + } else if ((max12 < 67) && (average < 60.0)) { + m_clef = "*clefF4"; + } else if ((min12 > 47) && (min12 <= 57) && (max12 < 77) && (max12 >= 65)) { + m_clef = "*clefGv2"; + } else if (average < 60.0) { + m_clef = "*clefF2"; } else { - out << "*clefF4"; - if (textQ) { - out << "\t*clefF4"; - } - out << "\n"; + m_clef = "*clefG2"; } +} - // generate a key signature - vector diatonic(7, 0); - diatonic[0] = getAccidentalMax(pitches[1], pitches[2], pitches[3]); - diatonic[1] = getAccidentalMax(pitches[7], pitches[8], pitches[9]); - diatonic[2] = getAccidentalMax(pitches[13], pitches[14], pitches[15]); - diatonic[3] = getAccidentalMax(pitches[18], pitches[19], pitches[20]); - diatonic[4] = getAccidentalMax(pitches[24], pitches[25], pitches[26]); - diatonic[5] = getAccidentalMax(pitches[30], pitches[31], pitches[32]); - diatonic[6] = getAccidentalMax(pitches[36], pitches[37], pitches[38]); - int flatcount = 0; - int sharpcount = 0; - int naturalcount = 0; - for (i=0; i<7; i++) { - switch (diatonic[i]) { - case -1: flatcount++; break; - case 0: naturalcount++; break; - case +1: sharpcount++; break; + +////////////////////////////// +// +// Tool_esac2hum::generateHumdrumNotes -- +// + +void Tool_esac2hum::Score::generateHumdrumNotes(void) { + vector notelist; + getNoteList(notelist); + + string tonic = m_params["_tonic"]; + if (tonic.empty()) { + m_errors.push_back("Error: cannot find KEY[] tonic pitch"); + return; + } + char letter = std::tolower(tonic[0]); + m_b40tonic = 40 * 4 + 2; // start with middle C + switch (letter) { + case 'd': m_b40tonic += 6; break; + case 'e': m_b40tonic += 12; break; + case 'f': m_b40tonic += 17; break; + case 'g': m_b40tonic += 23; break; + case 'a': m_b40tonic += 29; break; + case 'b': m_b40tonic += 35; break; + } + int flats = 0; + int sharps = 0; + for (int i=1; i<(int)tonic.size(); i++) { + if (tonic[i] == 'b') { + flats++; + } else if (tonic[i] == '#') { + sharps++; } } + if (flats > 0) { + m_b40tonic -= flats; + } else if (sharps > 0) { + m_b40tonic += sharps; + } - char kbuf[32] = {0}; - if (naturalcount == 7) { - // do nothing - } else if (flatcount > sharpcount) { - // print a flat key signature - if (diatonic[6] == -1) strcat(kbuf, "b-"); else goto keysigend; - if (diatonic[2] == -1) strcat(kbuf, "e-"); else goto keysigend; - if (diatonic[5] == -1) strcat(kbuf, "a-"); else goto keysigend; - if (diatonic[1] == -1) strcat(kbuf, "d-"); else goto keysigend; - if (diatonic[4] == -1) strcat(kbuf, "g-"); else goto keysigend; - if (diatonic[0] == -1) strcat(kbuf, "c-"); else goto keysigend; - if (diatonic[3] == -1) strcat(kbuf, "f-"); else goto keysigend; - } else { - // print a sharp key signature - if (diatonic[3] == +1) strcat(kbuf, "f#"); else goto keysigend; - if (diatonic[0] == +1) strcat(kbuf, "c#"); else goto keysigend; - if (diatonic[4] == +1) strcat(kbuf, "g#"); else goto keysigend; - if (diatonic[1] == +1) strcat(kbuf, "d#"); else goto keysigend; - if (diatonic[5] == +1) strcat(kbuf, "a#"); else goto keysigend; - if (diatonic[2] == +1) strcat(kbuf, "e#"); else goto keysigend; - if (diatonic[6] == +1) strcat(kbuf, "b#"); else goto keysigend; + string minrhy = m_params["_minrhy"]; + if (minrhy.empty()) { + m_errors.push_back("Error: cannot find KEY[] minrhy"); + return; } -keysigend: - out << "*k[" << kbuf << "]"; - if (textQ) { - out << "\t*k[" << kbuf << "]"; + m_minrhy = std::stoi(minrhy); + // maybe check of power of two? + + for (int i=0; i<(int)notelist.size(); i++) { + notelist.at(i)->generateHumdrum(m_minrhy, m_b40tonic); } - out << "\n"; - // look at the third scale degree above the tonic pitch - int minor = pitches[(tonic + 40 + 11) % 40]; - int major = pitches[(tonic + 40 + 12) % 40]; +} - if (minor > major) { - // minor key (or related mode) - out << "*" << Convert::base40ToKern(40 * 4 + tonic) << ":"; - if (textQ) { - out << "\t*" << Convert::base40ToKern(40 * 4 + tonic) << ":"; + + +////////////////////////////// +// +// Tool_esac2hum::Note::generateHumdrum -- convert EsAC note to Humdrum note token. +// + +void Tool_esac2hum::Note::generateHumdrum(int minrhy, int b40tonic) { + string pitch; + if (m_degree != 0) { + m_b40degree = 0; + switch (abs(m_degree)) { + case 2: m_b40degree += 6; break; + case 3: m_b40degree += 12; break; + case 4: m_b40degree += 17; break; + case 5: m_b40degree += 23; break; + case 6: m_b40degree += 29; break; + case 7: m_b40degree += 35; break; + } + if ((m_alter >= -2) && (m_alter <= 2)) { + m_b40degree += m_alter; + } else { + m_errors.push_back("Error: chromatic alteration on note too large"); } - out << "\n"; + m_b40 = 40 * m_octave + m_b40degree + b40tonic; + pitch = Convert::base40ToKern(m_b40); + // m_b12 is used for calculating clef later on. + m_b12 = Convert::base40ToMidiNoteNumber(m_b40); } else { - // major key (or related mode) - out << "*" << Convert::base40ToKern(40 * 3 + tonic) << ":"; - if (textQ) { - out << "\t*" << Convert::base40ToKern(40 * 3 + tonic) << ":"; - } - out << "\n"; + pitch = "r"; + m_b40 = -1000; + m_b40degree = -1000; + } + + HumNum duration(1, minrhy); + int multiplier = (1 << m_underscores); + duration *= multiplier; + duration *= 4; // convert from whole notes to quarter notes + duration *= m_factor; + string recip = Convert::durationToRecip(duration); + for (int i=0; i b && a > c) { - return -1; - } else if (c > a && c > b) { - return +1; - } else { - return 0; +void Tool_esac2hum::Score::analyzeTies(void) { + vector notelist; + getNoteList(notelist); + + for (int i=1; i<(int)notelist.size(); i++) { + // negative m_degree indicates a tied note to previous note + if (notelist.at(i)->m_degree < 0) { + // Tied note, so link to previous note. + notelist.at(i)->m_tieEnd = true; + notelist.at(i-1)->m_tieBegin = true; + if (notelist.at(i-1)->m_degree >= 0) { + notelist.at(i)->m_degree = -notelist.at(i-1)->m_degree; + // Copy chromatic alteration and octave: + notelist[i]->m_alter = notelist.at(i-1)->m_alter; + notelist[i]->m_octave = notelist.at(i-1)->m_octave; + } + } } } + ////////////////////////////// // -// Tool_esac2hum::postProcessSongData -- clean up data and do some interpreting. +// Tool_esac2hum::Score::getNoteList -- Return a list of all notes +// in the score. // -void Tool_esac2hum::postProcessSongData(vector& songdata, vector& numerator, - vector& denominator) { - int i, j; - // move phrase start markers off of rests and onto the - // first note that it finds - for (i=0; i<(int)songdata.size()-1; i++) { - if (songdata[i].pitch < 0 && songdata[i].phstart) { - songdata[i+1].phstart = songdata[i].phstart; - songdata[i].phstart = 0; +void Tool_esac2hum::Score::getNoteList(vector& notelist) { + notelist.clear(); + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + for (int j=0; j<(int)phrase.size(); j++) { + Tool_esac2hum::Measure& measure = phrase[j]; + for (int k=0; k<(int)measure.size(); k++) { + notelist.push_back(&measure.at(k)); + } } } +} - // move phrase ending markers off of rests and onto the - // previous note that it finds - for (i=(int)songdata.size()-1; i>0; i--) { - if (songdata[i].pitch < 0 && songdata[i].phend) { - songdata[i-1].phend = songdata[i].phend; - songdata[i].phend = 0; + + +////////////////////////////// +// +// Tool_esac2hum::Score::getMeasureList -- +// + +void Tool_esac2hum::Score::getMeasureList(vector& measurelist) { + measurelist.clear(); + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + for (int j=0; j<(int)phrase.size(); j++) { + Tool_esac2hum::Measure& measure = phrase[j]; + measurelist.push_back(&measure); } } +} - // examine barline information - double dur = 0.0; - for (i=(int)songdata.size()-1; i>=0; i--) { - if (songdata[i].bar == 1) { - songdata[i].bardur = dur; - dur = songdata[i].duration; - } else { - dur += songdata[i].duration; - } - } - - int barnum = 0; - double firstdur = 0.0; - if (numerator.size() == 1 && numerator[0] > 0) { - // handle single non-frei meter - songdata[0].num = numerator[0]; - songdata[0].denom = denominator[0]; - dur = 0; - double meterdur = 4.0 / denominator[0] * numerator[0]; - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].bar) { - dur = 0.0; - } else { - dur += songdata[i].duration; - if (fabs(dur - meterdur) < 0.001) { - songdata[i].bar = 1; - songdata[i].barinterp = 1; - dur = 0.0; - } - } - } - - // readjust measure beat counts - dur = 0.0; - for (i=(int)songdata.size()-1; i>=0; i--) { - if (songdata[i].bar == 1) { - songdata[i].bardur = dur; - dur = songdata[i].duration; - } else { - dur += songdata[i].duration; - } - } - firstdur = dur; - - // number the barlines - barnum = 0; - if (fabs(firstdur - meterdur) < 0.001) { - // music for first bar, next bar will be bar 2 - barnum = 2; - } else { - barnum = 1; - // pickup-measure - } - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].bar == 1) { - songdata[i].barnum = barnum++; - } - } - - } else if (numerator.size() == 1 && numerator[0] == -1) { - // handle free meter - // number the barline - firstdur = dur; - barnum = 1; - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].bar == 1) { - songdata[i].barnum = barnum++; - } - } - } else { - // handle multiple time signatures +////////////////////////////// +// +// Tool_esac2hum::Score::analyzePhrases -- Create a list of notes in the score +// and then search for ^ (-1 degrees) which mean a tied continuation +// of the previous note. +// - // get the duration of each type of meter: - vector meterdurs; - meterdurs.resize(numerator.size()); - for (i=0; i<(int)meterdurs.size(); i++) { - meterdurs[i] = 4.0 / denominator[i] * numerator[i]; - } +void Tool_esac2hum::Score::analyzePhrases(void) { + // first create a list of the notes in the score + vector notelist; + for (int i=0; i<(int)size(); i++) { + getPhraseNoteList(notelist, i); - // measure beat counts: - dur = 0.0; - for (i=(int)songdata.size()-1; i>=0; i--) { - if (songdata[i].bar == 1) { - songdata[i].bardur = dur; - dur = songdata[i].duration; - } else { - dur += songdata[i].duration; - } + if (notelist.empty()) { + at(i).m_errors.push_back("ERROR: no notes in phrase."); + return; } - firstdur = dur; - // interpret missing barlines - int currentmeter = 0; - // find first meter - for (i=0; i<(int)numerator.size(); i++) { - if (fabs(firstdur - meterdurs[i]) < 0.001) { - songdata[0].num = numerator[i]; - songdata[0].denom = denominator[i]; - currentmeter = i; + // Find the first non-rest note and mark with phrase start: + bool foundNote = false; + for (int j=0; j<(int)notelist.size(); j++) { + if (notelist.at(j)->m_degree <= 0) { + continue; } + foundNote = true; + notelist.at(j)->m_phraseBegin = true; + break; } - // now handle the meters in the rest of the music... - int fnd = 0; - dur = 0; - for (i=0; i<(int)songdata.size()-1; i++) { - if (songdata[i].bar) { - if (songdata[i].bardur != meterdurs[currentmeter]) { - // try to find the correct new meter - fnd = 0; - for (j=0; j<(int)numerator.size(); j++) { - if (j == currentmeter) { - continue; - } - if (fabs(songdata[i].bardur - meterdurs[j]) < 0.001) { - songdata[i+1].num = numerator[j]; - songdata[i+1].denom = denominator[j]; - currentmeter = j; - fnd = 1; - } - } - if (!fnd) { - for (j=0; j<(int)numerator.size(); j++) { - if (j == currentmeter) { - continue; - } - if (fabs(songdata[i].bardur/2.0 - meterdurs[j]) < 0.001) { - songdata[i+1].num = numerator[j]; - songdata[i+1].denom = denominator[j]; - currentmeter = j; - fnd = 1; - } - } - } - } - dur = 0.0; - } else { - dur += songdata[i].duration; - if (fabs(dur - meterdurs[currentmeter]) < 0.001) { - songdata[i].bar = 1; - songdata[i].barinterp = 1; - dur = 0.0; - } - } + if (!foundNote) { + at(i).m_errors.push_back("Error: cannot find any notes in phrase."); + continue; } - // perhaps sum duration of measures again and search for error here? - - // finally, number the barlines: - barnum = 1; - for (i=0; i<(int)numerator.size(); i++) { - if (fabs(firstdur - meterdurs[i]) < 0.001) { - barnum = 2; - break; - } - } - for (i=0; i<(int)songdata.size(); i++) { - if (songdata[i].bar == 1) { - songdata[i].barnum = barnum++; + // Find the last non-rest note and mark with phrase end: + for (int j=(int)notelist.size()-1; j>=0; j--) { + if (notelist.at(j)->m_degree <= 0) { + continue; } + notelist.at(j)->m_phraseEnd = true; + break; } - - } - } - ////////////////////////////// // -// Tool_esac2hum::getMeterInfo -- +// Tool_esac2hum::Score::getPhraseNoteList -- Return a list of all notes +// in the 0-indexed phrase // -void Tool_esac2hum::getMeterInfo(string& meter, vector& numerator, - vector& denominator) { - numerator.clear(); - denominator.clear(); - HumRegex hre; - hre.replaceDestructive(meter, "", "^\\s+"); - hre.replaceDestructive(meter, "", "\\s+$"); - if (hre.search(meter, "^(\\d+)/(\\d+)$")) { - numerator.push_back(hre.getMatchInt(1)); - denominator.push_back(hre.getMatchInt(2)); +void Tool_esac2hum::Score::getPhraseNoteList(vector& notelist, int index) { + notelist.clear(); + if (index < 0) { + m_errors.push_back("ERROR: trying to access a negative phrase index"); return; } - if (hre.search(meter, "^frei$", "i")) { - numerator.push_back(-1); - denominator.push_back(-1); + if (index >= (int)size()) { + m_errors.push_back("ERROR: trying to access a phrase index that is too large"); return; } - cerr << "NEED TO DEAL WITH METER: " << meter << endl; + Tool_esac2hum::Phrase& phrase = at(index); + + for (int i=0; i<(int)phrase.size(); i++) { + Tool_esac2hum::Measure& measure = phrase[i]; + for (int j=0; j<(int)measure.size(); j++) { + Tool_esac2hum::Note& note = measure.at(j); + notelist.push_back(¬e); + } + } } ////////////////////////////// // -// Tool_esac2hum::getLineRange -- get the staring line and ending line of a data -// field. Returns -1 if the data field was not found. +// Tool_esac2hum::Phrase::getNoteList -- Return a list of all notes +// in the phrase. // -void Tool_esac2hum::getLineRange(vector& song, const string& field, - int& start, int& stop) { - string searchstring = field;; - searchstring += "["; - start = stop = -1; - for (int i=0; i<(int)song.size(); i++) { - auto loc = song[i].find(']'); - if (song[i].compare(0, searchstring.size(), searchstring) == 0) { - start = i; - if (loc != string::npos) { - stop = i; - break; - } - } else if ((start >= 0) && (loc != string::npos)) { - stop = i; - break; +void Tool_esac2hum::Phrase::getNoteList(vector& notelist) { + notelist.clear(); + Tool_esac2hum::Phrase& phrase = *this; + + for (int i=0; i<(int)phrase.size(); i++) { + Tool_esac2hum::Measure& measure = phrase[i]; + for (int j=0; j<(int)measure.size(); j++) { + Tool_esac2hum::Note& note = measure.at(j); + notelist.push_back(¬e); } } } @@ -79325,172 +79713,117 @@ void Tool_esac2hum::getLineRange(vector& song, const string& field, ////////////////////////////// // -// Tool_esac2hum::getNoteList -- get a list of the notes and rests and barlines in -// the MEL field. +// Tool_esac2hum::Phrase::parsePhrase -- // -bool Tool_esac2hum::getNoteList(vector& song, vector& songdata, double mindur, - int tonic) { - songdata.resize(0); - NoteData tempnote; - int melstart = -1; - int melstop = -1; - int i, j; - int octave = 0; - int degree = 0; - int accidental = 0; - double duration = mindur; - int bar = 0; - // int tuplet = 0; - int major[8] = {-1, 0, 6, 12, 17, 23, 29, 35}; - // int oldstate = -1; - int state = -1; - int nextstate = -1; - int phend = 0; - int phnum = 0; - int phstart = 0; - int slend = 0; - int slstart = 0; - int tie = 0; +bool Tool_esac2hum::Phrase::parsePhrase(const string& phrase) { + esac = phrase; - getLineRange(song, "MEL", melstart, melstop); + vector bars; - for (i=melstart; i<=melstop; i++) { - if (song[i].size() < 4) { - cerr << "Error: invalid line in MEL[]: " << song[i] << endl; - return false; - } - j = 4; - phstart = 1; - phend = 0; - // Note Format: (+|-)*[0..7]_*\.*( )? - // ONADB - // Order of data: Octave, Note, Accidental, Duration, Barline + HumRegex hre; + string newphrase = phrase; + newphrase = trimSpaces(newphrase); + hre.split(bars, newphrase, "\\s+"); + if (bars.empty()) { + cerr << "Funny error with no measures" << endl; + return false; + } + int length = (int)bars.size(); + for (int i=0; i tokens; + vector factors; + HumNum factor = 1; + int length = (int)measure.size(); + for (int i=0; i': break; // unknown marker -// case '<': break; // - case '^': tie = 1; state = STATE_NOTE; break; - default : cerr << "Error: unknown character " << song[i][j] - << " on the line: " << song[i] << endl; - return false; - } - j++; - switch (song[i][j]) { - case '-': case '+': nextstate = STATE_OCTAVE; break; - case 'O': - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': nextstate = STATE_NOTE; break; - case 'b': case '#': nextstate = STATE_ACC; break; - case '_': case '.': nextstate = STATE_DUR; break; - case '{': nextstate = STATE_SLSTART; break; - case '}': nextstate = STATE_SLEND; break; - case '^': nextstate = STATE_NOTE; break; - case ' ': - if (song[i][j+1] == ' ') nextstate = STATE_BAR; - else if (song[i][j+1] == '/') nextstate = -2; - break; - case '\0': - phend = 1; - break; - default: nextstate = -1; - } + bool marker = false; + if (std::isdigit(measure[i])) { + marker = true; + } else if (measure[i] == '^') { // tie placeholder for degree digit + marker = true; + } else if (measure[i] == '(') { // tuplet start + marker = true; + } else if (measure[i] == '-') { // octave lower + marker = true; + } else if (measure[i] == '+') { // octave higher + marker = true; + } - if (nextstate < state || - ((nextstate == STATE_NOTE) && (state == nextstate))) { - tempnote.clear(); - if (degree < 0) { // rest - tempnote.pitch = -999; - } else { - tempnote.pitch = degree + 40*(octave + 4) + accidental + tonic; - } - if (tie) { - tempnote.pitch = songdata[(int)songdata.size()-1].pitch; - if (songdata[(int)songdata.size()-1].tieend) { - songdata[(int)songdata.size()-1].tiecont = 1; - songdata[(int)songdata.size()-1].tieend = 0; - } else { - songdata[(int)songdata.size()-1].tiestart = 1; - } - tempnote.tieend = 1; - } - tempnote.duration = duration; - tempnote.phend = phend; - tempnote.bar = bar; - tempnote.phstart = phstart; - tempnote.slstart = slstart; - tempnote.slend = slend; - if (nextstate == -2) { - tempnote.bar = 2; - tempnote.phend = 1; - } - tempnote.phnum = phnum; + if (marker && !tokens.empty() && !tokens.back().empty()) { + char checkChar = tokens.back().back(); + if (checkChar == '(') { + marker = false; + } else if (checkChar == '-') { + marker = false; + } else if (checkChar == '+') { + marker = false; + } + } - songdata.push_back(tempnote); - duration = mindur; - degree = 0; - bar = 0; - tie = 0; - phend = 0; - phstart = 0; - slend = 0; - slstart = 0; - octave = 0; - accidental = 0; - if (nextstate == -2) { - return true; - } + if (marker) { + tokens.resize(tokens.size() + 1); + tokens.back() += measure[i]; + factors.resize(factors.size() + 1); + factors.back() = factor; + } else { + if (!tokens.empty()) { + tokens.back() += measure[i]; + } else { + cerr << "!!ERROR: unknown character at start of measure: " << measure << endl; } } - phnum++; + + if (measure[i] == ')') { + factor = 1; + } + } + + if (tokens.empty()) { + cerr << "!!ERROR: In measure: " << measure << ": no notes to parts." << endl; + return false; + } + + for (int i=0; i<(int)tokens.size(); i++) { + resize(size() + 1); + back().parseNote(tokens[i], factors[i]); + } + + // Calculate ticks for measure: + m_ticks = 0; + for (int i=0; i<(int)size(); i++) { + m_ticks += at(i).m_ticks; } return true; @@ -79500,1670 +79833,1651 @@ bool Tool_esac2hum::getNoteList(vector& song, vector& songdata ////////////////////////////// // -// Tool_esac2hum::printNoteData -- +// Tool_esac2hum::Note::parseNote -- // -void Tool_esac2hum::printNoteData(NoteData& data, int textQ, ostream& out) { +bool Tool_esac2hum::Note::parseNote(const string& note, HumNum factor) { + esac = note; - if (data.num > 0) { - out << "*M" << data.num << "/" << data.denom; - if (textQ) { - out << "\t*M" << data.num << "/" << data.denom; + int minus = 0; + int plus = 0; + int b = 0; + int s = 0; + m_degree = 0; + m_dots = 0; + + for (int i=0; i<(int)note.size(); i++) { + if (note[i] == '.') { // augmentation dot + m_dots++; + } else if (note[i] == '_') { // duration modifier + m_underscores++; + } else if (note[i] == '-') { // lower octave + minus++; + } else if (note[i] == '+') { // upper octave + plus++; + } else if (note[i] == 'b') { // flat + b++; + } else if (note[i] == '#') { // sharp + s++; + } else if (isdigit(note[i])) { + m_degree = note[i] - '0'; + } else if (note[i] == '^') { // tied to previous note + m_degree = -1000; } - out << "\n"; - } - if (data.phstart == 1) { - out << "{"; } - if (data.slstart == 1) { - out << "("; + + m_ticks = 1 << m_underscores; + if (m_dots > 0) { + m_ticks = m_ticks * (2.0 - 1.0/(1 << m_dots)); } - if (data.tiestart == 1) { - out << "["; + + if (b > 2) { + cerr << "!! ERROR: more than double flat not parseable, note: " << esac << endl; } - out << Convert::durationFloatToRecip(data.duration); - if (data.pitch < 0) { - out << "r"; - } else { - out << Convert::base40ToKern(data.pitch); + if (s > 2) { + cerr << "!! ERROR: more than double sharp not parseable, note: " << esac << endl; } - if (data.tiecont == 1) { - out << "_"; + + m_alter = s - b; + m_octave = plus - minus; + + m_factor = factor; + + return true; +} + + + +////////////////////////////// +// +// Tool_esac2hum::printHeader -- +// + +void Tool_esac2hum::printHeader(ostream& output) { + string filename = createFilename(); + output << "!!!!SEGMENT: " << filename << endl; + + string title = m_score.m_params["_title"]; + output << "!!!OTL:"; + if (!title.empty()) { + output << " " << title; } - if (data.tieend == 1) { - out << "]"; + output << endl; + // sometimes CUT[] has two lines, and the sescond is the text incipit: + string incipit = m_score.m_params["_incipit"]; + if (!incipit.empty()) { + output << "!!!TIN: " << incipit << endl; } - if (data.slend == 1) { - out << ")"; + + string source = m_score.m_params["_source"]; + output << "!!!source:"; + if (!source.empty()) { + output << " " << source; } - if (data.phend == 1) { - out << "}"; + output << endl; + + string id = m_score.m_params["_id"]; + output << "!!!id:"; + if (!id.empty()) { + output << " " << id; } + output << endl; - if (textQ) { - out << "\t"; - if (data.phstart == 1) { - out << "{"; - } - if (data.text == "") { - if (data.pitch < 0) { - data.text = "%"; - } else { - data.text = "|"; - } - } - if (data.pitch < 0 && (data.text.find('%') == string::npos)) { - out << "%"; - } - if (data.text == " *") { - if (data.pitch < 0) { - data.text = "%*"; - } else { - data.text = "|*"; - } - } - if (data.text == "^") { - data.text = "|^"; - } - printString(data.text, out); - if (data.phend == 1) { - out << "}"; - } + string signature = m_score.m_params["SIG"]; + output << "!!!signature:"; + if (!signature.empty()) { + output << " " << signature; } + output << endl; - out << "\n"; + output << "**kern" << endl; +} - // print barline information - if (data.bar == 1) { - out << "="; - if (data.barnum > 0) { - out << data.barnum; - } - if (data.barinterp) { - // out << "yy"; - } - if (debugQ) { - if (data.bardur > 0.0) { - out << "[" << data.bardur << "]"; - } - } - if (textQ) { - out << "\t"; - out << "="; - if (data.barnum > 0) { - out << data.barnum; - } - if (data.barinterp) { - // out << "yy"; - } - if (debugQ) { - if (data.bardur > 0.0) { - out << "[" << data.bardur << "]"; + +////////////////////////////// +// +// Tool_esac2hum::createFilename -- from SIG[] and CUT[], with spaces in CUT[] turned into +// underscores and accents removed from characters. +// +// Also need to deal with decomposed accents, if necessary: +// 0x0301: Combining acute accent +// 0x0300: Combining grave accent +// 0x0302: Combining circumflex accent +// 0x0303: Combining tilde +// 0x0308: Combining diaeresis (umlaut) +// 0x0327: Combining cedilla +// 0x0328: Combining ogonek +// 0x0304: Combining macron +// 0x0306: Combining breve +// 0x0307: Combining dot above +// 0x0323: Combining dot below +// 0x030A: Combining ring above +// 0x030B: Combining double acute accent +// 0x030C: Combining caron +// +// +// std::unordered_map m_accent_map = { +// {'á', 'a'}, {'à', 'a'}, {'ä', 'a'}, {'â', 'a'}, {'ã', 'a'}, {'å', 'a'}, +// {'é', 'e'}, {'è', 'e'}, {'ë', 'e'}, {'ê', 'e'}, +// {'í', 'i'}, {'ì', 'i'}, {'ï', 'i'}, {'î', 'i'}, +// {'ó', 'o'}, {'ò', 'o'}, {'ö', 'o'}, {'ô', 'o'}, {'õ', 'o'}, {'ø', 'o'}, +// {'ú', 'u'}, {'ù', 'u'}, {'ü', 'u'}, {'û', 'u'}, +// {'ý', 'y'}, {'ÿ', 'y'}, +// {'ñ', 'n'}, {'ç', 'c'}, +// {'ą', 'a'}, {'ć', 'c'}, {'ę', 'e'}, {'ł', 'l'}, {'ń', 'n'}, +// {'ś', 's'}, {'ź', 'z'}, {'ż', 'z'} +// }; + +string Tool_esac2hum::createFilename(void) { + string source = m_score.m_params["_source"]; + string prefix; + string sig = m_score.m_params["SIG"]; + string title = m_score.m_params["_title"]; + string id = m_score.m_params["_id"]; + if (sig.empty()) { + sig = id; + } + + HumRegex hre; + // Should not be spaces, but just in case; + hre.replaceDestructive(sig, "", "\\s+", "g"); + hre.replaceDestructive(source, "", "\\s+", "g"); + + if (!m_filePrefix.empty()) { + prefix = m_filePrefix; + source = ""; + } + + // Convert spaces to underscores: + hre.replaceDestructive(title, "_", "\\s+", "g"); + // Remove accents: + hre.replaceDestructive(title, "a", "á", "g"); + hre.replaceDestructive(title, "a", "à", "g"); + hre.replaceDestructive(title, "a", "ä", "g"); + hre.replaceDestructive(title, "a", "â", "g"); + hre.replaceDestructive(title, "a", "ã", "g"); + hre.replaceDestructive(title, "a", "å", "g"); + hre.replaceDestructive(title, "e", "é", "g"); + hre.replaceDestructive(title, "e", "è", "g"); + hre.replaceDestructive(title, "e", "ë", "g"); + hre.replaceDestructive(title, "e", "ê", "g"); + hre.replaceDestructive(title, "i", "í", "g"); + hre.replaceDestructive(title, "i", "ì", "g"); + hre.replaceDestructive(title, "i", "ï", "g"); + hre.replaceDestructive(title, "i", "î", "g"); + hre.replaceDestructive(title, "o", "ó", "g"); + hre.replaceDestructive(title, "o", "ò", "g"); + hre.replaceDestructive(title, "o", "ö", "g"); + hre.replaceDestructive(title, "o", "ô", "g"); + hre.replaceDestructive(title, "o", "õ", "g"); + hre.replaceDestructive(title, "o", "ø", "g"); + hre.replaceDestructive(title, "u", "ú", "g"); + hre.replaceDestructive(title, "u", "ù", "g"); + hre.replaceDestructive(title, "u", "ü", "g"); + hre.replaceDestructive(title, "u", "û", "g"); + hre.replaceDestructive(title, "y", "ý", "g"); + hre.replaceDestructive(title, "y", "ÿ", "g"); + hre.replaceDestructive(title, "n", "ñ", "g"); + hre.replaceDestructive(title, "c", "ç", "g"); + hre.replaceDestructive(title, "a", "ą", "g"); + hre.replaceDestructive(title, "c", "ć", "g"); + hre.replaceDestructive(title, "e", "ę", "g"); + hre.replaceDestructive(title, "l", "ł", "g"); + hre.replaceDestructive(title, "n", "ń", "g"); + hre.replaceDestructive(title, "s", "ś", "g"); + hre.replaceDestructive(title, "z", "ź", "g"); + hre.replaceDestructive(title, "z", "ż", "g"); + hre.replaceDestructive(title, "", "[^a-zA-Z0-9-_.]", "g"); + + std::transform(title.begin(), title.end(), title.begin(), + [](unsigned char c) { return std::tolower(c); }); + + string output; + if (!prefix.empty()) { + output += prefix + "-"; + } else if (!source.empty()) { + if (hre.search(source, "^DWOK(\\d+)$")) { + string volume = hre.getMatch(1); + if (volume.size() == 1) { + volume = "0" + volume; + } + if (!sig.empty()) { + if (hre.search(sig, "^(\\d\\d)")) { + string volume2 = hre.getMatch(1); + if (volume == volume2) { + source = "DWOK"; + output += source; + } + } else { + output += source + "-"; } + } else { + output += source + "-"; } + } else { + output += source + "-"; } - - out << "\n"; - } else if (data.bar == 2) { - out << "=="; - if (textQ) { - out << "\t=="; - } - out << "\n"; } + output += sig; + if (!(sig.empty() || title.empty())) { + output += "-"; + } + output += title; + if (output.empty()) { + output = "file"; + } + output += m_filePostfix; + + return output; } ////////////////////////////// // -// Tool_esac2hum::getKeyInfo -- look for a KEY[] entry and extract the data. +// Tool_esac2hum::getParameters -- // -// ggg fix this function -// - -bool Tool_esac2hum::getKeyInfo(vector& song, string& key, double& mindur, - int& tonic, string& meter, ostream& out) { - int i; - for (i=0; i<(int)song.size(); i++) { - if (song[i].compare(0, 4, "KEY[") == 0) { - key = song[i][4]; // letter - key += song[i][5]; // number - key += song[i][6]; // number - key += song[i][7]; // number - key += song[i][8]; // number - if (!isspace(song[i][9])) { - key += song[i][9]; // optional letter (sometimes ' or ") - } - if (!isspace(song[i][10])) { - key += song[i][10]; // illegal but possible extra letter - } - if (song[i][10] != ' ') { - out << "!! Warning key field is not complete" << endl; - out << "!!Key field: " << song[i] << endl; - } - - mindur = (song[i][11] - '0') * 10 + (song[i][12] - '0'); - mindur = 4.0 / mindur; - string tonicstr; - if (song[i][14] != ' ') { - tonicstr[0] = song[i][14]; - if (tolower(song[i][15]) == 'b') { - tonicstr[1] = '-'; +void Tool_esac2hum::getParameters(vector& infile) { + m_score.m_params.clear(); + HumRegex hre; + bool expectingCloseQ = false; + string lastKey = ""; + for (int i=0; i<(int)infile.size(); i++) { + if (hre.search(infile[i], "^\\s*$")) { + continue; + } + if ((i == 0) && hre.search(infile[i], "^([A-Z_a-z][^\\]\\[]*)\\s*$")) { + m_score.m_params["_source"] = hre.getMatch(1); + continue; + } + if (expectingCloseQ) { + if (infile[i].find("[") != string::npos) { + cerr << "Strange case searching for close: " << infile[i] << endl; + } else if (infile[i].find("]") == string::npos) { + // continuing a parameter: + if (lastKey == "") { + cerr << "Strange case of no last key when closing parameter: " << infile[i] << endl; } else { - tonicstr[1] = song[i][15]; + m_score.m_params[lastKey] += "\n" + infile[i]; + } + } else if (hre.search(infile[i], "^([^\\]]+)\\]\\s*$")) { + // closing a parameter: + if (lastKey == "") { + cerr << "Strange case B of no last key when closing parameter: " << infile[i] << endl; + } else { + string value = hre.getMatch(1); + m_score.m_params[lastKey] += "\n" + value; + expectingCloseQ = false; + continue; } - tonicstr[2] = '\0'; } else { - tonicstr = song[i][15]; + cerr << "Problem closing parameter: " << infile[i] << endl; } + continue; + } else if (hre.search(infile[i], "^\\s*([A-Z_a-z]+)\\s*\\[([^\\]]*)\\]\\s*$")) { + // single line parameter + string key = hre.getMatch(1); + string value = hre.getMatch(2); - // convert German notation to English for note names - // Hopefully all references to B will mean English B-flat. - if (tonicstr == "B") { - tonicstr = "B-"; - } - if (tonicstr == "H") { - tonicstr = "B"; - } + // Rare cases where the key has lower case letters that should not be there: + std::transform(key.begin(), key.end(), key.begin(), + [](unsigned char c) { return std::toupper(c); }); - tonic = Convert::kernToBase40(tonicstr); - if (tonic <= 0) { - cerr << "Error: invalid tonic on line: " << song[i] << endl; - return false; - } - tonic = tonic % 40; - meter = song[i].substr(17); - if (meter.back() != ']') { - cerr << "Error with meter on line: " << song[i] << endl; - cerr << "Meter area: " << meter << endl; - cerr << "Expected ] as last character but found " << meter.back() << endl; - return false; - } else { - meter.resize((int)meter.size() - 1); - } - return true; - } - } - cerr << "Error: did not find a KEY field" << endl; - return false; -} + m_score.m_params[key] = value; + continue; + } else if (hre.search(infile[i], "^\\s*([A-Z_a-z]+)\\s*\\[([^\\]]*)\\s*$")) { + // opening of a parameter + string key = hre.getMatch(1); + string value = hre.getMatch(2); + // Rare cases where the key has lower case letters that should not be there: + std::transform(key.begin(), key.end(), key.begin(), + [](unsigned char c) { return std::toupper(c); }); + m_score.m_params[key] = value; + lastKey = key; + expectingCloseQ = true; + continue; + } else if (hre.search(infile[i], "^#")) { + // Do nothing: an external comment, or embedded filter processed + // when filter loading the file. + } else { + cerr << "UNKNOWN CASE: " << infile[i] << endl; + } + } -/////////////////////////////// -// -// Tool_esac2hum::getFileContents -- read a file into the array. -// + // The CUT[] line can be multiple lines, the first being the title and + // the second being the text incipit. Split them into _title and _incipit + // fields (not checking if more than two lines): + string cut = m_score.m_params["CUT"]; + if (hre.search(cut, "^\\s*(.*?)\\n(.*?)\\s*$", "s")) { + m_score.m_params["_title"] = trimSpaces(hre.getMatch(1)); + m_score.m_params["_incipit"] = trimSpaces(hre.getMatch(2)); + } else { + // Don't know if CUT[] is title or incipit, but assign to title. + m_score.m_params["_title"] = trimSpaces(cut); + m_score.m_params["_incipit"] = ""; + } -bool Tool_esac2hum::getFileContents(vector& array, const string& filename) { - ifstream infile(filename.c_str()); - array.reserve(100); - array.resize(0); + string key = m_score.m_params["KEY"]; + if (hre.search(key, "^\\s*([^\\s]+)\\s+(\\d+)\\s+([A-Gacdefg][bs]*)\\s+(.*?)\\s*$")) { + string id = hre.getMatch(1); + string minrhy = hre.getMatch(2); + string tonic = hre.getMatch(3); + if (tonic.size() >= 1) { + if (tonic[0] == 'b') { + cerr << "Error: key signature cannot be 'b'." << endl; + } else { + if (std::islower(tonic[0])) { + cerr << "Warning: Tonic note should be upper case." << endl; + tonic[0] = std::toupper(tonic[0]); + } + } + } + string time = hre.getMatch(4); + m_score.m_params["_id"] = id; + m_score.m_params["_minrhy"] = minrhy; + m_score.m_params["_tonic"] = tonic; + m_score.m_params["_time"] = time; + m_minrhy = stoi(minrhy); + } else { + cerr << "Problem parsing KEY parameter: " << key << endl; + } - if (!infile.is_open()) { - cerr << "Error: cannot open file: " << filename << endl; - return false; + string trd; + if (hre.search(trd, "^\\s*(.*)\\ss\\.")) { + m_score.m_params["_source_trd"] = hre.getMatch(1); + } + if (hre.search(trd, "s\\.\\s*(\\d+-?\\d*)")) { + // Could be text aftewards about the origin of the song. + m_score.m_params["_page"] = hre.getMatch(1); } - char holdbuffer[1024] = {0}; + if (m_debugQ) { + printParameters(); + } - infile.getline(holdbuffer, 256, '\n'); - while (!infile.eof()) { - array.push_back(holdbuffer); - infile.getline(holdbuffer, 256, '\n'); + if (hre.search(m_score.m_params["_source_trd"], "^\\s*(DWOK\\d+)")) { + m_dwokQ = true; + } else if (hre.search(m_score.m_params["_source"], "^\\s*(DWOK\\d+)")) { + m_dwokQ = true; } - infile.close(); - return true; } ////////////////////////////// // -// Tool_esac2hum::example -- +// Tool_esac2hum::printParameters -- // -void Tool_esac2hum::example(void) { - - +void Tool_esac2hum::printParameters(void) { + cerr << endl; + cerr << "========================================" << endl; + for (const auto& [key, value] : m_score.m_params) { + cerr << "Key: " << key << ", Value: " << value << endl; + } + cerr << "========================================" << endl; + cerr << endl; } ////////////////////////////// // -// Tool_esac2hum::usage -- +// Tool_esac2hum::printBemComment -- // -void Tool_esac2hum::usage(const string& command) { - +void Tool_esac2hum::printBemComment(ostream& output) { + string bem = m_score.m_params["BEM"]; + if (bem.empty()) { + return; + } + string english = m_bem_translation[bem]; + if (english.empty()) { + output << "!!!ONB: " << bem << endl; + } else { + output << "!!!ONB@@PL: " << bem << endl; + output << "!!!ONB@@EN: " << english << endl; + } } ////////////////////////////// // -// Tool_esac2hum::printBibInfo -- +// Tool_esac2hum::printFooter -- // -void Tool_esac2hum::printBibInfo(vector& song, ostream& out) { - int i, j; - char buffer[32] = {0}; - int start = -1; - int stop = -1; - int count = 0; - string templine; - - for (i=0; i<(int)song.size(); i++) { - if (song[i] == "") { - continue; - } - if (song[i][0] != ' ') { - if (song[i].size() < 4 || song[i][3] != '[') { - if (song[i].compare(0, 2, "!!") != 0) { - out << "!! " << song[i] << "\n"; - } - continue; - } - strncpy(buffer, song[i].c_str(), 3); - buffer[3] = '\0'; - if (strcmp(buffer, "MEL") == 0) continue; - if (strcmp(buffer, "TXT") == 0) continue; - // if (strcmp(buffer, "KEY") == 0) continue; - getLineRange(song, buffer, start, stop); +void Tool_esac2hum::printFooter(ostream& output, vector& infile) { + output << "*-" << endl; - // don't print CUT field if only one line. !!!OTL: will contain CUT[] - // if (strcmp(buffer, "CUT") == 0 && start == stop) continue; + printBemComment(output); + printPdfLinks(output); + printPageNumbers(output); + printConversionDate(output); - buffer[0] = tolower(buffer[0]); - buffer[1] = tolower(buffer[1]); - buffer[2] = tolower(buffer[2]); - count = 1; - templine = ""; - for (j=start; j<=stop; j++) { - if (song[j].size() < 4) { - continue; - } - if (stop - start == 0) { - templine = song[j].substr(4); - auto loc = templine.find(']'); - if (loc != string::npos) { - templine.resize(loc); - } - if (templine != "") { - out << "!!!" << buffer << ": "; - printString(templine, out); - out << "\n"; - } + if (m_embedEsacQ) { + output << "!!@@BEGIN: ESAC" << endl; + output << "!!@CONTENTS:" << endl;; + for (int i=0; i<(int)infile.size(); i++) { + output << "!!" << infile[i] << endl; + } + if (m_analysisQ) { + embedAnalyses(output); + } + output << "!!@@END: ESAC" << endl; + } - } else if (j==start) { - out << "!!!" << buffer << count++ << ": "; - printString(song[j].substr(4), out); - out << "\n"; - } else if (j==stop) { - templine = song[j].substr(4); - auto loc = templine.find(']'); - if (loc != string::npos) { - templine.resize(loc); - } - if (templine != "") { - out << "!!!" << buffer << count++ << ": "; - printString(templine, out); - out << "\n"; - } - } else { - out << "!!!" << buffer << count++ << ": "; - printString(&(song[j][4]), out); - out << "\n"; - } - } + if (!m_globalComments.empty()) { + for (int i=0; i<(int)m_globalComments.size(); i++) { + output << m_globalComments.at(i) << endl; } } } -////////////////////////////// +/////////////////////////////// // -// Tool_esac2hum::printString -- print characters in string. +// Tool_esac2hum::printPageNumbers -- // -void Tool_esac2hum::printString(const string& string, ostream& out) { - for (int i=0; i<(int)string.size(); i++) { - printChar(string[i], out); +void Tool_esac2hum::printPageNumbers(ostream& output) { + HumRegex hre; + string trd = m_score.m_params["TRD"]; + if (hre.search(trd, "\\bs\\.\\s*(\\d+)\\s*-\\s*(\\d+)", "i")) { + output << "!!!page: " << hre.getMatch(1) << "-" << hre.getMatch(2) << endl; + } else if (hre.search(trd, "\\bs\\.\\s*(\\d+)", "i")) { + output << "!!!page: " << hre.getMatch(1) << endl; } } +/////////////////////////////// +// +// Tool_esac::embedAnalyses -- +// + +void Tool_esac2hum::embedAnalyses(ostream& output) { + m_score.doAnalyses(); + string MEL_SEM = m_score.m_params["MEL_SEM"]; + string MEL_RAW = m_score.m_params["MEL_RAW"]; + string NO_REP = m_score.m_params["NO_REP"]; + string RTM = m_score.m_params["RTM"]; + string SCL_DEG = m_score.m_params["SCL_DEG"]; + string SCL_SEM = m_score.m_params["SCL_SEM"]; + string PHR_NO = m_score.m_params["PHR_NO"]; + string PHR_BARS = m_score.m_params["PHR_BARS"]; + string PHR_CAD = m_score.m_params["PHR_CAD"]; + string ACC = m_score.m_params["ACC"]; + + bool allEmptyQ = true; + if (!MEL_SEM.empty() ) { allEmptyQ = false; } + else if (!MEL_RAW.empty() ) { allEmptyQ = false; } + else if (!NO_REP.empty() ) { allEmptyQ = false; } + else if (!RTM.empty() ) { allEmptyQ = false; } + else if (!SCL_DEG.empty() ) { allEmptyQ = false; } + else if (!SCL_SEM.empty() ) { allEmptyQ = false; } + else if (!PHR_NO.empty() ) { allEmptyQ = false; } + else if (!PHR_BARS.empty()) { allEmptyQ = false; } + else if (!PHR_CAD.empty() ) { allEmptyQ = false; } + else if (!ACC.empty() ) { allEmptyQ = false; } + + if (allEmptyQ) { + // no analyses for some strange reason. + return; + } + output << "!!@ANALYSES:" << endl; + if (!MEL_SEM.empty() ) { output << "!!MEL_SEM[" << MEL_SEM << "]" << endl; } + if (!MEL_RAW.empty() ) { output << "!!MEL_RAW[" << MEL_RAW << "]" << endl; } + if (!NO_REP.empty() ) { output << "!!NO_REP[" << NO_REP << "]" << endl; } + if (!RTM.empty() ) { output << "!!RTM[" << RTM << "]" << endl; } + if (!SCL_DEG.empty() ) { output << "!!SCL_DEG[" << SCL_DEG << "]" << endl; } + if (!SCL_SEM.empty() ) { output << "!!SCL_SEM[" << SCL_SEM << "]" << endl; } + if (!PHR_NO.empty() ) { output << "!!PHR_NO[" << PHR_NO << "]" << endl; } + if (!PHR_BARS.empty()) { output << "!!PHR_BARS[" << PHR_BARS << "]" << endl; } + if (!PHR_CAD.empty() ) { output << "!!PHR_CAD[" << PHR_CAD << "]" << endl; } + if (!ACC.empty() ) { output << "!!ACC[" << ACC << "]" << endl; } + +} -///////////////////////////////// +/////////////////////////////// // -// Tool_extract::Tool_extract -- Set the recognized options for the tool. +// Tool_esac2hum::printPdfLinks -- // -Tool_extract::Tool_extract(void) { - define("P|F|S|x|exclude=s:", "remove listed spines from output"); - define("i=s:", "exclusive interpretation list to extract from input"); - define("I=s:", "exclusive interpretation exclusion list"); - define("f|p|s|field|path|spine=s:", "for extraction of particular spines"); - define("C|count=b", "print a count of the number of spines in file"); - define("c|cointerp=s:**kern", "exclusive interpretation for cospines"); - define("g|grep=s:", "extract spines which match a given regex."); - define("r|reverse=b", "reverse order of spines by **kern group"); - define("R=s:**kern", "reverse order of spine by exinterp group"); - define("t|trace=s:", "use a trace file to extract data"); - define("e|expand=b", "expand spines with subspines"); - define("k|kern=s", "extract by kern spine group"); - define("K|reverse-kern=s", "extract by kern spine group top to bottom numbering"); - define("E|expand-interp=s:", "expand subspines limited to exinterp"); - define("m|model|method=s:d", "method for extracting secondary spines"); - define("M|cospine-model=s:d", "method for extracting cospines"); - define("Y|no-editoral-rests=b", "do not display yy marks on interpreted rests"); - define("n|name|b|blank=s:**blank", "name if exinterp added with 0"); - define("no-empty|no-empties=b", "suppress spines with only null data tokens"); - define("empty|empties=b", "only keep spines with only null data tokens"); - define("spine-list=b", "show spine list and then exit"); - define("no-rest|no-rests=b", "remove **kern spines containing only rests (and their co-spines)"); +void Tool_esac2hum::printPdfLinks(ostream& output) { + output << "!!!URL: http://webesac.pcss.pl WebEsAC" << endl; + + if (!m_dwokQ) { + return; + } + + output << "!!!URL: https::kolberg.ispan.pl/dwok/tomy Oskar Kolberg: Complete Works digital edition" << endl; + + string source = m_score.m_params["_source"]; + HumRegex hre; + if (!hre.search(source, "^DWOK(\\d+)")) { + return; + } + string volume = hre.getMatch(1); + if (volume.size() == 1) { + volume = "0" + volume; + } + if (volume.size() == 2) { + volume = "0" + volume; + } + if (volume.size() > 3) { + return; + } + string nozero = volume; + hre.replaceDestructive(nozero, "" , "^0+"); + // need http:// not https:// for the following PDF link: + output << "!!!URL-pdf: http://oskarkolberg.pl/MediaFiles/" << volume << "dwok.pdf" << " Oskar Kolberg: Complete Works, volume " << nozero << endl; - define("debug=b", "print debugging information"); - define("author=b", "author of the program"); - define("version=b", "compilation info"); - define("example=b", "example usages"); - define("h|help=b", "short description"); } -///////////////////////////////// +/////////////////////////////// // -// Tool_extract::run -- Primary interfaces to the tool. +// Tool_esac2hum::printCoversionDate -- // -bool Tool_extract::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i notelist; + getNoteList(notelist); + + vector b12s; // list of notes to calculate intervals between + + for (int i=0; i<(int)notelist.size(); i++) { + if (notelist[i]->isRest()) { + continue; + } + if (notelist[i]->m_tieEnd) { + continue; + } + b12s.push_back(notelist[i]->m_b12); } - return status; + + string output; + for (int i=1; i<(int)b12s.size(); i++) { + int difference = b12s[i] - b12s[i-1]; + output += to_string(difference); + if (i < (int)b12s.size() - 1) { + output += " "; + } + } + + m_params["MEL_SEM"] = output; } + + +////////////////////////////// // -// In-place processing of file: +// Tool_esac2hum::Score::analyzeMEL_RAW -- Remove rhythms from MEL[] data. +// Preserve spaces as in original MEL[]; +// What to do with parentheses? Currently removed. +// What to do with tied notes? Currently removed. // -bool Tool_extract::run(HumdrumFile& infile) { - initialize(infile); - processFile(infile); - // Re-load the text for each line from their tokens. - // infile.createLinesFromTokens(); - return true; +void Tool_esac2hum::Score::analyzeMEL_RAW(void) { + string output = m_params["MEL"]; + HumRegex hre; + hre.replaceDestructive(output, "", "[^\\d+\\sb#-]+", "g"); + hre.replaceDestructive(output, "", "\\s*//\\s*$"); + hre.replaceDestructive(output, "\n!!", "\n", "g"); + m_params["MEL_RAW"] = output; } ////////////////////////////// // -// Tool_extract::processFile -- +// Tool_esac2hum::Score::analyzeNO_REP -- Return +// the non-repeated notes/rests without rhythms +// in each phrase with a newlines between phrases +// and no spaces between notes or measures. // -void Tool_extract::processFile(HumdrumFile& infile) { - if (countQ) { - m_free_text << infile.getMaxTrack() << endl; - return; - } - if (expandQ) { - expandSpines(field, subfield, model, infile, expandInterp); - } else if (interpQ) { - getInterpretationFields(field, subfield, model, infile, interps, - interpstate); - } else if (reverseQ) { - reverseSpines(field, subfield, model, infile, reverseInterp); - } else if (removerestQ) { - fillFieldDataByNoRest(field, subfield, model, grepString, infile, - interpstate); - } else if (grepQ) { - fillFieldDataByGrep(field, subfield, model, grepString, infile, - interpstate); - } else if (emptyQ) { - fillFieldDataByEmpty(field, subfield, model, infile, interpstate); - } else if (noEmptyQ) { - fillFieldDataByNoEmpty(field, subfield, model, infile, interpstate); - } else if (fieldQ || excludeQ) { - fillFieldData(field, subfield, model, fieldstring, infile); +void Tool_esac2hum::Score::analyzeNO_REP(void) { + string output; + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + string line = phrase.getNO_REP(); + if (i > 0) { + output += "\n "; + } + output += line; } - if (spineListQ) { - m_free_text << "-s "; - for (int i=0; i<(int)field.size(); i++) { - m_free_text << field[i]; - if (i < (int)field.size() - 1) { - m_free_text << ","; - } + HumRegex hre; + hre.replaceDestructive(output, "\n!!", "\n", "g"); + + m_params["NO_REP"] = output; +} + + + +////////////////////////////// +// +// Tool_esac2hum::Phrase::getNO_REP -- Return +// the non-repeated notes/rests without rhythms +// with no spaces between notes or measures. +// What to do if line starts with an ending tied note? +// Currently ignoring leading tied end notes. +// + +string Tool_esac2hum::Phrase::getNO_REP(void) { + vector notelist; + getNoteList(notelist); + string output; + int foundNonTie = false; + string lastitem = ""; + for (int i=0; i<(int)notelist.size(); i++) { + if (!foundNonTie && notelist[i]->m_tieEnd) { + continue; + } + foundNonTie = true; + string curitem = notelist[i]->getScaleDegree(); + if (curitem != lastitem) { + output += curitem; + lastitem = curitem; } - m_free_text << endl; - return; } + return output; +} - if (debugQ && !traceQ) { - m_free_text << "!! Field Expansion List:"; - for (int j=0; j<(int)field.size(); j++) { - m_free_text << " " << field[j]; - if (subfield[j]) { - m_free_text << (char)subfield[j]; - } - if (model[j]) { - m_free_text << (char)model[j]; - } + + +////////////////////////////// +// +// Tool_esac2hum::Score::analyzeRTM -- Convert pitches/rests to "x". +// What to do with tied notes? Leaving ^ in for now. +// What to do with ()? Removing for now. +// + +void Tool_esac2hum::Score::analyzeRTM(void) { + string output = m_params["MEL"]; + HumRegex hre; + hre.replaceDestructive(output, "", "[()]+", "g"); + hre.replaceDestructive(output, "x", "[+-]*(\\d|\\^)[b#]*", "g"); + hre.replaceDestructive(output, "", "\\s*//\\s*$"); + hre.replaceDestructive(output, "\n!!", "\n", "g"); + m_params["RTM"] = output; +} + + + +////////////////////////////// +// +// Tool_esac2hum::Score::analyzeSCL_DEG -- List of scale degrees +// present in melody from lowest to highest with no spaces between +// the scale degrees. +// + +void Tool_esac2hum::Score::analyzeSCL_DEG(void) { + vector notelist; + getNoteList(notelist); + map list; + for (int i=0; i<(int)notelist.size(); i++) { + if (notelist[i]->isRest()) { + continue; } - m_free_text << endl; + if (notelist[i]->m_tieEnd) { + continue; + } + int b40 = notelist[i]->m_b40; + list[b40] = notelist[i]; } - // preserve SEGMENT filename if present (now printed in main()) - // infile.printNonemptySegmentLabel(m_humdrum_text); - - // analyze the input file according to command-line options - if (fieldQ || grepQ || removerestQ) { - extractFields(infile, field, subfield, model); - } else if (excludeQ) { - excludeFields(infile, field, subfield, model); - } else if (traceQ) { - extractTrace(infile, tracefile); - } else { - m_humdrum_text << infile; + string output; + for (const auto& pair : list) { + output += pair.second->getScaleDegree(); } + m_params["SCL_DEG"] = output; } ////////////////////////////// // -// Tool_extract::getNullDataTracks -- +// Tool_esac2hum::Score::analyzeSCL_SEM -- Get the semitone +// between scale degrees in SCL_DEG analysis. // -vector Tool_extract::getNullDataTracks(HumdrumFile& infile) { - vector output(infile.getMaxTrack() + 1, 1); - for (int i=0; i notelist; + getNoteList(notelist); + map list; + for (int i=0; i<(int)notelist.size(); i++) { + if (notelist[i]->isRest()) { continue; } - for (int j=0; jgetTrack(); - if (!output[track]) { - continue; - } - if (!token->isNull()) { - output[track] = 0; - } + if (notelist[i]->m_tieEnd) { + continue; } - // maybe exit here if all tracks are non-null + int b40 = notelist[i]->m_b40; + list[b40] = notelist[i]; } - return output; + string output; + Tool_esac2hum::Note* lastnote = nullptr; + for (const auto& pair : list) { + if (lastnote == nullptr) { + lastnote = pair.second; + continue; + } + int second = pair.second->m_b12; + int first = lastnote->m_b12; + int difference = second -first; + if (!output.empty()) { + output += " "; + } + output += to_string(difference); + lastnote = pair.second; + } + m_params["SCL_SEM"] = output; } ////////////////////////////// // -// Tool_extract::fillFieldDataByEmpty -- Only keep the spines which contain only -// null data tokens. +// Tool_esac2hum::Score::analyzePHR_NO -- // -void Tool_extract::fillFieldDataByEmpty(vector& field, vector& subfield, - vector& model, HumdrumFile& infile, int negate) { +void Tool_esac2hum::Score::analyzePHR_NO(void) { + int phraseCount = (int)size(); + m_params["PHR_NO"] = to_string(phraseCount); +} - field.reserve(infile.getMaxTrack()+1); - subfield.reserve(infile.getMaxTrack()+1); - model.reserve(infile.getMaxTrack()+1); - field.resize(0); - subfield.resize(0); - model.resize(0); - vector nullTrack = getNullDataTracks(infile); - int zero = 0; - for (int i=1; i<(int)nullTrack.size(); i++) { - if (negate) { - if (!nullTrack[i]) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } else { - if (nullTrack[i]) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } + +////////////////////////////// +// +// Tool_esac2hum::Score::analyzePHR_BARS -- Return the number +// of measures in each phrase. +// + +void Tool_esac2hum::Score::analyzePHR_BARS(void) { + string output; + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + int barCount = phrase.getFullMeasureCount(); + output += to_string(barCount); + if (i < (int)size() - 1) { + output += " "; } } - + m_params["PHR_BARS"] = output; } ////////////////////////////// // -// Tool_extract::fillFieldDataByNoEmpty -- Only keep spines which are not all -// null data tokens. +// Tool_esac2hum:::Phrase::getFullMeasureCount -- Return the number +// of measures, but subtrack one if the first measure is a +// partialEnd and the last is a partialBegin. // -void Tool_extract::fillFieldDataByNoEmpty(vector& field, vector& subfield, - vector& model, HumdrumFile& infile, int negate) { +int Tool_esac2hum::Phrase::getFullMeasureCount(void) { + int measureCount = (int)size(); + if (measureCount < 2) { + return measureCount; + } + if (at(0).isPartialEnd() && back().isPartialBegin()) { + measureCount--; + } - field.reserve(infile.getMaxTrack()+1); - subfield.reserve(infile.getMaxTrack()+1); - model.reserve(infile.getMaxTrack()+1); - field.resize(0); - subfield.resize(0); - model.resize(0); - vector nullTrack = getNullDataTracks(infile); - for (int i=1; i<(int)nullTrack.size(); i++) { - nullTrack[i] = !nullTrack[i]; + // if the fist is partial and the last is not, also -1 + if (at(0).isPartialEnd() && back().isComplete()) { + measureCount--; } - int zero = 0; - for (int i=1; i<(int)nullTrack.size(); i++) { - if (negate) { - if (!nullTrack[i]) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } else { - if (nullTrack[i]) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } + // if the fist is complete and the last is incomplete, also -1 + if (at(0).isComplete() && back().isPartialBegin()) { + measureCount--; } + + + // what to do if first measure is pickup (and maybe last measure)? + return measureCount; } ////////////////////////////// // -// Tool_extract::fillFieldDataByNoRest -- Find the spines which -// contain only rests and remove them. Also remove cospines (non-kern spines -// to the right of the kern spine containing only rests). If there are -// *part# interpretations in the data, then any spine which is all rests -// will not be removed if there is another **kern spine with the same -// part number if it is also not all rests. +// Tool_esac2hum::Score::analyzePHR_CAD -- Give a space-delimited +// list of the last scale degree of each phrase. // -void Tool_extract::fillFieldDataByNoRest(vector& field, vector& subfield, - vector& model, const string& searchstring, HumdrumFile& infile, - int state) { +void Tool_esac2hum::Score::analyzePHR_CAD(void) { + string output; + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + output += phrase.getLastScaleDegree(); + if (i < (int)size() - 1) { + output += " "; + } + } + m_params["PHR_CAD"] = output; +} - field.clear(); - subfield.clear(); - model.clear(); - // Check every **kern spine for any notes. If there is a note - // then the tracks variable for that spine will be marked - // as non-zero. - vector tracks(infile.getMaxTrack() + 1, 0); - int track; - int partline = 0; - bool dataQ = false; - for (int i=0; i notelist; + getNoteList(notelist); - } - if (!infile[i].isData()) { - continue; - } - dataQ = true; - for (int j=0; jisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - if (token->isRest()) { - continue; - } - track = token->getTrack(); - tracks[track] = 1; + for (int i=(int)notelist.size() - 1; i>=0; i--) { + if (notelist[i]->isPitch()) { + return notelist[i]->getScaleDegree(); } } - // Go back and mark any empty spines as non-empty if they - // are in a part that contains multiple staves. I.e., only - // delete a staff if all staves for the part are empty. - // There should be a single *part# line at the start of the - // score. - if (partline > 0) { - vector kerns; - for (int i=0; iisKern()) { - continue; - } - kerns.push_back(token); - } - for (int i=0; i<(int)kerns.size(); i++) { - for (int j=i+1; j<(int)kerns.size(); j++) { - if (*kerns[i] != *kerns[j]) { - continue; - } - if (kerns[i]->find("*part") == string::npos) { - continue; - } - int track1 = kerns[i]->getTrack(); - int track2 = kerns[j]->getTrack(); - int state1 = tracks[track1]; - int state2 = tracks[track2]; - if ((state1 && !state2) || (state2 && !state1)) { - // Prevent empty staff from being removed - // from a multi-staff part: - tracks[track1] = 1; - tracks[track2] = 1; - } - } - } - } + return "?"; +} - // deal with co-spines - vector sstarts; - infile.getSpineStartList(sstarts); - for (int i=0; i<(int)sstarts.size(); i++) { - if (!sstarts[i]->isKern()) { - track = sstarts[i]->getTrack(); - tracks[track] = 1; - } - } +////////////////////////////// +// +// Tool_esac2hum::Note::getScaleDegree -- return the scale degree +// string for the note, such as: 6, -6, +7b, 5#. +// - // remove co-spines attached to removed kern spines - for (int i=0; i<(int)sstarts.size(); i++) { - if (!sstarts[i]->isKern()) { - continue; - } - if (tracks[sstarts[i]->getTrack()] != 0) { - continue; +string Tool_esac2hum::Note::getScaleDegree(void) { + string output; + if (m_octave < 0) { + for (int i=0; i<-m_octave; i++) { + output += "-"; } - for (int j=i+1; j<(int)sstarts.size(); j++) { - if (sstarts[j]->isKern()) { - break; - } - track = sstarts[j]->getTrack(); - tracks[track] = 0; + } else if (m_octave > 0) { + for (int i=0; i 0) { + for (int i=0; i& field, vector& subfield, - vector& model, const string& searchstring, HumdrumFile& infile, - int state) { - - field.reserve(infile.getMaxTrack()+1); - subfield.reserve(infile.getMaxTrack()+1); - model.reserve(infile.getMaxTrack()+1); - field.resize(0); - subfield.resize(0); - model.resize(0); - - vector tracks; - tracks.resize(infile.getMaxTrack()+1); - fill(tracks.begin(), tracks.end(), 0); - HumRegex hre; - int track; - - int i, j; - for (i=0; igetTrack(); - tracks[track] = 1; - } - } - } - - int zero = 0; - for (i=1; i<(int)tracks.size(); i++) { - if (state != 0) { - tracks[i] = !tracks[i]; - } - if (tracks[i]) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } +bool Tool_esac2hum::Note::isPitch(void) { + return (m_degree > 0); } ////////////////////////////// // -// Tool_extract::getInterpretationFields -- +// Tool_esac2hum::Note::isRest -- return true if scale degree is 0. // -void Tool_extract::getInterpretationFields(vector& field, vector& subfield, - vector& model, HumdrumFile& infile, string& interps, int state) { - vector sstrings; // search strings - sstrings.reserve(100); - sstrings.resize(0); - - int i, j, k; - string buffer; - buffer = interps; - - HumRegex hre; - hre.replaceDestructive(buffer, "", "\\s+", "g"); +bool Tool_esac2hum::Note::isRest(void) { + return (m_degree <= 0); +} - int start = 0; - while (hre.search(buffer, start, "^([^,]+)")) { - sstrings.push_back(hre.getMatch(1)); - start = hre.getMatchEndIndex(1); - } - if (debugQ) { - m_humdrum_text << "!! Interpretation strings to search for: " << endl; - for (i=0; i<(int)sstrings.size(); i++) { - m_humdrum_text << "!!\t" << sstrings[i] << endl; - } - } - vector tracks; - tracks.resize(infile.getMaxTrack()+1); - fill(tracks.begin(), tracks.end(), 0); +////////////////////////////// +// +// Tool_esac2hum::Score::analyzeACC -- The first scale degree +// of each (complete) meausre, or partial measure start. +// the scale degress for each phrase are placed into a word +// without spaces, and then a space between each phrase. +// +// Todo: Deal with tied notes at starts of measures. +// - // Algorithm below could be made more efficient by - // not searching the entire file... - for (i=0; igetTrack()] = 1; - } +void Tool_esac2hum::Score::analyzeACC(void) { + string output; + for (int i=0; i<(int)size(); i++) { + Tool_esac2hum::Phrase& phrase = at(i); + for (int j=0; j<(int)phrase.size(); j++) { + Tool_esac2hum::Measure& measure = phrase.at(j); + if (measure.isComplete()) { + output += measure.at(0).getScaleDegree(); } } + if (i < (int)size() -1) { + output += " "; + } } + m_params["ACC"] = output; +} - field.reserve(tracks.size()); - subfield.reserve(tracks.size()); - model.reserve(tracks.size()); - field.resize(0); - subfield.resize(0); - model.resize(0); - int zero = 0; - for (i=1; i<(int)tracks.size(); i++) { - if (state == 0) { - tracks[i] = !tracks[i]; - } - if (tracks[i]) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } +///////////////////////////////// +// +// Tool_esac2humold::Tool_esac2humold -- Set the recognized options for the tool. +// + +Tool_esac2humold::Tool_esac2humold(void) { + define("debug=b", "print debug information"); + define("v|verbose=b", "verbose output"); + define("h|header=s:", "header filename for placement in output"); + define("t|trailer=s:", "trailer filename for placement in output"); + define("s|split=s:file", "split song info into separate files"); + define("x|extension=s:.krn", "split filename extension"); + define("f|first=i:1", "number of first split filename"); + define("author=b", "author of program"); + define("version=b", "compilation info"); + define("example=b", "example usages"); + define("help=b", "short description"); } ////////////////////////////// // -// Tool_extract::expandSpines -- +// Tool_esac2humold::convert -- Convert a MusicXML file into +// Humdrum content. // -void Tool_extract::expandSpines(vector& field, vector& subfield, vector& model, - HumdrumFile& infile, string& interp) { +bool Tool_esac2humold::convertFile(ostream& out, const string& filename) { + ifstream file(filename); + stringstream s; + if (file) { + s << file.rdbuf(); + file.close(); + } + return convert(out, s.str()); +} - vector splits; - splits.resize(infile.getMaxTrack()+1); - fill(splits.begin(), splits.end(), 0); - int i, j; - for (i=0; igetSpineInfo().c_str(), '(') != NULL) { - splits[infile[i].token(j)->getTrack()] = 1; - } - } - } - field.reserve(infile.getMaxTrack()*2); - field.resize(0); +bool Tool_esac2humold::convert(ostream& out, const string& input) { + stringstream ss; + ss << input; + convertEsacToHumdrum(out, ss); + return true; +} - subfield.reserve(infile.getMaxTrack()*2); - subfield.resize(0); - model.reserve(infile.getMaxTrack()*2); - model.resize(0); - bool allQ = interp.empty(); - vector dummyfield; - vector dummysubfield; - vector dummymodel; - getInterpretationFields(dummyfield, dummysubfield, model, infile, interp, 1); +////////////////////////////// +// +// Tool_esac2humold::initialize -- +// - vector interptracks; +bool Tool_esac2humold::initialize(void) { + // handle basic options: + if (getBoolean("author")) { + cerr << "Written by Craig Stuart Sapp, " + << "craig@ccrma.stanford.edu, March 2002" << endl; + return false; + } else if (getBoolean("version")) { + cerr << getCommand() << ", version: 6 June 2017" << endl; + cerr << "compiled: " << __DATE__ << endl; + return false; + } else if (getBoolean("help")) { + usage(getCommand()); + return false; + } else if (getBoolean("example")) { + example(); + return false; + } - interptracks.resize(infile.getMaxTrack()+1); - fill(interptracks.begin(), interptracks.end(), 0); + debugQ = getBoolean("debug"); + verboseQ = getBoolean("verbose"); - for (i=0; i<(int)dummyfield.size(); i++) { - interptracks[dummyfield[i]] = 1; + if (getBoolean("header")) { + if (!getFileContents(header, getString("header"))) { + return false; + } + } else { + header.resize(0); } - - int aval = 'a'; - int bval = 'b'; - int zero = 0; - for (i=1; i<(int)splits.size(); i++) { - if (splits[i] && (allQ || interptracks[i])) { - field.push_back(i); - subfield.push_back(aval); - model.push_back(zero); - field.push_back(i); - subfield.push_back(bval); - model.push_back(zero); - } else { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); + if (getBoolean("trailer")) { + if (!getFileContents(trailer, getString("trailer"))) { + return false; } + } else { + trailer.resize(0); } - if (debugQ) { - m_humdrum_text << "!!expand: "; - for (i=0; i<(int)field.size(); i++) { - m_humdrum_text << field[i]; - if (subfield[i]) { - m_humdrum_text << (char)subfield[i]; - } - if (i < (int)field.size()-1) { - m_humdrum_text << ","; - } - } - m_humdrum_text << endl; + if (getBoolean("split")) { + splitQ = 1; } + namebase = getString("split"); + fileextension = getString("extension"); + firstfilenum = getInteger("first"); + return true; } +////////////////////////////////////////////////////////////////////////// + + ////////////////////////////// // -// Tool_extract::reverseSpines -- reverse the order of spines, grouped by the -// given exclusive interpretation. +// Tool_esac2humold::convertEsacToHumdrum -- // -void Tool_extract::reverseSpines(vector& field, vector& subfield, - vector& model, HumdrumFile& infile, const string& exinterp) { - - vector target; - target.resize(infile.getMaxTrack()+1); - fill(target.begin(), target.end(), 0); - - vector trackstarts; - infile.getSpineStartList(trackstarts); - - for (int t=0; t<(int)trackstarts.size(); t++) { - if (trackstarts[t]->isDataType(exinterp)) { - target.at(t + 1) = 1; +void Tool_esac2humold::convertEsacToHumdrum(ostream& output, istream& infile) { + initialize(); + vector song; + song.reserve(400); + int init = 0; + // int filecounter = firstfilenum; + string outfilename; + string numberstring; + // ofstream outfile; + while (!infile.eof()) { + if (debugQ) { + cerr << "Getting a song..." << endl; + } + getSong(song, infile, init); + if (debugQ) { + cerr << "Got a song ..." << endl; } + init = 1; + convertSong(song, output); } +} - field.reserve(infile.getMaxTrack()*2); - field.resize(0); - int lasti = (int)target.size(); - for (int i=(int)target.size()-1; i>0; i--) { - if (target[i]) { - lasti = i; - field.push_back(i); - for (int j=i+1; j<(int)target.size(); j++) { - if (!target.at(j)) { - field.push_back(j); - } else { - break; - } + +////////////////////////////// +// +// Tool_esac2humold::getSong -- get a song from the EsAC file +// + +bool Tool_esac2humold::getSong(vector& song, istream& infile, int init) { + string holdbuffer; + song.resize(0); + if (init) { + // do nothing holdbuffer has the CUT[] information + } else { + while (!infile.eof() && holdbuffer.compare(0, 4, "CUT[") != 0) { + getline(infile, holdbuffer); + if (verboseQ) { + cerr << "Contents: " << holdbuffer << endl; + } + if (holdbuffer.compare(0, 2, "!!") == 0) { + song.push_back(holdbuffer); } } + if (infile.eof()) { + return false; + } } - // if the grouping spine is not first, then preserve the - // locations of the pre-spines. - int extras = 0; - if (lasti != 1) { - extras = lasti - 1; - field.resize(field.size()+extras); - for (int i=0; i<(int)field.size()-extras; i++) { - field[(int)field.size()-1-i] = field[(int)field.size()-1-extras-i]; - } - for (int i=0; i& field, vector& subfield, - vector& model, string& fieldstring, HumdrumFile& infile) { - - int maxtrack = infile.getMaxTrack(); - - field.reserve(maxtrack); - field.resize(0); - - subfield.reserve(maxtrack); - subfield.resize(0); - - model.reserve(maxtrack); - model.resize(0); - +void Tool_esac2humold::chopExtraInfo(string& buffer) { HumRegex hre; - string buffer = fieldstring; - hre.replaceDestructive(buffer, "", "\\s", "gs"); - int start = 0; - string tempstr; - vector tempfield; - vector tempsubfield; - vector tempmodel; - while (hre.search(buffer, start, "^([^,]+,?)")) { - tempfield.clear(); - tempsubfield.clear(); - tempmodel.clear(); - processFieldEntry(tempfield, tempsubfield, tempmodel, hre.getMatch(1), infile); - start += hre.getMatchEndIndex(1); - field.insert(field.end(), tempfield.begin(), tempfield.end()); - subfield.insert(subfield.end(), tempsubfield.begin(), tempsubfield.end()); - model.insert(model.end(), tempmodel.begin(), tempmodel.end()); - } + hre.replaceDestructive(buffer, "", "^\\s+"); + hre.replaceDestructive(buffer, "", "\\s+$"); } ////////////////////////////// // -// Tool_extract::processFieldEntry -- -// 3-6 expands to 3 4 5 6 -// $ expands to maximum spine track -// $-1 expands to maximum spine track minus 1, etc. +// Tool_esac2humold::printHumdrumHeaderInfo -- // -void Tool_extract::processFieldEntry(vector& field, - vector& subfield, vector& model, const string& astring, - HumdrumFile& infile) { - - int finitsize = (int)field.size(); - int maxtrack = infile.getMaxTrack(); - - vector ktracks; - infile.getKernSpineStartList(ktracks); - int maxkerntrack = (int)ktracks.size(); +void Tool_esac2humold::printHumdrumHeaderInfo(ostream& out, vector& song) { + for (int i=0; i<(int)song.size(); i++) { + if (song[i].size() == 0) { + continue; + } + if (song[i].compare(0, 2, "!!") == 0) { + out << song[i] << "\n"; + continue; + } + if ((song[i][0] == ' ') || (song[i][0] == '\t')) { + continue; + } + break; + } +} - int modletter; - int subletter; - HumRegex hre; - string buffer = astring; - // remove any comma left at end of input astring (or anywhere else) - hre.replaceDestructive(buffer, "", ",", "g"); +////////////////////////////// +// +// Tool_esac2humold::printHumdrumFooterInfo -- +// - // first remove $ symbols and replace with the correct values - if (kernQ) { - removeDollarsFromString(buffer, maxkerntrack); - } else { - removeDollarsFromString(buffer, maxtrack); - } - - int zero = 0; - if (hre.search(buffer, "^(\\d+)-(\\d+)$")) { - int firstone = hre.getMatchInt(1); - int lastone = hre.getMatchInt(2); - - if ((firstone < 1) && (firstone != 0)) { - m_error_text << "Error: range token: \"" << astring << "\"" - << " contains too small a number at start: " << firstone << endl; - m_error_text << "Minimum number allowed is " << 1 << endl; - return; +void Tool_esac2humold::printHumdrumFooterInfo(ostream& out, vector& song) { + int i = 0; + for (i=0; i<(int)song.size(); i++) { + if (song[i].size() == 0) { + continue; } - if ((lastone < 1) && (lastone != 0)) { - m_error_text << "Error: range token: \"" << astring << "\"" - << " contains too small a number at end: " << lastone << endl; - m_error_text << "Minimum number allowed is " << 1 << endl; - return; + if (song[i].compare(0, 2, "!!") == 0) { + continue; } - if (firstone > maxtrack) { - m_error_text << "Error: range token: \"" << astring << "\"" - << " contains number too large at start: " << firstone << endl; - m_error_text << "Maximum number allowed is " << maxtrack << endl; - return; + if ((song[i][0] == ' ') || (song[i][0] == '\t')) { + continue; } - if (lastone > maxtrack) { - m_error_text << "Error: range token: \"" << astring << "\"" - << " contains number too large at end: " << lastone << endl; - m_error_text << "Maximum number allowed is " << maxtrack << endl; - return; + break; + } + int j = i; + for (j=i; j<(int)song.size(); j++) { + if (song[j].compare(0, 2, "!!") == 0) { + out << song[j] << "\n"; } + } +} - if (firstone > lastone) { - for (int i=firstone; i>=lastone; i--) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } else { - for (int i=firstone; i<=lastone; i++) { - field.push_back(i); - subfield.push_back(zero); - model.push_back(zero); - } - } - } else if (hre.search(buffer, "^(\\d+)([a-z]*)")) { - int value = hre.getMatchInt(1); - modletter = 0; - subletter = 0; - if (hre.getMatch(2) == "a") { - subletter = 'a'; - } - if (hre.getMatch(2) == "b") { - subletter = 'b'; - } - if (hre.getMatch(2) == "c") { - subletter = 'c'; - } - if (hre.getMatch(2) == "d") { - modletter = 'd'; - } - if (hre.getMatch(2) == "n") { - modletter = 'n'; - } - if (hre.getMatch(2) == "r") { - modletter = 'r'; - } - if ((value < 1) && (value != 0)) { - m_error_text << "Error: range token: \"" << astring << "\"" - << " contains too small a number at end: " << value << endl; - m_error_text << "Minimum number allowed is " << 1 << endl; - return; - } - if (value > maxtrack) { - m_error_text << "Error: range token: \"" << astring << "\"" - << " contains number too large at start: " << value << endl; - m_error_text << "Maximum number allowed is " << maxtrack << endl; - return; - } - field.push_back(value); - if (value == 0) { - subfield.push_back(zero); - model.push_back(zero); - } else { - subfield.push_back(subletter); - model.push_back(modletter); + +////////////////////////////// +// +// Tool_esac2humold::convertSong -- +// + +void Tool_esac2humold::convertSong(vector& song, ostream& out) { + + int i; + if (verboseQ) { + for (i=0; i<(int)song.size(); i++) { + out << song[i] << "\n"; } } - if (!kernQ) { - return; - } + printHumdrumHeaderInfo(out, song); - // Insert fields to next **kern spine. - vector newfield; - vector newsubfield; - vector newmodel; + string key; + double mindur = 1.0; + string meter; + int tonic = 0; + getKeyInfo(song, key, mindur, tonic, meter, out); - vector trackstarts; - infile.getTrackStartList(trackstarts); - int spine; + vector songdata; + songdata.resize(0); + songdata.reserve(1000); + getNoteList(song, songdata, mindur, tonic); + placeLyrics(song, songdata); - // convert kern tracks into spine tracks: - for (int i=finitsize; i<(int)field.size(); i++) { - if (field[i] > 0) { - spine = ktracks[field[i]-1]->getTrack(); - field[i] = spine; - } - } + vector numerator; + vector denominator; + getMeterInfo(meter, numerator, denominator); - int startspineindex, stopspineindex; - for (int i=0; i<(int)field.size(); i++) { - newfield.push_back(field[i]); // copy **kern spine index into new list - newsubfield.push_back(subfield[i]); - newmodel.push_back(model[i]); + postProcessSongData(songdata, numerator, denominator); - // search for non **kern spines after specified **kern spine: - startspineindex = field[i] + 1 - 1; - stopspineindex = maxtrack; - for (int j=startspineindex; jisKern()) { - break; - } - newfield.push_back(j+1); - newsubfield.push_back(zero); - newmodel.push_back(zero); + printTitleInfo(song, out); + out << "!!!id: " << key << "\n"; + + // check for presence of lyrics + int textQ = 0; + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].text != "") { + textQ = 1; + break; } } - field = newfield; - subfield = newsubfield; - model = newmodel; -} + for (i=0; i<(int)header.size(); i++) { + out << header[i] << "\n"; + } + out << "**kern"; + if (textQ) { + out << "\t**text"; + } + out << "\n"; + printKeyInfo(songdata, tonic, textQ, out); + for (i=0; i<(int)songdata.size(); i++) { + printNoteData(songdata[i], textQ, out); + } + out << "*-"; + if (textQ) { + out << "\t*-"; + } + out << "\n"; -////////////////////////////// -// -// Tool_extract::removeDollarsFromString -- substitute $ sign for maximum track count. -// + out << "!!!minrhy: "; + out << Convert::durationFloatToRecip(mindur)<<"\n"; + out << "!!!meter"; + if (numerator.size() > 1) { + out << "s"; + } + out << ": " << meter; + if ((meter == "frei") || (meter == "Frei")) { + out << " [unmetered]"; + } else if (meter.find('/') == string::npos) { + out << " interpreted as ["; + for (i=0; i<(int)numerator.size(); i++) { + out << numerator[i] << "/" << denominator[i]; + if (i < (int)numerator.size()-1) { + out << ", "; + } + } + out << "]"; + } + out << "\n"; -void Tool_extract::removeDollarsFromString(string& buffer, int maxtrack) { - HumRegex hre; - char buf2[128] = {0}; - int value2; + printBibInfo(song, out); + printSpecialChars(out); - if (hre.search(buffer, "\\$$")) { - snprintf(buf2, 128, "%d", maxtrack); - hre.replaceDestructive(buffer, buf2, "\\$$"); + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].lyricerr) { + out << "!!!RWG: Lyric placement mismatch " + << "in phrase (too many syllables) " << songdata[i].phnum << " [" + << key << "]\n"; + break; + } } - if (hre.search(buffer, "\\$(?![\\d-])")) { - // don't know how this case could happen, however... - snprintf(buf2, 128, "%d", maxtrack); - hre.replaceDestructive(buffer, buf2, "\\$(?![\\d-])", "g"); + for (i=0; i<(int)trailer.size(); i++) { + out << trailer[i] << "\n"; } - if (hre.search(buffer, "\\$0")) { - // replace $0 with maxtrack (used for reverse orderings) - snprintf(buf2, 128, "%d", maxtrack); - hre.replaceDestructive(buffer, buf2, "\\$0", "g"); - } + printHumdrumFooterInfo(out, song); - while (hre.search(buffer, "\\$(-?\\d+)")) { - value2 = maxtrack - abs(hre.getMatchInt(1)); - snprintf(buf2, 128, "%d", value2); - hre.replaceDestructive(buffer, buf2, "\\$-?\\d+"); +/* + if (!splitQ) { + out << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; } +*/ } ////////////////////////////// // -// Tool_extract::excludeFields -- print all spines except the ones in the list of fields. +// Tool_esac2humold::placeLyrics -- extract lyrics (if any) and place on correct notes // -void Tool_extract::excludeFields(HumdrumFile& infile, vector& field, - vector& subfield, vector& model) { - int start = 0; - for (int i=0; igetTrack(), field)) { - continue; - } - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(i, j); - } - if (start != 0) { - m_humdrum_text << endl; +bool Tool_esac2humold::placeLyrics(vector& song, vector& songdata) { + int start = -1; + int stop = -1; + getLineRange(song, "TXT", start, stop); + if (start < 0) { + // no TXT[] field, so don't do anything + return true; + } + int line = 0; + vector lyrics; + string buffer; + for (line=0; line<=stop-start; line++) { + if (song[line+start].size() <= 4) { + cerr << "Error: lyric line is too short!: " + << song[line+start] << endl; + return false; + } + buffer = song[line+start].substr(4); + if (line == stop - start) { + auto loc = buffer.rfind(']'); + if (loc != string::npos) { + buffer.resize(loc); } } + if (buffer == "") { + continue; + } + getLyrics(lyrics, buffer); + cleanupLyrics(lyrics); + placeLyricPhrase(songdata, lyrics, line); } + + return true; } ////////////////////////////// // -// Tool_extract::extractFields -- print all spines in the list of fields. +// Tool_esac2humold::cleanupLyrics -- add preceeding dashes, avoid starting *'s if any, +// and convert _'s to spaces. // -void Tool_extract::extractFields(HumdrumFile& infile, vector& field, - vector& subfield, vector& model) { - - HumRegex hre; - int start = 0; - int target; - int subtarget; - int modeltarget; - string spat; - bool foundBarline = true; - - for (int i=0; i& lyrics) { + int length; + int length2; + int i, j, m; + int lastsyl = 0; + for (i=0; i<(int)lyrics.size(); i++) { + length = (int)lyrics[i].size(); + for (j=0; j 0) { + if ((lyrics[i] != ".") && + (lyrics[i] != "") && + (lyrics[i] != "%") && + (lyrics[i] != "^") && + (lyrics[i] != "|") && + (lyrics[i] != " ")) { + lastsyl = -1; + for (m=i-1; m>=0; m--) { + if ((lyrics[m] != ".") && + (lyrics[m] != "") && + (lyrics[m] != "%") && + (lyrics[i] != "^") && + (lyrics[m] != "|") && + (lyrics[m] != " ")) { + lastsyl = m; break; - case 'c': - modeltarget = comodel; - } - } - if (target == 0) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - if (!infile[i].isManipulator()) { - if (infile[i].isLocalComment()) { - m_humdrum_text << "!"; - } else if (infile[i].isBarline()) { - m_humdrum_text << infile[i].token(0); - } else if (infile[i].isData()) { - if (foundBarline) { - if (addRestsQ) { - HumNum dur = infile[i].getDurationToBarline(); - m_humdrum_text << Convert::durationToRecip(dur); - } else { - m_humdrum_text << "."; - } - } else { - m_humdrum_text << "."; - } - // interpretations handled in dealWithSpineManipulators() - // [obviously not, so adding a blank one here - } else if (infile[i].isInterpretation()) { - HTp token = infile.token(i, 0); - if (token->isExpansionLabel()) { - m_humdrum_text << token; - } else if (token->isExpansionList()) { - m_humdrum_text << token; - } else { - if (addRestsQ) { - printInterpretationForKernSpine(infile, i); - } else { - m_humdrum_text << "*"; - } - } } } - } else { - for (int j=0; jgetTrack() != target) { - continue; - } - switch (subtarget) { - case 'a': - getSearchPat(spat, target, "a"); - if (hre.search(infile.token(i,j)->getSpineInfo(), spat) || - !hre.search(infile.token(i, j)->getSpineInfo(), "\\(")) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(i, j); - } - break; - case 'b': - getSearchPat(spat, target, "b"); - if (hre.search(infile.token(i, j)->getSpineInfo(), spat)) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(i, j); - } else if (!hre.search(infile.token(i, j)->getSpineInfo(), - "\\(")) { - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - dealWithSecondarySubspine(field, subfield, model, t, - infile, i, j, modeltarget); - } - break; - case 'c': - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - dealWithCospine(field, subfield, model, t, infile, i, j, - modeltarget, modeltarget, cointerp); - break; - default: - if (start != 0) { - m_humdrum_text << '\t'; + if (lastsyl >= 0) { + length2 = (int)lyrics[lastsyl].size(); + if (lyrics[lastsyl][length2-1] == '-') { + for (j=0; j<=length; j++) { + lyrics[i][length - j + 1] = lyrics[i][length - j]; } - start = 1; - m_humdrum_text << infile.token(i, j); + lyrics[i][0] = '-'; } } } } - if (infile[i].isData()) { - foundBarline = false; + // avoid *'s on the start of lyrics by placing a space before + // them if they exist. + if (lyrics[i][0] == '*') { + length = (int)lyrics[i].size(); + for (j=0; j<=length; j++) { + lyrics[i][length - j + 1] = lyrics[i][length - j]; + } + lyrics[i][0] = ' '; } - if (start != 0) { - m_humdrum_text << endl; + // avoid !'s on the start of lyrics by placing a space before + // them if they exist. + if (lyrics[i][0] == '!') { + length = (int)lyrics[i].size(); + for (j=0; j<=length; j++) { + lyrics[i][length - j + 1] = lyrics[i][length - j]; + } + lyrics[i][0] = ' '; } + } + } -////////////////////////////// +/////////////////////////////// // -// Tool_extract::printInterpretationForKernSpine -- +// Tool_esac2humold::getLyrics -- extract the lyrics from the text string. // -void Tool_extract::printInterpretationForKernSpine(HumdrumFile& infile, int index) { - HTp kerntok = NULL; - for (int j=0; jisKern()) { +void Tool_esac2humold::getLyrics(vector& lyrics, const string& buffer) { + lyrics.resize(0); + int zero1 = 0; + string current; + int zero2 = 0; + zero2 = zero1 + zero2; + + int length = (int)buffer.size(); + int i; + + i = 0; + while (iisKeySignature()) { - m_humdrum_text << kerntok; - return; - } - if (kerntok->isKeyDesignation()) { - m_humdrum_text << kerntok; - return; - } - if (kerntok->isTimeSignature()) { - m_humdrum_text << kerntok; - return; - } - if (kerntok->isMensurationSymbol()) { - m_humdrum_text << kerntok; - return; - } - if (kerntok->isTempo()) { - m_humdrum_text << kerntok; - return; - } - if (kerntok->isInstrumentName()) { - m_humdrum_text << "*I\""; - return; - } - if (kerntok->isInstrumentAbbreviation()) { - m_humdrum_text << "*I'"; - return; - } - - m_humdrum_text << "*"; } ////////////////////////////// // -// Tool_extract::dealWithCospine -- extract the required token(s) from a co-spine. +// Tool_esac2humold::placeLyricPhrase -- match lyrics from a phrase to the songdata. // -void Tool_extract::dealWithCospine(vector& field, vector& subfield, vector& model, - int targetindex, HumdrumFile& infile, int line, int cospine, - int comodel, int submodel, const string& cointerp) { - - vector cotokens; - cotokens.reserve(50); - - string buffer; - int i, j, k; - int index; - - if (infile[line].isInterpretation()) { - m_humdrum_text << infile.token(line, cospine); - return; - } - - if (infile[line].isBarline()) { - m_humdrum_text << infile.token(line, cospine); - return; - } - - if (infile[line].isLocalComment()) { - m_humdrum_text << infile.token(line, cospine); - return; - } +bool Tool_esac2humold::placeLyricPhrase(vector& songdata, vector& lyrics, int line) { + int i = 0; + int start = 0; + int found = 0; - int count = infile[line].token(cospine)->getSubtokenCount(); - for (k=0; kgetSubtoken(k); - cotokens.resize(cotokens.size()+1); - index = (int)cotokens.size()-1; - cotokens[index] = buffer; + if (lyrics.empty()) { + return true; } - vector spineindex; - vector subspineindex; - - spineindex.reserve(infile.getMaxTrack()*2); - spineindex.resize(0); - - subspineindex.reserve(infile.getMaxTrack()*2); - subspineindex.resize(0); - - for (j=0; jisDataType(cointerp)) { - continue; - } - if (*infile.token(line, j) == ".") { - continue; - } - count = infile[line].token(j)->getSubtokenCount(); - for (k=0; kgetSubtoken(k); - if (comodel == 'r') { - if (buffer == "r") { - continue; - } - } - spineindex.push_back(j); - subspineindex.push_back(k); + // find the phrase to which the lyrics belongs + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].phnum == line) { + found = 1; + break; } } + start = i; - if (debugQ) { - m_humdrum_text << "\n!!codata:\n"; - for (i=0; i<(int)cotokens.size(); i++) { - m_humdrum_text << "!!\t" << i << "\t" << cotokens[i]; - if (i < (int)spineindex.size()) { - m_humdrum_text << "\tspine=" << spineindex[i]; - m_humdrum_text << "\tsubspine=" << subspineindex[i]; - } else { - m_humdrum_text << "\tspine=."; - m_humdrum_text << "\tsubspine=."; - } - m_humdrum_text << endl; - } + if (!found) { + cerr << "Error: cannot find music for lyrics line " << line << endl; + cerr << "Error near input data line: " << inputline << endl; + return false; } - string buff; - - int start = 0; - for (i=0; i<(int)field.size(); i++) { - if (infile.token(line, field[i])->isDataType(cointerp)) { - continue; - } - - for (j=0; jgetTrack() != field[i]) { - continue; - } - if (subfield[i] == 'a') { - getSearchPat(buff, field[i], "a"); - if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) || - (infile.token(line, j)->getSpineInfo().find(buff) != string::npos)) { - printCotokenInfo(start, infile, line, j, cotokens, spineindex, - subspineindex); - } - } else if (subfield[i] == 'b') { - // this section may need more work... - getSearchPat(buff, field[i], "b"); - if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) || - (strstr(infile.token(line, j)->getSpineInfo().c_str(), buff.c_str()) != NULL)) { - printCotokenInfo(start, infile, line, j, cotokens, spineindex, - subspineindex); - } + for (i=0; i<(int)lyrics.size() && i+start < (int)songdata.size(); i++) { + if ((lyrics[i] == " ") || (lyrics[i] == ".") || (lyrics[i] == "")) { + if (songdata[i+start].pitch < 0) { + lyrics[i] = "%"; } else { - printCotokenInfo(start, infile, line, j, cotokens, spineindex, - subspineindex); + lyrics[i] = "|"; } + // lyrics[i] = "."; + } + songdata[i+start].text = lyrics[i]; + songdata[i+start].lyricnum = line; + if (line != songdata[i+start].phnum) { + songdata[i+start].lyricerr = 1; // lyric does not line up with music } } + + return true; } ////////////////////////////// // -// Tool_extract::printCotokenInfo -- +// Tool_esac2humold::printSpecialChars -- print high ASCII character table // -void Tool_extract::printCotokenInfo(int& start, HumdrumFile& infile, int line, int spine, - vector& cotokens, vector& spineindex, - vector& subspineindex) { +void Tool_esac2humold::printSpecialChars(ostream& out) { int i; - int found = 0; - for (i=0; i<(int)spineindex.size(); i++) { - if (spineindex[i] == spine) { - if (start == 0) { - start++; - } else { - m_humdrum_text << subtokenseparator; - } - if (i<(int)cotokens.size()) { - m_humdrum_text << cotokens[i]; - } else { - m_humdrum_text << "."; - } - found = 1; + for (i=0; i<(int)chartable.size(); i++) { + if (chartable[i]) { + switch (i) { + case 129: out << "!!!RNB" << ": symbol: ü = u umlaut (UTF-8: " + << (char)0xc3 << (char)0xb3 << ")\n"; break; + case 130: out << "!!!RNB" << ": symbol: é= e acute (UTF-8: " + << (char)0xc3 << (char)0xa9 << ")\n"; break; + case 132: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: " + << (char)0xc3 << (char)0xa4 << ")\n"; break; + case 134: out << "!!!RNB" << ": symbol: $c = c acute (UTF-8: " + << (char)0xc4 << (char)0x87 << ")\n"; break; + case 136: out << "!!!RNB" << ": symbol: $l = l slash (UTF-8: " + << (char)0xc5 << (char)0x82 << ")\n"; break; + case 140: out << "!!!RNB" << ": symbol: î = i circumflex (UTF-8: " + << (char)0xc3 << (char)0xaf << ")\n"; break; + case 141: out << "!!!RNB" << ": symbol: $X = Z acute (UTF-8: " + << (char)0xc5 << (char)0xb9 << ")\n"; break; + case 142: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: " + << (char)0xc3 << (char)0xa4 << ")\n"; break; + case 143: out << "!!!RNB" << ": symbol: $C = C acute (UTF-8: " + << (char)0xc4 << (char)0x86 << ")\n"; break; + case 148: out << "!!!RNB" << ": symbol: ö = o umlaut (UTF-8: " + << (char)0xc3 << (char)0xb6 << ")\n"; break; + case 151: out << "!!!RNB" << ": symbol: $S = S acute (UTF-8: " + << (char)0xc5 << (char)0x9a << ")\n"; break; + case 152: out << "!!!RNB" << ": symbol: $s = s acute (UTF-8: " + << (char)0xc5 << (char)0x9b << ")\n"; break; + case 156: out << "!!!RNB" << ": symbol: $s = s acute (UTF-8: " + << (char)0xc5 << (char)0x9b << ")\n"; break; + case 157: out << "!!!RNB" << ": symbol: $L = L slash (UTF-8: " + << (char)0xc5 << (char)0x81 << ")\n"; break; + case 159: out << "!!!RNB" << ": symbol: $vc = c hachek (UTF-8: " + << (char)0xc4 << (char)0x8d << ")\n"; break; + case 162: out << "!!!RNB" << ": symbol: ó= o acute (UTF-8: " + << (char)0xc3 << (char)0xb3 << ")\n"; break; + case 163: out << "!!!RNB" << ": symbol: ú= u acute (UTF-8: " + << (char)0xc3 << (char)0xba << ")\n"; break; + case 165: out << "!!!RNB" << ": symbol: $a = a hook (UTF-8: " + << (char)0xc4 << (char)0x85 << ")\n"; break; + case 169: out << "!!!RNB" << ": symbol: $e = e hook (UTF-8: " + << (char)0xc4 << (char)0x99 << ")\n"; break; + case 171: out << "!!!RNB" << ": symbol: $y = z acute (UTF-8: " + << (char)0xc5 << (char)0xba << ")\n"; break; + case 175: out << "!!!RNB" << ": symbol: $Z = Z dot (UTF-8: " + << (char)0xc5 << (char)0xbb << ")\n"; break; + case 179: out << "!!!RNB" << ": symbol: $l = l slash (UTF-8: " + << (char)0xc5 << (char)0x82 << ")\n"; break; + case 185: out << "!!!RNB" << ": symbol: $a = a hook (UTF-8: " + << (char)0xc4 << (char)0x85 << ")\n"; break; + case 189: out << "!!!RNB" << ": symbol: $Z = Z dot (UTF-8: " + << (char)0xc5 << (char)0xbb << ")\n"; break; + case 190: out << "!!!RNB" << ": symbol: $z = z dot (UTF-8: " + << (char)0xc5 << (char)0xbc << ")\n"; break; + case 191: out << "!!!RNB" << ": symbol: $z = z dot (UTF-8: " + << (char)0xc5 << (char)0xbc << ")\n"; break; + case 224: out << "!!!RNB" << ": symbol: Ó= O acute (UTF-8: " + << (char)0xc3 << (char)0x93 << ")\n"; break; + case 225: out << "!!!RNB" << ": symbol: ß = sz ligature (UTF-8: " + << (char)0xc3 << (char)0x9f << ")\n"; break; + case 0xdf: out << "!!!RNB" << ": symbol: ß = sz ligature (UTF-8: " + << (char)0xc3 << (char)0x9f << ")\n"; break; +// Polish version: +// case 228: out << "!!!RNB" << ": symbol: $n = n acute (UTF-8: " +// << (char)0xc5 << (char)0x84 << ")\n"; break; +// Luxembourg version for some reason...: + case 228: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: " + << (char)0xc5 << (char)0x84 << ")\n"; break; + case 230: out << "!!!RNB" << ": symbol: c = c\n"; break; + case 231: out << "!!!RNB" << ": symbol: $vs = s hachek (UTF-8: " + << (char)0xc5 << (char)0xa1 << ")\n"; break; + case 234: out << "!!!RNB" << ": symbol: $e = e hook (UTF-8: " + << (char)0xc4 << (char)0x99 << ")\n"; break; + case 241: out << "!!!RNB" << ": symbol: $n = n acute (UTF-8: " + << (char)0xc5 << (char)0x84 << ")\n"; break; + case 243: out << "!!!RNB" << ": symbol: ó= o acute (UTF-8: " + << (char)0xc3 << (char)0xb3 << ")\n"; break; + case 252: out << "!!!RNB" << ": symbol: ü = u umlaut (UTF-8: " + << (char)0xc3 << (char)0xbc << ")\n"; break; +// default: } - } - if (!found) { - if (start == 0) { - start++; - } else { - m_humdrum_text << subtokenseparator; } - m_humdrum_text << "."; + chartable[i] = 0; } } @@ -81171,937 +81485,1047 @@ void Tool_extract::printCotokenInfo(int& start, HumdrumFile& infile, int line, i ////////////////////////////// // -// Tool_extract::dealWithSecondarySubspine -- what to print if a secondary spine -// does not exist on a line. +// Tool_esac2humold::printTitleInfo -- print the first line of the CUT[] field. // -void Tool_extract::dealWithSecondarySubspine(vector& field, vector& subfield, - vector& model, int targetindex, HumdrumFile& infile, int line, - int spine, int submodel) { - - int& i = line; - int& j = spine; +bool Tool_esac2humold::printTitleInfo(vector& song, ostream& out) { + int start = -1; + int stop = -1; + getLineRange(song, "CUT", start, stop); + if (start == -1) { + cerr << "Error: cannot find CUT[] field in song: " << song[0] << endl; + return false; + } - HumRegex hre; string buffer; - if (infile[line].isLocalComment()) { - if ((submodel == 'n') || (submodel == 'r')) { - m_humdrum_text << "!"; - } else { - m_humdrum_text << infile.token(i, j); - } - } else if (infile[line].isBarline()) { - m_humdrum_text << infile.token(i, j); - } else if (infile[line].isInterpretation()) { - if ((submodel == 'n') || (submodel == 'r')) { - m_humdrum_text << "*"; - } else { - m_humdrum_text << infile.token(i, j); - } - } else if (infile[line].isData()) { - if (submodel == 'n') { - m_humdrum_text << "."; - } else if (submodel == 'r') { - if (*infile.token(i, j) == ".") { - m_humdrum_text << "."; - } else if (infile.token(i, j)->find('q') != string::npos) { - m_humdrum_text << "."; - } else if (infile.token(i, j)->find('Q') != string::npos) { - m_humdrum_text << "."; - } else { - buffer = *infile.token(i, j); - if (hre.search(buffer, "{")) { - m_humdrum_text << "{"; - } - // remove secondary chord notes: - hre.replaceDestructive(buffer, "", " .*"); - // remove unnecessary characters (such as stem direction): - hre.replaceDestructive(buffer, "", - "[^}pPqQA-Ga-g0-9.;%#nr-]", "g"); - // change pitch to rest: - hre.replaceDestructive(buffer, "[A-Ga-g#n-]+", "r"); - // add editorial marking unless -Y option is given: - if (editorialInterpretation != "") { - if (hre.search(buffer, "rr")) { - hre.replaceDestructive(buffer, editorialInterpretation, "(?<=rr)"); - hre.replaceDestructive(buffer, "r", "rr"); - } else { - hre.replaceDestructive(buffer, editorialInterpretation, "(?<=r)"); - } - } - m_humdrum_text << buffer; - } - } else { - m_humdrum_text << infile.token(i, j); - } - } else { - m_error_text << "Should not get to this line of code" << endl; - return; + buffer = song[start].substr(4); + if (buffer.back() == ']') { + buffer.resize((int)buffer.size() - 1); } -} + out << "!!!OTL: "; + for (int i=0; i<(int)buffer.size(); i++) { + printChar(buffer[i], out); + } + out << "\n"; + + return true; +} ////////////////////////////// // -// Tool_extract::getSearchPat -- +// Tool_esac2humold::printChar -- print text characters, translating high-bit data +// if required. // -void Tool_extract::getSearchPat(string& spat, int target, const string& modifier) { - if (modifier.size() > 20) { - m_error_text << "Error in GetSearchPat" << endl; - return; +void Tool_esac2humold::printChar(unsigned char c, ostream& out) { + out << c; +/* + if (c < 128) { + out << c; + } else { + chartable[c]++; + switch (c) { + case 129: out << "ü"; break; + case 130: out << "é"; break; + case 132: out << "ä"; break; + case 134: out << "$c"; break; + case 136: out << "$l"; break; + case 140: out << "î"; break; + case 141: out << "$X"; break; // Z acute + case 142: out << "ä"; break; // ? + case 143: out << "$C"; break; + case 148: out << "ö"; break; + case 151: out << "$S"; break; + case 152: out << "$s"; break; + case 156: out << "$s"; break; // 1250 encoding + case 157: out << "$L"; break; + case 159: out << "$vc"; break; // Cech c with v accent + case 162: out << "ó"; break; + case 163: out << "ú"; break; + case 165: out << "$a"; break; + case 169: out << "$e"; break; + case 171: out << "$y"; break; + case 175: out << "$Z"; break; // 1250 encoding + case 179: out << "$l"; break; // 1250 encoding + case 185: out << "$a"; break; // 1250 encoding + case 189: out << "$Z"; break; // Z dot + case 190: out << "$z"; break; // z dot + case 191: out << "$z"; break; // 1250 encoding + case 224: out << "Ó"; break; + case 225: out << "ß"; break; + case 0xdf: out << "ß"; break; + // Polish version: + // case 228: out << "$n"; break; + // Luxembourg version (for some reason...) + case 228: out << "ä"; break; + case 230: out << "c"; break; // ? + case 231: out << "$vs"; break; // Cech s with v accent + case 234: out << "$e"; break; // 1250 encoding + case 241: out << "$n"; break; // 1250 encoding + case 243: out << "ó"; break; // 1250 encoding + case 252: out << "ü"; break; + default: out << c; + } } - spat.reserve(16); - spat = "\\("; - spat += to_string(target); - spat += "\\)"; - spat += modifier; +*/ } ////////////////////////////// // -// Tool_extract::dealWithSpineManipulators -- check for proper Humdrum syntax of -// spine manipulators (**, *-, *x, *v, *^) when creating the output. +// Tool_esac2humold::printKeyInfo -- // -void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, - vector& field, vector& subfield, vector& model) { - - vector vmanip; // counter for *v records on line - vmanip.resize(infile[line].getFieldCount()); - fill(vmanip.begin(), vmanip.end(), 0); - - vector xmanip; // counter for *x record on line - xmanip.resize(infile[line].getFieldCount()); - fill(xmanip.begin(), xmanip.end(), 0); - - int i = 0; - int j; - for (j=0; j<(int)vmanip.size(); j++) { - if (*infile.token(line, j) == "*v") { - vmanip[j] = 1; - } - if (*infile.token(line, j) == "*x") { - xmanip[j] = 1; +void Tool_esac2humold::printKeyInfo(vector& songdata, int tonic, int textQ, + ostream& out) { + vector pitches(40, 0); + int pitchsum = 0; + int pitchcount = 0; + int i; + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].pitch >= 0) { + pitches[songdata[i].pitch % 40]++; + pitchsum += Convert::base40ToMidiNoteNumber(songdata[i].pitch); + pitchcount++; } } - int counter = 1; - for (i=1; i<(int)xmanip.size(); i++) { - if ((xmanip[i] == 1) && (xmanip[i-1] == 1)) { - xmanip[i] = counter; - xmanip[i-1] = counter; - counter++; + // generate a clef, choosing either treble or bass clef depending + // on the average pitch. + double averagepitch = pitchsum * 1.0 / pitchcount; + if (averagepitch > 60.0) { + out << "*clefG2"; + if (textQ) { + out << "\t*clefG2"; } - } - - counter = 1; - i = 0; - while (i < (int)vmanip.size()) { - if (vmanip[i] == 1) { - while ((i < (int)vmanip.size()) && (vmanip[i] == 1)) { - vmanip[i] = counter; - i++; - } - counter++; + out << "\n"; + } else { + out << "*clefF4"; + if (textQ) { + out << "\t*clefF4"; } - i++; + out << "\n"; } - vector fieldoccur; // nth occurance of an input spine in the output - fieldoccur.resize(field.size()); - fill(fieldoccur.begin(), fieldoccur.end(), 0); - - vector trackcounter; // counter of input spines occurances in output - trackcounter.resize(infile.getMaxTrack()+1); - fill(trackcounter.begin(), trackcounter.end(), 0); + // generate a key signature + vector diatonic(7, 0); + diatonic[0] = getAccidentalMax(pitches[1], pitches[2], pitches[3]); + diatonic[1] = getAccidentalMax(pitches[7], pitches[8], pitches[9]); + diatonic[2] = getAccidentalMax(pitches[13], pitches[14], pitches[15]); + diatonic[3] = getAccidentalMax(pitches[18], pitches[19], pitches[20]); + diatonic[4] = getAccidentalMax(pitches[24], pitches[25], pitches[26]); + diatonic[5] = getAccidentalMax(pitches[30], pitches[31], pitches[32]); + diatonic[6] = getAccidentalMax(pitches[36], pitches[37], pitches[38]); - for (i=0; i<(int)field.size(); i++) { - if (field[i] != 0) { - trackcounter[field[i]]++; - fieldoccur[i] = trackcounter[field[i]]; + int flatcount = 0; + int sharpcount = 0; + int naturalcount = 0; + for (i=0; i<7; i++) { + switch (diatonic[i]) { + case -1: flatcount++; break; + case 0: naturalcount++; break; + case +1: sharpcount++; break; } } - vector tempout; - vector vserial; - vector xserial; - vector fpos; // input column of output spine - - tempout.reserve(1000); - tempout.resize(0); - - vserial.reserve(1000); - vserial.resize(0); + char kbuf[32] = {0}; + if (naturalcount == 7) { + // do nothing + } else if (flatcount > sharpcount) { + // print a flat key signature + if (diatonic[6] == -1) strcat(kbuf, "b-"); else goto keysigend; + if (diatonic[2] == -1) strcat(kbuf, "e-"); else goto keysigend; + if (diatonic[5] == -1) strcat(kbuf, "a-"); else goto keysigend; + if (diatonic[1] == -1) strcat(kbuf, "d-"); else goto keysigend; + if (diatonic[4] == -1) strcat(kbuf, "g-"); else goto keysigend; + if (diatonic[0] == -1) strcat(kbuf, "c-"); else goto keysigend; + if (diatonic[3] == -1) strcat(kbuf, "f-"); else goto keysigend; + } else { + // print a sharp key signature + if (diatonic[3] == +1) strcat(kbuf, "f#"); else goto keysigend; + if (diatonic[0] == +1) strcat(kbuf, "c#"); else goto keysigend; + if (diatonic[4] == +1) strcat(kbuf, "g#"); else goto keysigend; + if (diatonic[1] == +1) strcat(kbuf, "d#"); else goto keysigend; + if (diatonic[5] == +1) strcat(kbuf, "a#"); else goto keysigend; + if (diatonic[2] == +1) strcat(kbuf, "e#"); else goto keysigend; + if (diatonic[6] == +1) strcat(kbuf, "b#"); else goto keysigend; + } - xserial.reserve(1000); - xserial.resize(0); +keysigend: + out << "*k[" << kbuf << "]"; + if (textQ) { + out << "\t*k[" << kbuf << "]"; + } + out << "\n"; - fpos.reserve(1000); - fpos.resize(0); + // look at the third scale degree above the tonic pitch + int minor = pitches[(tonic + 40 + 11) % 40]; + int major = pitches[(tonic + 40 + 12) % 40]; - string spat; - string spinepat; - HumRegex hre; - int subtarget; - int modeltarget; - int xdebug = 0; - int vdebug = 0; - int suppress = 0; - int target; - int tval; - for (int t=0; t<(int)field.size(); t++) { - target = field[t]; - subtarget = subfield[t]; - modeltarget = model[t]; - if (modeltarget == 0) { - switch (subtarget) { - case 'a': - case 'b': - modeltarget = submodel; - break; - case 'c': - modeltarget = comodel; - } + if (minor > major) { + // minor key (or related mode) + out << "*" << Convert::base40ToKern(40 * 4 + tonic) << ":"; + if (textQ) { + out << "\t*" << Convert::base40ToKern(40 * 4 + tonic) << ":"; } - suppress = 0; - if (target == 0) { - if (infile.token(line, 0)->compare(0, 2, "**") == 0) { - storeToken(tempout, blankName); - tval = 0; - vserial.push_back(tval); - xserial.push_back(tval); - fpos.push_back(tval); - } else if (*infile.token(line, 0) == "*-") { - storeToken(tempout, "*-"); - tval = 0; - vserial.push_back(tval); - xserial.push_back(tval); - fpos.push_back(tval); - } else { - storeToken(tempout, "*"); - tval = 0; - vserial.push_back(tval); - xserial.push_back(tval); - fpos.push_back(tval); - } - } else { - for (j=0; jgetTrack() != target) { - continue; - } - // filter by subfield - if (subtarget == 'a') { - getSearchPat(spat, target, "b"); - if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { - continue; - } - } else if (subtarget == 'b') { - getSearchPat(spat, target, "a"); - if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { - continue; - } + out << "\n"; + } else { + // major key (or related mode) + out << "*" << Convert::base40ToKern(40 * 3 + tonic) << ":"; + if (textQ) { + out << "\t*" << Convert::base40ToKern(40 * 3 + tonic) << ":"; } + out << "\n"; + } - switch (subtarget) { - case 'a': - - if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) { - if (*infile.token(line, j) == "*^") { - storeToken(tempout, "*"); - } else { - storeToken(tempout, *infile.token(line, j)); - } - } else { - getSearchPat(spat, target, "a"); - spinepat = infile.token(line, j)->getSpineInfo(); - hre.replaceDestructive(spinepat, "\\(", "\\(", "g"); - hre.replaceDestructive(spinepat, "\\)", "\\)", "g"); - - if ((*infile.token(line, j) == "*v") && - (spinepat == spat)) { - storeToken(tempout, "*"); - } else { - getSearchPat(spat, target, "b"); - if ((spinepat == spat) && - (*infile.token(line, j) == "*v")) { - // do nothing - suppress = 1; - } else { - storeToken(tempout, *infile.token(line, j)); - } - } - } - - break; - case 'b': - - if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) { - if (*infile.token(line, j) == "*^") { - storeToken(tempout, "*"); - } else { - storeToken(tempout, *infile.token(line, j)); - } - } else { - getSearchPat(spat, target, "b"); - spinepat = infile.token(line, j)->getSpineInfo(); - hre.replaceDestructive(spinepat, "\\(", "\\(", "g"); - hre.replaceDestructive(spinepat, "\\)", "\\)", "g"); - - if ((*infile.token(line, j) == "*v") && - (spinepat == spat)) { - storeToken(tempout, "*"); - } else { - getSearchPat(spat, target, "a"); - if ((spinepat == spat) && - (*infile.token(line, j) == "*v")) { - // do nothing - suppress = 1; - } else { - storeToken(tempout, *infile.token(line, j)); - } - } - } +} - break; - case 'c': - // work on later - storeToken(tempout, *infile.token(line, j)); - break; - default: - storeToken(tempout, *infile.token(line, j)); - } - if (suppress) { - continue; - } +////////////////////////////// +// +// Tool_esac2humold::getAccidentalMax -- +// - if (tempout[(int)tempout.size()-1] == "*x") { - tval = fieldoccur[t] * 1000 + xmanip[j]; - xserial.push_back(tval); - xdebug = 1; - } else { - tval = 0; - xserial.push_back(tval); - } +int Tool_esac2humold::getAccidentalMax(int a, int b, int c) { + if (a > b && a > c) { + return -1; + } else if (c > a && c > b) { + return +1; + } else { + return 0; + } +} - if (tempout[(int)tempout.size()-1] == "*v") { - tval = fieldoccur[t] * 1000 + vmanip[j]; - vserial.push_back(tval); - vdebug = 1; - } else { - tval = 0; - vserial.push_back(tval); - } - fpos.push_back(j); +////////////////////////////// +// +// Tool_esac2humold::postProcessSongData -- clean up data and do some interpreting. +// - } +void Tool_esac2humold::postProcessSongData(vector& songdata, vector& numerator, + vector& denominator) { + int i, j; + // move phrase start markers off of rests and onto the + // first note that it finds + for (i=0; i<(int)songdata.size()-1; i++) { + if (songdata[i].pitch < 0 && songdata[i].phstart) { + songdata[i+1].phstart = songdata[i].phstart; + songdata[i].phstart = 0; } } - if (debugQ && xdebug) { - m_humdrum_text << "!! *x serials = "; - for (int ii=0; ii<(int)xserial.size(); ii++) { - m_humdrum_text << xserial[ii] << " "; + // move phrase ending markers off of rests and onto the + // previous note that it finds + for (i=(int)songdata.size()-1; i>0; i--) { + if (songdata[i].pitch < 0 && songdata[i].phend) { + songdata[i-1].phend = songdata[i].phend; + songdata[i].phend = 0; } - m_humdrum_text << "\n"; } - if (debugQ && vdebug) { - m_humdrum_text << "!!LINE: " << infile[line] << endl; - m_humdrum_text << "!! *v serials = "; - for (int ii=0; ii<(int)vserial.size(); ii++) { - m_humdrum_text << vserial[ii] << " "; + // examine barline information + double dur = 0.0; + for (i=(int)songdata.size()-1; i>=0; i--) { + if (songdata[i].bar == 1) { + songdata[i].bardur = dur; + dur = songdata[i].duration; + } else { + dur += songdata[i].duration; } - m_humdrum_text << "\n"; } - // check for proper *x syntax ///////////////////////////////// - for (i=0; i<(int)xserial.size()-1; i++) { - if (!xserial[i]) { - continue; + int barnum = 0; + double firstdur = 0.0; + if (numerator.size() == 1 && numerator[0] > 0) { + // handle single non-frei meter + songdata[0].num = numerator[0]; + songdata[0].denom = denominator[0]; + dur = 0; + double meterdur = 4.0 / denominator[0] * numerator[0]; + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].bar) { + dur = 0.0; + } else { + dur += songdata[i].duration; + if (fabs(dur - meterdur) < 0.001) { + songdata[i].bar = 1; + songdata[i].barinterp = 1; + dur = 0.0; + } + } } - if (xserial[i] != xserial[i+1]) { - if (tempout[i] == "*x") { - xserial[i] = 0; - tempout[i] = "*"; + + // readjust measure beat counts + dur = 0.0; + for (i=(int)songdata.size()-1; i>=0; i--) { + if (songdata[i].bar == 1) { + songdata[i].bardur = dur; + dur = songdata[i].duration; + } else { + dur += songdata[i].duration; } - } else { - i++; } - } + firstdur = dur; - if ((tempout.size() == 1) || (xserial.size() == 1)) { - // get rid of *x if there is only one spine in output - if (xserial[0]) { - xserial[0] = 0; - tempout[0] = "*"; + // number the barlines + barnum = 0; + if (fabs(firstdur - meterdur) < 0.001) { + // music for first bar, next bar will be bar 2 + barnum = 2; + } else { + barnum = 1; + // pickup-measure } - } else if ((int)xserial.size() > 1) { - // check the last item in the list - int index = (int)xserial.size()-1; - if (tempout[index] == "*x") { - if (xserial[index] != xserial[index-1]) { - xserial[index] = 0; - tempout[index] = "*"; + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].bar == 1) { + songdata[i].barnum = barnum++; } } - } - // check for proper *v syntax ///////////////////////////////// - vector vsplit; - vsplit.resize((int)vserial.size()); - fill(vsplit.begin(), vsplit.end(), 0); + } else if (numerator.size() == 1 && numerator[0] == -1) { + // handle free meter - // identify necessary line splits - for (i=0; i<(int)vserial.size()-1; i++) { - if (!vserial[i]) { - continue; + // number the barline + firstdur = dur; + barnum = 1; + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].bar == 1) { + songdata[i].barnum = barnum++; + } } - while ((i<(int)vserial.size()-1) && (vserial[i]==vserial[i+1])) { - i++; + + } else { + // handle multiple time signatures + + // get the duration of each type of meter: + vector meterdurs; + meterdurs.resize(numerator.size()); + for (i=0; i<(int)meterdurs.size(); i++) { + meterdurs[i] = 4.0 / denominator[i] * numerator[i]; } - if ((i<(int)vserial.size()-1) && vserial[i]) { - if (vserial.size() > 1) { - if (vserial[i+1]) { - vsplit[i+1] = 1; - } + + // measure beat counts: + dur = 0.0; + for (i=(int)songdata.size()-1; i>=0; i--) { + if (songdata[i].bar == 1) { + songdata[i].bardur = dur; + dur = songdata[i].duration; + } else { + dur += songdata[i].duration; } } - } - - // remove single *v spines: + firstdur = dur; - for (i=0; i<(int)vsplit.size()-1; i++) { - if (vsplit[i] && vsplit[i+1]) { - if (tempout[i] == "*v") { - tempout[i] = "*"; - vsplit[i] = 0; + // interpret missing barlines + int currentmeter = 0; + // find first meter + for (i=0; i<(int)numerator.size(); i++) { + if (fabs(firstdur - meterdurs[i]) < 0.001) { + songdata[0].num = numerator[i]; + songdata[0].denom = denominator[i]; + currentmeter = i; } } - } + // now handle the meters in the rest of the music... + int fnd = 0; + dur = 0; + for (i=0; i<(int)songdata.size()-1; i++) { + if (songdata[i].bar) { + if (songdata[i].bardur != meterdurs[currentmeter]) { + // try to find the correct new meter - if (debugQ) { - m_humdrum_text << "!!vsplit array: "; - for (i=0; i<(int)vsplit.size(); i++) { - m_humdrum_text << " " << vsplit[i]; + fnd = 0; + for (j=0; j<(int)numerator.size(); j++) { + if (j == currentmeter) { + continue; + } + if (fabs(songdata[i].bardur - meterdurs[j]) < 0.001) { + songdata[i+1].num = numerator[j]; + songdata[i+1].denom = denominator[j]; + currentmeter = j; + fnd = 1; + } + } + if (!fnd) { + for (j=0; j<(int)numerator.size(); j++) { + if (j == currentmeter) { + continue; + } + if (fabs(songdata[i].bardur/2.0 - meterdurs[j]) < 0.001) { + songdata[i+1].num = numerator[j]; + songdata[i+1].denom = denominator[j]; + currentmeter = j; + fnd = 1; + } + } + } + } + dur = 0.0; + } else { + dur += songdata[i].duration; + if (fabs(dur - meterdurs[currentmeter]) < 0.001) { + songdata[i].bar = 1; + songdata[i].barinterp = 1; + dur = 0.0; + } + } } - m_humdrum_text << endl; - } - if (vsplit.size() > 0) { - if (vsplit[(int)vsplit.size()-1]) { - if (tempout[(int)tempout.size()-1] == "*v") { - tempout[(int)tempout.size()-1] = "*"; - vsplit[(int)vsplit.size()-1] = 0; + // perhaps sum duration of measures again and search for error here? + + // finally, number the barlines: + barnum = 1; + for (i=0; i<(int)numerator.size(); i++) { + if (fabs(firstdur - meterdurs[i]) < 0.001) { + barnum = 2; + break; + } + } + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].bar == 1) { + songdata[i].barnum = barnum++; } } - } - int vcount = 0; - for (i=0; i<(int)vsplit.size(); i++) { - vcount += vsplit[i]; - } - if (vcount) { - printMultiLines(vsplit, vserial, tempout); } - int start = 0; - for (i=0; i<(int)tempout.size(); i++) { - if (tempout[i] != "") { - if (start != 0) { - m_humdrum_text << "\t"; - } - m_humdrum_text << tempout[i]; - start++; - } +} + + + +////////////////////////////// +// +// Tool_esac2humold::getMeterInfo -- +// + +void Tool_esac2humold::getMeterInfo(string& meter, vector& numerator, + vector& denominator) { + numerator.clear(); + denominator.clear(); + HumRegex hre; + hre.replaceDestructive(meter, "", "^\\s+"); + hre.replaceDestructive(meter, "", "\\s+$"); + if (hre.search(meter, "^(\\d+)/(\\d+)$")) { + numerator.push_back(hre.getMatchInt(1)); + denominator.push_back(hre.getMatchInt(2)); + return; } - if (start) { - m_humdrum_text << '\n'; + if (hre.search(meter, "^frei$", "i")) { + numerator.push_back(-1); + denominator.push_back(-1); + return; } + cerr << "NEED TO DEAL WITH METER: " << meter << endl; } ////////////////////////////// // -// Tool_extract::printMultiLines -- print separate *v lines. +// Tool_esac2humold::getLineRange -- get the staring line and ending line of a data +// field. Returns -1 if the data field was not found. // -void Tool_extract::printMultiLines(vector& vsplit, vector& vserial, - vector& tempout) { - int i; - - int splitpoint = -1; - for (i=0; i<(int)vsplit.size(); i++) { - if (vsplit[i]) { - splitpoint = i; +void Tool_esac2humold::getLineRange(vector& song, const string& field, + int& start, int& stop) { + string searchstring = field;; + searchstring += "["; + start = stop = -1; + for (int i=0; i<(int)song.size(); i++) { + auto loc = song[i].find(']'); + if (song[i].compare(0, searchstring.size(), searchstring) == 0) { + start = i; + if (loc != string::npos) { + stop = i; + break; + } + } else if ((start >= 0) && (loc != string::npos)) { + stop = i; break; } } +} - if (debugQ) { - m_humdrum_text << "!!tempout: "; - for (i=0; i<(int)tempout.size(); i++) { - m_humdrum_text << tempout[i] << " "; - } - m_humdrum_text << endl; - } - if (splitpoint == -1) { - return; - } - int start = 0; - int printv = 0; - for (i=0; i& song, vector& songdata, double mindur, + int tonic) { + songdata.resize(0); + NoteData tempnote; + int melstart = -1; + int melstop = -1; + int i, j; + int octave = 0; + int degree = 0; + int accidental = 0; + double duration = mindur; + int bar = 0; + // int tuplet = 0; + int major[8] = {-1, 0, 6, 12, 17, 23, 29, 35}; + // int oldstate = -1; + int state = -1; + int nextstate = -1; + int phend = 0; + int phnum = 0; + int phstart = 0; + int slend = 0; + int slstart = 0; + int tie = 0; - vsplit[splitpoint] = 0; + getLineRange(song, "MEL", melstart, melstop); - printMultiLines(vsplit, vserial, tempout); -} + for (i=melstart; i<=melstop; i++) { + if (song[i].size() < 4) { + cerr << "Error: invalid line in MEL[]: " << song[i] << endl; + return false; + } + j = 4; + phstart = 1; + phend = 0; + // Note Format: (+|-)*[0..7]_*\.*( )? + // ONADB + // Order of data: Octave, Note, Accidental, Duration, Barline + #define STATE_SLSTART -1 + #define STATE_OCTAVE 0 + #define STATE_NOTE 1 + #define STATE_ACC 2 + #define STATE_DUR 3 + #define STATE_BAR 4 + #define STATE_SLEND 5 + while (j < 200 && (j < (int)song[i].size())) { + // oldstate = state; + switch (song[i][j]) { + // Octave information: + case '-': octave--; state = STATE_OCTAVE; break; + case '+': octave++; state = STATE_OCTAVE; break; -////////////////////////////// -// -// Tool_extract::storeToken -- -// + // Duration information: + case '_': duration *= 2.0; state = STATE_DUR; break; + case '.': duration *= 1.5; state = STATE_DUR; break; -void Tool_extract::storeToken(vector& storage, const string& string) { - storage.push_back(string); -} + // Accidental information: + case 'b': accidental--; state = STATE_ACC; break; + case '#': accidental++; state = STATE_ACC; break; -void storeToken(vector& storage, int index, const string& string) { - storage[index] = string; -} + // Note information: + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + degree = major[song[i][j] - '0']; + state = STATE_NOTE; + break; + case 'O': + degree = major[0]; + state = STATE_NOTE; + break; + // Barline information: + case ' ': + state = STATE_BAR; + if (song[i][j+1] == ' ') { + bar = 1; + } + break; + // Other information: + case '{': slstart = 1; state = STATE_SLSTART; break; + case '}': slend = 1; state = STATE_SLEND; break; + // case '(': tuplet = 1; break; + // case ')': tuplet = 0; break; + case '/': break; + case ']': break; +// case '>': break; // unknown marker +// case '<': break; // + case '^': tie = 1; state = STATE_NOTE; break; + default : cerr << "Error: unknown character " << song[i][j] + << " on the line: " << song[i] << endl; + return false; + } + j++; + switch (song[i][j]) { + case '-': case '+': nextstate = STATE_OCTAVE; break; + case 'O': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': nextstate = STATE_NOTE; break; + case 'b': case '#': nextstate = STATE_ACC; break; + case '_': case '.': nextstate = STATE_DUR; break; + case '{': nextstate = STATE_SLSTART; break; + case '}': nextstate = STATE_SLEND; break; + case '^': nextstate = STATE_NOTE; break; + case ' ': + if (song[i][j+1] == ' ') nextstate = STATE_BAR; + else if (song[i][j+1] == '/') nextstate = -2; + break; + case '\0': + phend = 1; + break; + default: nextstate = -1; + } -////////////////////////////// -// -// Tool_extract::isInList -- returns true if first number found in list of numbers. -// returns the matching index plus one. -// + if (nextstate < state || + ((nextstate == STATE_NOTE) && (state == nextstate))) { + tempnote.clear(); + if (degree < 0) { // rest + tempnote.pitch = -999; + } else { + tempnote.pitch = degree + 40*(octave + 4) + accidental + tonic; + } + if (tie) { + tempnote.pitch = songdata[(int)songdata.size()-1].pitch; + if (songdata[(int)songdata.size()-1].tieend) { + songdata[(int)songdata.size()-1].tiecont = 1; + songdata[(int)songdata.size()-1].tieend = 0; + } else { + songdata[(int)songdata.size()-1].tiestart = 1; + } + tempnote.tieend = 1; + } + tempnote.duration = duration; + tempnote.phend = phend; + tempnote.bar = bar; + tempnote.phstart = phstart; + tempnote.slstart = slstart; + tempnote.slend = slend; + if (nextstate == -2) { + tempnote.bar = 2; + tempnote.phend = 1; + } + tempnote.phnum = phnum; -int Tool_extract::isInList(int number, vector& listofnum) { - int i; - for (i=0; i<(int)listofnum.size(); i++) { - if (listofnum[i] == number) { - return i+1; + songdata.push_back(tempnote); + duration = mindur; + degree = 0; + bar = 0; + tie = 0; + phend = 0; + phstart = 0; + slend = 0; + slstart = 0; + octave = 0; + accidental = 0; + if (nextstate == -2) { + return true; + } + } } + phnum++; } - return 0; + return true; } ////////////////////////////// // -// Tool_extract::getTraceData -- +// Tool_esac2humold::printNoteData -- // -void Tool_extract::getTraceData(vector& startline, vector >& fields, - const string& tracefile, HumdrumFile& infile) { - char buffer[1024] = {0}; - HumRegex hre; - int linenum; - startline.reserve(10000); - startline.resize(0); - fields.reserve(10000); - fields.resize(0); +void Tool_esac2humold::printNoteData(NoteData& data, int textQ, ostream& out) { - ifstream input; - input.open(tracefile.c_str()); - if (!input.is_open()) { - m_error_text << "Error: cannot open file for reading: " << tracefile << endl; - return; + if (data.num > 0) { + out << "*M" << data.num << "/" << data.denom; + if (textQ) { + out << "\t*M" << data.num << "/" << data.denom; + } + out << "\n"; + } + if (data.phstart == 1) { + out << "{"; + } + if (data.slstart == 1) { + out << "("; + } + if (data.tiestart == 1) { + out << "["; + } + out << Convert::durationFloatToRecip(data.duration); + if (data.pitch < 0) { + out << "r"; + } else { + out << Convert::base40ToKern(data.pitch); + } + if (data.tiecont == 1) { + out << "_"; + } + if (data.tieend == 1) { + out << "]"; + } + if (data.slend == 1) { + out << ")"; + } + if (data.phend == 1) { + out << "}"; } - string temps; - vector field; - vector subfield; - vector model; - - input.getline(buffer, 1024); - while (!input.eof()) { - if (hre.search(buffer, "^\\s*$")) { - continue; + if (textQ) { + out << "\t"; + if (data.phstart == 1) { + out << "{"; } - if (!hre.search(buffer, "(\\d+)")) { - continue; + if (data.text == "") { + if (data.pitch < 0) { + data.text = "%"; + } else { + data.text = "|"; + } } - linenum = hre.getMatchInt(1); - linenum--; // adjust so that line 0 is the first line in the file - temps = buffer; - hre.replaceDestructive(temps, "", "\\d+"); - hre.replaceDestructive(temps, "", "[^,\\s\\d\\$\\-].*"); // remove any possible comments - hre.replaceDestructive(temps, "", "\\s", "g"); - if (hre.search(temps, "^\\s*$")) { - // no field data to process online - continue; + if (data.pitch < 0 && (data.text.find('%') == string::npos)) { + out << "%"; + } + if (data.text == " *") { + if (data.pitch < 0) { + data.text = "%*"; + } else { + data.text = "|*"; + } + } + if (data.text == "^") { + data.text = "|^"; + } + printString(data.text, out); + if (data.phend == 1) { + out << "}"; } - startline.push_back(linenum); - string ttemp = temps; - fillFieldData(field, subfield, model, ttemp, infile); - fields.push_back(field); - input.getline(buffer, 1024); } + out << "\n"; + + // print barline information + if (data.bar == 1) { + + out << "="; + if (data.barnum > 0) { + out << data.barnum; + } + if (data.barinterp) { + // out << "yy"; + } + if (debugQ) { + if (data.bardur > 0.0) { + out << "[" << data.bardur << "]"; + } + } + if (textQ) { + out << "\t"; + out << "="; + if (data.barnum > 0) { + out << data.barnum; + } + if (data.barinterp) { + // out << "yy"; + } + if (debugQ) { + if (data.bardur > 0.0) { + out << "[" << data.bardur << "]"; + } + } + } + + out << "\n"; + } else if (data.bar == 2) { + out << "=="; + if (textQ) { + out << "\t=="; + } + out << "\n"; + } } ////////////////////////////// // -// Tool_extract::extractTrace -- +// Tool_esac2humold::getKeyInfo -- look for a KEY[] entry and extract the data. +// +// ggg fix this function // -void Tool_extract::extractTrace(HumdrumFile& infile, const string& tracefile) { - vector startline; - vector > fields; - getTraceData(startline, fields, tracefile, infile); - int i, j; - - if (debugQ) { - for (i=0; i<(int)startline.size(); i++) { - m_humdrum_text << "!!TRACE " << startline[i]+1 << ":\t"; - for (j=0; j<(int)fields[i].size(); j++) { - m_humdrum_text << fields[i][j] << " "; +bool Tool_esac2humold::getKeyInfo(vector& song, string& key, double& mindur, + int& tonic, string& meter, ostream& out) { + int i; + for (i=0; i<(int)song.size(); i++) { + if (song[i].compare(0, 4, "KEY[") == 0) { + key = song[i][4]; // letter + key += song[i][5]; // number + key += song[i][6]; // number + key += song[i][7]; // number + key += song[i][8]; // number + if (!isspace(song[i][9])) { + key += song[i][9]; // optional letter (sometimes ' or ") + } + if (!isspace(song[i][10])) { + key += song[i][10]; // illegal but possible extra letter + } + if (song[i][10] != ' ') { + out << "!! Warning key field is not complete" << endl; + out << "!!Key field: " << song[i] << endl; } - m_humdrum_text << "\n"; - } - } + mindur = (song[i][11] - '0') * 10 + (song[i][12] - '0'); + mindur = 4.0 / mindur; - if (startline.size() == 0) { - for (i=0; i& field) { - int j; - int t; - int start = 0; - int target; +bool Tool_esac2humold::getFileContents(vector& array, const string& filename) { + ifstream infile(filename.c_str()); + array.reserve(100); + array.resize(0); - start = 0; - for (t=0; t<(int)field.size(); t++) { - target = field[t]; - for (j=0; jgetTrack() != target) { - continue; - } - if (start != 0) { - m_humdrum_text << '\t'; - } - start = 1; - m_humdrum_text << infile.token(line, j); - } + if (!infile.is_open()) { + cerr << "Error: cannot open file: " << filename << endl; + return false; } - if (start != 0) { - m_humdrum_text << endl; + + char holdbuffer[1024] = {0}; + + infile.getline(holdbuffer, 256, '\n'); + while (!infile.eof()) { + array.push_back(holdbuffer); + infile.getline(holdbuffer, 256, '\n'); } + + infile.close(); + return true; } ////////////////////////////// // -// Tool_extract::example -- example usage of the sonority program +// Tool_esac2humold::example -- // -void Tool_extract::example(void) { - m_free_text << - " \n" - << endl; +void Tool_esac2humold::example(void) { + + } ////////////////////////////// // -// Tool_extract::usage -- gives the usage statement for the sonority program +// Tool_esac2humold::usage -- // -void Tool_extract::usage(const string& command) { - m_free_text << - " \n" - << endl; +void Tool_esac2humold::usage(const string& command) { + } ////////////////////////////// // -// Tool_extract::initialize -- +// Tool_esac2humold::printBibInfo -- // -void Tool_extract::initialize(HumdrumFile& infile) { - // handle basic options: - if (getBoolean("author")) { - m_free_text << "Written by Craig Stuart Sapp, " - << "craig@ccrma.stanford.edu, Feb 2008" << endl; - return; - } else if (getBoolean("version")) { - m_free_text << getArg(0) << ", version: Feb 2008" << endl; - m_free_text << "compiled: " << __DATE__ << endl; - return; - } else if (getBoolean("help")) { - usage(getCommand().c_str()); - return; - } else if (getBoolean("example")) { - example(); - return; - } - - excludeQ = getBoolean("x"); - interpQ = getBoolean("i"); - interps = getString("i"); - kernQ = getBoolean("k"); - rkernQ = getBoolean("K"); - - interpstate = 1; - if (!interpQ) { - interpQ = getBoolean("I"); - interpstate = 0; - interps = getString("I"); - } - if (interps.size() > 0) { - if (interps[0] != '*') { - // Automatically add ** if not given on exclusive interpretation - string tstring = "**"; - interps = tstring + interps; - } - } - - removerestQ = getBoolean("no-rest"); - noEmptyQ = getBoolean("no-empty"); - emptyQ = getBoolean("empty"); - fieldQ = getBoolean("f"); - debugQ = getBoolean("debug"); - countQ = getBoolean("count"); - traceQ = getBoolean("trace"); - tracefile = getString("trace"); - reverseQ = getBoolean("reverse"); - expandQ = getBoolean("expand") || getBoolean("E"); - submodel = getString("model").c_str()[0]; - cointerp = getString("cointerp"); - comodel = getString("cospine-model").c_str()[0]; - - if (getBoolean("no-editoral-rests")) { - editorialInterpretation = ""; - } - - if (interpQ) { - fieldQ = true; - } - - if (emptyQ) { - fieldQ = true; - } - - if (noEmptyQ) { - fieldQ = true; - } - - if (expandQ) { - fieldQ = true; - expandInterp = getString("expand-interp"); - } +void Tool_esac2humold::printBibInfo(vector& song, ostream& out) { + int i, j; + char buffer[32] = {0}; + int start = -1; + int stop = -1; + int count = 0; + string templine; - if (!reverseQ) { - reverseQ = getBoolean("R"); - if (reverseQ) { - reverseInterp = getString("R"); + for (i=0; i<(int)song.size(); i++) { + if (song[i] == "") { + continue; } - } + if (song[i][0] != ' ') { + if (song[i].size() < 4 || song[i][3] != '[') { + if (song[i].compare(0, 2, "!!") != 0) { + out << "!! " << song[i] << "\n"; + } + continue; + } + strncpy(buffer, song[i].c_str(), 3); + buffer[3] = '\0'; + if (strcmp(buffer, "MEL") == 0) continue; + if (strcmp(buffer, "TXT") == 0) continue; + // if (strcmp(buffer, "KEY") == 0) continue; + getLineRange(song, buffer, start, stop); - if (reverseQ) { - fieldQ = true; - } + // don't print CUT field if only one line. !!!OTL: will contain CUT[] + // if (strcmp(buffer, "CUT") == 0 && start == stop) continue; - if (excludeQ) { - fieldstring = getString("x"); - } else if (fieldQ) { - fieldstring = getString("f"); - } else if (kernQ) { - fieldstring = getString("k"); - fieldQ = true; - } else if (rkernQ) { - fieldstring = getString("K"); - fieldQ = true; - fieldstring = reverseFieldString(fieldstring, infile.getMaxTrack()); - } + buffer[0] = tolower(buffer[0]); + buffer[1] = tolower(buffer[1]); + buffer[2] = tolower(buffer[2]); - spineListQ = getBoolean("spine-list"); - grepQ = getBoolean("grep"); - grepString = getString("grep"); + count = 1; + templine = ""; + for (j=start; j<=stop; j++) { + if (song[j].size() < 4) { + continue; + } + if (stop - start == 0) { + templine = song[j].substr(4); + auto loc = templine.find(']'); + if (loc != string::npos) { + templine.resize(loc); + } + if (templine != "") { + out << "!!!" << buffer << ": "; + printString(templine, out); + out << "\n"; + } - if (getBoolean("name")) { - blankName = getString("name"); - if (blankName == "") { - blankName = "**blank"; - } else if (blankName.compare(0, 2, "**") != 0) { - if (blankName.compare(0, 1, "*") != 0) { - blankName = "**" + blankName; - } else { - blankName = "*" + blankName; + } else if (j==start) { + out << "!!!" << buffer << count++ << ": "; + printString(song[j].substr(4), out); + out << "\n"; + } else if (j==stop) { + templine = song[j].substr(4); + auto loc = templine.find(']'); + if (loc != string::npos) { + templine.resize(loc); + } + if (templine != "") { + out << "!!!" << buffer << count++ << ": "; + printString(templine, out); + out << "\n"; + } + } else { + out << "!!!" << buffer << count++ << ": "; + printString(&(song[j][4]), out); + out << "\n"; + } } } - if (blankName == "**kern") { - addRestsQ = true; - } } - } + ////////////////////////////// // -// Tool_extract::reverseFieldString -- No dollar expansion for now. +// Tool_esac2humold::printString -- print characters in string. // -string Tool_extract::reverseFieldString(const string& input, int maxval) { - string output; - string number; - for (int i=0; i<(int)input.size(); i++) { - if (isdigit(input[i])) { - number += input[i]; - continue; - } else { - if (!number.empty()) { - int value = (int)strtol(number.c_str(), NULL, 10); - value = maxval - value + 1; - output += to_string(value); - output += input[i]; - number.clear(); - } - } - } - if (!number.empty()) { - int value = (int)strtol(number.c_str(), NULL, 10); - value = maxval - value + 1; - output += to_string(value); +void Tool_esac2humold::printString(const string& string, ostream& out) { + for (int i=0; i<(int)string.size(); i++) { + printChar(string[i], out); } - return output; } -////////////////////////////// + + +///////////////////////////////// // -// Tool_fb::Tool_fb -- Set the recognized options for the tool. +// Tool_extract::Tool_extract -- Set the recognized options for the tool. // -Tool_fb::Tool_fb(void) { - define("c|compound=b", "output reasonable figured bass numbers within octave"); - define("a|accidentals|accid|acc=b", "display accidentals in front of the numbers"); - define("b|base|base-track=i:1", "number of the base kern track (compare with -k)"); - define("i|intervallsatz=b", "display numbers under their voice instead of under the base staff"); - define("o|sort|order=b", "sort figured bass numbers by size"); - define("l|lowest=b", "use lowest note as base note"); - define("n|normalize=b", "remove number 8 and doubled numbers; adds -co"); - define("r|reduce|abbreviate|abbr=b", "use abbreviated figures; adds -nco"); - define("t|ties=b", "hide numbers without attack or changing base (needs -i)"); - define("f|figuredbass=b", "shortcut for -acorn3"); - define("3|hide-three=b", "hide number 3 if it has an accidental"); - define("m|negative=b", "show negative numbers"); - define("above=b", "show numbers above the staff (**fba)"); - define("rate=s:", "rate to display the numbers (use a **recip value, e.g. 4, 4.)"); - define("k|kern-tracks=s", "process only the specified kern spines"); - define("s|spine-tracks|spine|spines|track|tracks=s", "Process only the specified spines"); - define("hint=b", "determine harmonic intervals with interval quality"); +Tool_extract::Tool_extract(void) { + define("P|F|S|x|exclude=s:", "remove listed spines from output"); + define("i=s:", "exclusive interpretation list to extract from input"); + define("I=s:", "exclusive interpretation exclusion list"); + define("f|p|s|field|path|spine=s:", "for extraction of particular spines"); + define("C|count=b", "print a count of the number of spines in file"); + define("c|cointerp=s:**kern", "exclusive interpretation for cospines"); + define("g|grep=s:", "extract spines which match a given regex."); + define("r|reverse=b", "reverse order of spines by **kern group"); + define("R=s:**kern", "reverse order of spine by exinterp group"); + define("t|trace=s:", "use a trace file to extract data"); + define("e|expand=b", "expand spines with subspines"); + define("k|kern=s", "extract by kern spine group"); + define("K|reverse-kern=s", "extract by kern spine group top to bottom numbering"); + define("E|expand-interp=s:", "expand subspines limited to exinterp"); + define("m|model|method=s:d", "method for extracting secondary spines"); + define("M|cospine-model=s:d", "method for extracting cospines"); + define("Y|no-editoral-rests=b", "do not display yy marks on interpreted rests"); + define("n|name|b|blank=s:**blank", "name if exinterp added with 0"); + define("no-empty|no-empties=b", "suppress spines with only null data tokens"); + define("empty|empties=b", "only keep spines with only null data tokens"); + define("spine-list=b", "show spine list and then exit"); + define("no-rest|no-rests=b", "remove **kern spines containing only rests (and their co-spines)"); + + define("debug=b", "print debugging information"); + define("author=b", "author of the program"); + define("version=b", "compilation info"); + define("example=b", "example usages"); + define("h|help=b", "short description"); } -////////////////////////////// +///////////////////////////////// // -// Tool_fb::run -- Do the main work of the tool. +// Tool_extract::run -- Primary interfaces to the tool. // -bool Tool_fb::run(HumdrumFileSet &infiles) { +bool Tool_extract::run(HumdrumFileSet& infiles) { bool status = true; - for (int i = 0; i < infiles.getCount(); i++) { + for (int i=0; i Tool_extract::getNullDataTracks(HumdrumFile& infile) { + vector output(infile.getMaxTrack() + 1, 1); + for (int i=0; igetTrack(); + if (!output[track]) { + continue; + } + if (!token->isNull()) { + output[track] = 0; + } + } + // maybe exit here if all tracks are non-null + } - NoteGrid grid(infile); + return output; +} - vector numbers; - vector kernspines = infile.getKernSpineStartList(); - int maxTrack = infile.getMaxTrack(); +////////////////////////////// +// +// Tool_extract::fillFieldDataByEmpty -- Only keep the spines which contain only +// null data tokens. +// - // Do nothing if base track not withing kern track range - if (m_baseTrackQ < 1 || m_baseTrackQ > maxTrack) { - return; - } +void Tool_extract::fillFieldDataByEmpty(vector& field, vector& subfield, + vector& model, HumdrumFile& infile, int negate) { - m_selectedKernSpines.resize(maxTrack + 1); // +1 is needed since track=0 is not used - // By default, process all tracks: - fill(m_selectedKernSpines.begin(), m_selectedKernSpines.end(), true); - // Otherwise, select which **kern track, or spine tracks to process selectively: + field.reserve(infile.getMaxTrack()+1); + subfield.reserve(infile.getMaxTrack()+1); + model.reserve(infile.getMaxTrack()+1); + field.resize(0); + subfield.resize(0); + model.resize(0); + vector nullTrack = getNullDataTracks(infile); - // Calculate which input spines to process based on -s or -k option: - if (!m_kernTracks.empty()) { - vector ktracks = Convert::extractIntegerList(m_kernTracks, maxTrack); - fill(m_selectedKernSpines.begin(), m_selectedKernSpines.end(), false); - for (int i=0; i<(int)ktracks.size(); i++) { - int index = ktracks[i] - 1; - if ((index < 0) || (index >= (int)kernspines.size())) { - continue; + int zero = 0; + for (int i=1; i<(int)nullTrack.size(); i++) { + if (negate) { + if (!nullTrack[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); + } + } else { + if (nullTrack[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } - int track = kernspines.at(ktracks[i] - 1)->getTrack(); - m_selectedKernSpines.at(track) = true; } - } else if (!m_spineTracks.empty()) { - infile.makeBooleanTrackList(m_selectedKernSpines, m_spineTracks); } - vector> lastNumbers = {}; - lastNumbers.resize((int)grid.getVoiceCount()); - vector> currentNumbers = {}; +} - // Interate through the NoteGrid and fill the numbers vector with - // all generated FiguredBassNumbers - for (int i=0; i<(int)grid.getSliceCount(); i++) { - currentNumbers.clear(); - currentNumbers.resize((int)grid.getVoiceCount()); - // Reset usedBaseKernTrack - int usedBaseKernTrack = m_baseTrackQ; - // Overwrite usedBaseKernTrack with the lowest voice index of the lowest pitched note - if (m_lowestQ) { - int lowestNotePitch = 99999; - for (int k=0; k<(int)grid.getVoiceCount(); k++) { - NoteCell* checkCell = grid.cell(k, i); - HTp currentToken = checkCell->getToken(); - int initialTokenTrack = currentToken->getTrack(); +////////////////////////////// +// +// Tool_extract::fillFieldDataByNoEmpty -- Only keep spines which are not all +// null data tokens. +// - // Handle spine splits - do { - HTp resolvedToken = currentToken->resolveNull(); - int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); +void Tool_extract::fillFieldDataByNoEmpty(vector& field, vector& subfield, + vector& model, HumdrumFile& infile, int negate) { - if (abs(lowest) < lowestNotePitch) { - lowestNotePitch = abs(lowest); - usedBaseKernTrack = k + 1; - } + field.reserve(infile.getMaxTrack()+1); + subfield.reserve(infile.getMaxTrack()+1); + model.reserve(infile.getMaxTrack()+1); + field.resize(0); + subfield.resize(0); + model.resize(0); + vector nullTrack = getNullDataTracks(infile); + for (int i=1; i<(int)nullTrack.size(); i++) { + nullTrack[i] = !nullTrack[i]; + } - HTp nextToken = currentToken->getNextField(); - if (nextToken && (initialTokenTrack == nextToken->getTrack())) { - currentToken = nextToken; - } else { - // Break loop if nextToken is not the same track as initialTokenTrack - break; - } - } while (currentToken); + int zero = 0; + for (int i=1; i<(int)nullTrack.size(); i++) { + if (negate) { + if (!nullTrack[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); + } + } else { + if (nullTrack[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } } + } +} - NoteCell* baseCell = grid.cell(usedBaseKernTrack - 1, i); - - // Ignore grace notes - if (baseCell->getToken()->getOwner()->getDuration() == 0) { - continue; - } - string keySignature = getKeySignature(infile, baseCell->getLineIndex()); - - // Hide numbers if they do not match rhythmic position of --rate - if (!m_rateQ.empty()) { - // Get time signatures - vector> timeSigs; - infile.getTimeSigs(timeSigs, baseCell->getToken()->getTrack()); - // Ignore numbers if they don't fit - if (hideNumbersForTokenLine(baseCell->getToken(), timeSigs[baseCell->getLineIndex()])) { - continue; - } - } +////////////////////////////// +// +// Tool_extract::fillFieldDataByNoRest -- Find the spines which +// contain only rests and remove them. Also remove cospines (non-kern spines +// to the right of the kern spine containing only rests). If there are +// *part# interpretations in the data, then any spine which is all rests +// will not be removed if there is another **kern spine with the same +// part number if it is also not all rests. +// - HTp currentToken = baseCell->getToken(); - int initialTokenTrack = baseCell->getToken()->getTrack(); - int lowestBaseNoteBase40Pitch = 9999; +void Tool_extract::fillFieldDataByNoRest(vector& field, vector& subfield, + vector& model, const string& searchstring, HumdrumFile& infile, + int state) { - // Handle spine splits - do { - HTp resolvedToken = currentToken->resolveNull(); - int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + field.clear(); + subfield.clear(); + model.clear(); - // Ignore if base is a rest or silent note - if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) { - if(abs(lowest) < lowestBaseNoteBase40Pitch) { - lowestBaseNoteBase40Pitch = abs(lowest); - } - } - HTp nextToken = currentToken->getNextField(); - if (nextToken && (initialTokenTrack == nextToken->getTrack())) { - currentToken = nextToken; - } else { - // Break loop if nextToken is not the same track as initialTokenTrack - break; - } - } while (currentToken); + // Check every **kern spine for any notes. If there is a note + // then the tracks variable for that spine will be marked + // as non-zero. + vector tracks(infile.getMaxTrack() + 1, 0); + int track; + int partline = 0; + bool dataQ = false; + for (int i=0; igetToken()->getTrack()) == false) { + dataQ = true; + for (int j=0; jisKern()) { continue; } + if (token->isNull()) { + continue; + } + if (token->isRest()) { + continue; + } + track = token->getTrack(); + tracks[track] = 1; + } + } - HTp currentToken = targetCell->getToken(); - int initialTokenTrack = targetCell->getToken()->getTrack(); - vector chordNumbers = {}; - - // Handle spine splits - do { - HTp resolvedToken = currentToken->resolveNull(); - for (int subtokenBase40: resolvedToken->getBase40Pitches()) { - - // Ignore if target is a rest or silent note - if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) { - continue; - } - - // Ignore if same pitch as base voice - if ((abs(lowestBaseNoteBase40Pitch) == abs(subtokenBase40)) && (baseCell->getToken()->getTrack() == initialTokenTrack)) { - continue; - } - - // Create FiguredBassNumber - FiguredBassNumber* number = createFiguredBassNumber(abs(lowestBaseNoteBase40Pitch), abs(subtokenBase40), targetCell->getVoiceIndex(), targetCell->getLineIndex(), targetCell->isAttack(), keySignature); - - currentNumbers[j].push_back(number->m_number); - chordNumbers.push_back(number); + // Go back and mark any empty spines as non-empty if they + // are in a part that contains multiple staves. I.e., only + // delete a staff if all staves for the part are empty. + // There should be a single *part# line at the start of the + // score. + if (partline > 0) { + vector kerns; + for (int i=0; iisKern()) { + continue; + } + kerns.push_back(token); + } + for (int i=0; i<(int)kerns.size(); i++) { + for (int j=i+1; j<(int)kerns.size(); j++) { + if (*kerns[i] != *kerns[j]) { + continue; } - - HTp nextToken = currentToken->getNextField(); - if (nextToken && (initialTokenTrack == nextToken->getTrack())) { - currentToken = nextToken; - } else { - // Break loop if nextToken is not the same track as initialTokenTrack - break; + if (kerns[i]->find("*part") == string::npos) { + continue; } - } while (currentToken); - - // Sort chord numbers by size - sort(chordNumbers.begin(), chordNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { - return a->m_number > b->m_number; - }); - - // Then add to numbers vector - for (FiguredBassNumber* num: chordNumbers) { - if (lastNumbers[j].size() != 0) { - // If a number belongs to a sustained note but the base note did change - // the new numbers need to be displayable - num->m_baseOfSustainedNoteDidChange = !num->m_isAttack && std::find(lastNumbers[j].begin(), lastNumbers[j].end(), num->m_number) == lastNumbers[j].end(); + int track1 = kerns[i]->getTrack(); + int track2 = kerns[j]->getTrack(); + int state1 = tracks[track1]; + int state2 = tracks[track2]; + if ((state1 && !state2) || (state2 && !state1)) { + // Prevent empty staff from being removed + // from a multi-staff part: + tracks[track1] = 1; + tracks[track2] = 1; } - numbers.push_back(num); } } - - // Set current numbers as the new last numbers - lastNumbers = currentNumbers; } - string exinterp = m_aboveQ ? "**fba" : "**fb"; - if (m_hintQ) { - exinterp = "**hint"; + // deal with co-spines + vector sstarts; + infile.getSpineStartList(sstarts); + for (int i=0; i<(int)sstarts.size(); i++) { + if (!sstarts[i]->isKern()) { + track = sstarts[i]->getTrack(); + tracks[track] = 1; + } } - if (m_intervallsatzQ) { - // Create **fb spine for each voice - for (int voiceIndex = 0; voiceIndex < grid.getVoiceCount(); voiceIndex++) { - vector trackData = getTrackDataForVoice(voiceIndex, numbers, infile.getLineCount()); - if (voiceIndex + 1 < grid.getVoiceCount()) { - int trackIndex = kernspines[voiceIndex + 1]->getTrack(); - infile.insertDataSpineBefore(trackIndex, trackData, ".", exinterp); - } else { - infile.appendDataSpine(trackData, ".", exinterp); + // remove co-spines attached to removed kern spines + for (int i=0; i<(int)sstarts.size(); i++) { + if (!sstarts[i]->isKern()) { + continue; + } + if (tracks[sstarts[i]->getTrack()] != 0) { + continue; + } + for (int j=i+1; j<(int)sstarts.size(); j++) { + if (sstarts[j]->isKern()) { + break; } + track = sstarts[j]->getTrack(); + tracks[track] = 0; } - } else { - // Create **fb spine and bind it to the base voice - vector trackData = getTrackData(numbers, infile.getLineCount()); - if (m_baseTrackQ < grid.getVoiceCount()) { - int trackIndex = kernspines[m_baseTrackQ]->getTrack(); - infile.insertDataSpineBefore(trackIndex, trackData, ".", exinterp); - } else { - infile.appendDataSpine(trackData, ".", exinterp); + } + + int zero = 0; + for (int i=1; i<(int)tracks.size(); i++) { + if (state != 0) { + tracks[i] = !tracks[i]; + } + if (tracks[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } } - // Enables usage in verovio (`!!!filter: fb`) - m_humdrum_text << infile; } ////////////////////////////// // -// Tool_fb::hideNumbersForTokenLine -- Checks if rhythmic position of line should display numbers +// Tool_extract::fillFieldDataByGrep -- // -bool Tool_fb::hideNumbersForTokenLine(HTp token, pair timeSig) { - // Get note duration from --rate option - HumNum rateDuration = Convert::recipToDuration(m_rateQ); - if (rateDuration.toFloat() != 0) { - double timeSigBarDuration = timeSig.first * Convert::recipToDuration(to_string(timeSig.second.getInteger())).toFloat(); - double durationFromBarline = token->getDurationFromBarline().toFloat(); - // Handle upbeats - if (token->getBarlineDuration().toFloat() < timeSigBarDuration) { - // Fix durationFromBarline when current bar duration is shorter than - // the bar duration of the time signature - durationFromBarline = timeSigBarDuration - token->getDurationToBarline().toFloat(); - } - // Checks if rhythmic position is divisible by rateDuration - return fmod(durationFromBarline, rateDuration.toFloat()) != 0; - } - return false; -} - - +void Tool_extract::fillFieldDataByGrep(vector& field, vector& subfield, + vector& model, const string& searchstring, HumdrumFile& infile, + int state) { -////////////////////////////// -// -// Tool_fb::getTrackData -- Create **fb spine data with formatted numbers for all voices -// + field.reserve(infile.getMaxTrack()+1); + subfield.reserve(infile.getMaxTrack()+1); + model.reserve(infile.getMaxTrack()+1); + field.resize(0); + subfield.resize(0); + model.resize(0); -vector Tool_fb::getTrackData(const vector& numbers, int lineCount) { - vector trackData; - trackData.resize(lineCount); + vector tracks; + tracks.resize(infile.getMaxTrack()+1); + fill(tracks.begin(), tracks.end(), 0); + HumRegex hre; + int track; - for (int i = 0; i < lineCount; i++) { - vector sliceNumbers = filterFiguredBassNumbersForLine(numbers, i); - if (sliceNumbers.size() > 0) { - trackData[i] = formatFiguredBassNumbers(sliceNumbers); + int i, j; + for (i=0; igetTrack(); + tracks[track] = 1; + } } } - return trackData; -} - - - -////////////////////////////// -// -// Tool_fb::getTrackDataForVoice -- Create **fb spine data with formatted numbers for passed voiceIndex -// - -vector Tool_fb::getTrackDataForVoice(int voiceIndex, const vector& numbers, int lineCount) { - vector trackData; - trackData.resize(lineCount); - - for (int i = 0; i < lineCount; i++) { - vector sliceNumbers = filterFiguredBassNumbersForLineAndVoice(numbers, i, voiceIndex); - if (sliceNumbers.size() > 0) { - trackData[i] = formatFiguredBassNumbers(sliceNumbers); + int zero = 0; + for (i=1; i<(int)tracks.size(); i++) { + if (state != 0) { + tracks[i] = !tracks[i]; + } + if (tracks[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } } - - return trackData; } ////////////////////////////// // -// Tool_fb::createFiguredBassNumber -- Create FiguredBassNumber from a NoteCell. -// The figured bass number (num) is calculated with a base and target NoteCell -// as well as a passed key signature. +// Tool_extract::getInterpretationFields -- // -FiguredBassNumber* Tool_fb::createFiguredBassNumber(int basePitchBase40, int targetPitchBase40, int voiceIndex, int lineIndex, bool isAttack, string keySignature) { - - // Calculate figured bass number - int baseDiatonicPitch = Convert::base40ToDiatonic(basePitchBase40); - int targetDiatonicPitch = Convert::base40ToDiatonic(targetPitchBase40); - int diff = abs(targetDiatonicPitch) - abs(baseDiatonicPitch); - int num; +void Tool_extract::getInterpretationFields(vector& field, vector& subfield, + vector& model, HumdrumFile& infile, string& interps, int state) { + vector sstrings; // search strings + sstrings.reserve(100); + sstrings.resize(0); - if ((baseDiatonicPitch == 0) || (targetDiatonicPitch == 0)) { - num = 0; - } else if (diff == 0) { - num = 1; - } else if (diff > 0) { - num = diff + 1; - } else { - num = diff - 1; - } + int i, j, k; + string buffer; + buffer = interps; - // Transform key signature to lower case - transform(keySignature.begin(), keySignature.end(), keySignature.begin(), [](unsigned char c) { - return tolower(c); - }); + HumRegex hre; + hre.replaceDestructive(buffer, "", "\\s+", "g"); - char targetPitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(targetPitchBase40)); - int targetAccidNr = Convert::base40ToAccidental(targetPitchBase40); - string targetAccid; - for (int i=0; i tracks; + tracks.resize(infile.getMaxTrack()+1); + fill(tracks.begin(), tracks.end(), 0); - // Show accidentals when they are not included in the key signature - if ((targetAccidNr != 0) && (keySignature.find(targetPitchName + targetAccid) == std::string::npos)) { - showAccid = true; + // Algorithm below could be made more efficient by + // not searching the entire file... + for (i=0; igetTrack()] = 1; + } + } + } } - // Show natural accidentals when they are alterations of the key signature - if ((targetAccidNr == 0) && (keySignature.find(targetPitchName + targetAccid) != std::string::npos)) { - accid = "n"; - showAccid = true; - } + field.reserve(tracks.size()); + subfield.reserve(tracks.size()); + model.reserve(tracks.size()); - // Show accidentlas when pitch class of base and target is equal but alteration is different - if (basePitchName == targetPitchName) { - if (baseAccidNr == targetAccidNr) { - showAccid = false; - } else { - accid = (targetAccidNr == 0) ? "n" : targetAccid; - showAccid = true; + field.resize(0); + subfield.resize(0); + model.resize(0); + + int zero = 0; + for (i=1; i<(int)tracks.size(); i++) { + if (state == 0) { + tracks[i] = !tracks[i]; + } + if (tracks[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } } - string intervalQuality = getIntervalQuality(basePitchBase40, targetPitchBase40); - - FiguredBassNumber* number = new FiguredBassNumber(num, accid, showAccid, voiceIndex, lineIndex, isAttack, m_intervallsatzQ, intervalQuality, m_hintQ); - - return number; } ////////////////////////////// // -// Tool_fb::filterNegativeNumbers -- Hide negative numbers if m_showNegativeQ if not true +// Tool_extract::expandSpines -- // -vector Tool_fb::filterNegativeNumbers(vector numbers) { +void Tool_extract::expandSpines(vector& field, vector& subfield, vector& model, + HumdrumFile& infile, string& interp) { - vector filteredNumbers; + vector splits; + splits.resize(infile.getMaxTrack()+1); + fill(splits.begin(), splits.end(), 0); - bool mQ = m_showNegativeQ; - copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [mQ](FiguredBassNumber* num) { - return mQ ? true : (num->m_number > 0); - }); + int i, j; + for (i=0; igetSpineInfo().c_str(), '(') != NULL) { + splits[infile[i].token(j)->getTrack()] = 1; + } + } + } + field.reserve(infile.getMaxTrack()*2); + field.resize(0); + subfield.reserve(infile.getMaxTrack()*2); + subfield.resize(0); -////////////////////////////// -// -// Tool_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music. -// + model.reserve(infile.getMaxTrack()*2); + model.resize(0); -vector Tool_fb::filterFiguredBassNumbersForLine(vector numbers, int lineIndex) { + bool allQ = interp.empty(); - vector filteredNumbers; + vector dummyfield; + vector dummysubfield; + vector dummymodel; + getInterpretationFields(dummyfield, dummysubfield, model, infile, interp, 1); - // filter numbers with passed lineIndex - copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex](FiguredBassNumber* num) { - return num->m_lineIndex == lineIndex; - }); + vector interptracks; - // sort by voiceIndex - sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { - return a->m_voiceIndex > b->m_voiceIndex; - }); + interptracks.resize(infile.getMaxTrack()+1); + fill(interptracks.begin(), interptracks.end(), 0); - return filterNegativeNumbers(filteredNumbers); + for (i=0; i<(int)dummyfield.size(); i++) { + interptracks[dummyfield[i]] = 1; + } + + int aval = 'a'; + int bval = 'b'; + int zero = 0; + for (i=1; i<(int)splits.size(); i++) { + if (splits[i] && (allQ || interptracks[i])) { + field.push_back(i); + subfield.push_back(aval); + model.push_back(zero); + field.push_back(i); + subfield.push_back(bval); + model.push_back(zero); + } else { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); + } + } + + if (debugQ) { + m_humdrum_text << "!!expand: "; + for (i=0; i<(int)field.size(); i++) { + m_humdrum_text << field[i]; + if (subfield[i]) { + m_humdrum_text << (char)subfield[i]; + } + if (i < (int)field.size()-1) { + m_humdrum_text << ","; + } + } + m_humdrum_text << endl; + } } ////////////////////////////// // -// Tool_fb::filterFiguredBassNumbersForLineAndVoice -- +// Tool_extract::reverseSpines -- reverse the order of spines, grouped by the +// given exclusive interpretation. // -vector Tool_fb::filterFiguredBassNumbersForLineAndVoice(vector numbers, int lineIndex, int voiceIndex) { +void Tool_extract::reverseSpines(vector& field, vector& subfield, + vector& model, HumdrumFile& infile, const string& exinterp) { - vector filteredNumbers; + vector target; + target.resize(infile.getMaxTrack()+1); + fill(target.begin(), target.end(), 0); - // filter numbers with passed lineIndex and passed voiceIndex - copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex, voiceIndex](FiguredBassNumber* num) { - return (num->m_lineIndex == lineIndex) && (num->m_voiceIndex == voiceIndex); - }); + vector trackstarts; + infile.getSpineStartList(trackstarts); - // sort by voiceIndex (probably not needed here) - sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { - return a->m_voiceIndex > b->m_voiceIndex; - }); + for (int t=0; t<(int)trackstarts.size(); t++) { + if (trackstarts[t]->isDataType(exinterp)) { + target.at(t + 1) = 1; + } + } - return filterNegativeNumbers(filteredNumbers); -} - - - -////////////////////////////// -// -// Tool_fb::formatFiguredBassNumbers -- Create a **fb data record string out of the passed FiguredBassNumber objects -// - -string Tool_fb::formatFiguredBassNumbers(const vector& numbers) { - - vector formattedNumbers; - - // Normalize numbers (remove 8 and 1, sort by size, remove duplicate numbers) - if (m_normalizeQ) { - bool aQ = m_accidentalsQ; - // remove 8 and 1 but keep them if they have an accidental - copy_if(numbers.begin(), numbers.end(), back_inserter(formattedNumbers), [aQ](FiguredBassNumber* num) { - return ((num->getNumberWithinOctave() != 8) && (num->getNumberWithinOctave() != 1)) || (aQ && num->m_showAccidentals); - }); - // sort by size - sort(formattedNumbers.begin(), formattedNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { - return a->getNumberWithinOctave() < b->getNumberWithinOctave(); - }); - // remove duplicate numbers - formattedNumbers.erase(unique(formattedNumbers.begin(), formattedNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) { - return a->getNumberWithinOctave() == b->getNumberWithinOctave(); - }), formattedNumbers.end()); - } else { - formattedNumbers = numbers; - } + field.reserve(infile.getMaxTrack()*2); + field.resize(0); - // Hide numbers if they have no attack - if (m_intervallsatzQ && m_attackQ) { - vector attackNumbers; - copy_if(formattedNumbers.begin(), formattedNumbers.end(), back_inserter(attackNumbers), [](FiguredBassNumber* num) { - return num->m_isAttack || num->m_baseOfSustainedNoteDidChange; - }); - formattedNumbers = attackNumbers; + int lasti = (int)target.size(); + for (int i=(int)target.size()-1; i>0; i--) { + if (target[i]) { + lasti = i; + field.push_back(i); + for (int j=i+1; j<(int)target.size(); j++) { + if (!target.at(j)) { + field.push_back(j); + } else { + break; + } + } + } } - // Analysze before sorting - if (m_compoundQ) { - formattedNumbers = analyzeChordNumbers(formattedNumbers); + // if the grouping spine is not first, then preserve the + // locations of the pre-spines. + int extras = 0; + if (lasti != 1) { + extras = lasti - 1; + field.resize(field.size()+extras); + for (int i=0; i<(int)field.size()-extras; i++) { + field[(int)field.size()-1-i] = field[(int)field.size()-1-extras-i]; + } + for (int i=0; i bool { - // sort by getNumberWithinOctave if compoundQ is true otherwise sort by number - return (cQ) ? a->getNumberWithinOctave() > b->getNumberWithinOctave() : a->m_number > b->m_number; - }); + if (debugQ) { + m_humdrum_text << "!!reverse: "; + for (int i=0; i<(int)field.size(); i++) { + m_humdrum_text << field[i] << " "; + } + m_humdrum_text << endl; } - if (m_reduceQ) { - // Overwrite formattedNumbers with abbreviated numbers - formattedNumbers = getAbbreviatedNumbers(formattedNumbers); - } + subfield.resize(field.size()); + fill(subfield.begin(), subfield.end(), 0); - // join numbers - string str = ""; - bool first = true; - for (FiguredBassNumber* number: formattedNumbers) { - string num = number->toString(m_compoundQ, m_accidentalsQ, m_hideThreeQ); - if (num.length() > 0) { - if (!first) str += " "; - first = false; - str += num; - } - } - return str; + model.resize(field.size()); + fill(model.begin(), model.end(), 0); } ////////////////////////////// // -// Tool_fb::getAbbreviatedNumbers -- Get abbreviated figured bass numbers -// If no abbreviation is found all numbers will be shown +// Tool_extract::fillFieldData -- +// -vector Tool_fb::getAbbreviatedNumbers(const vector& numbers) { +void Tool_extract::fillFieldData(vector& field, vector& subfield, + vector& model, string& fieldstring, HumdrumFile& infile) { - vector abbreviatedNumbers; + int maxtrack = infile.getMaxTrack(); - string numberString = getNumberString(numbers); + field.reserve(maxtrack); + field.resize(0); - // Check if an abbreviation exists for passed numbers - auto it = find_if(FiguredBassAbbreviationMapping::s_mappings.begin(), FiguredBassAbbreviationMapping::s_mappings.end(), [&numberString](const FiguredBassAbbreviationMapping& abbr) { - return abbr.m_str == numberString; - }); + subfield.reserve(maxtrack); + subfield.resize(0); - if (it != FiguredBassAbbreviationMapping::s_mappings.end()) { - const FiguredBassAbbreviationMapping& abbr = *it; - bool aQ = m_accidentalsQ; - // Store numbers to display by the abbreviation mapping in abbreviatedNumbers - copy_if(numbers.begin(), numbers.end(), back_inserter(abbreviatedNumbers), [&abbr, aQ](FiguredBassNumber* num) { - const vector& nums = abbr.m_numbers; - // Show numbers if they are part of the abbreviation mapping or if they have an accidental - return (find(nums.begin(), nums.end(), num->getNumberWithinOctave()) != nums.end()) || (num->m_showAccidentals && aQ); - }); + model.reserve(maxtrack); + model.resize(0); - return abbreviatedNumbers; + HumRegex hre; + string buffer = fieldstring; + hre.replaceDestructive(buffer, "", "\\s", "gs"); + int start = 0; + string tempstr; + vector tempfield; + vector tempsubfield; + vector tempmodel; + while (hre.search(buffer, start, "^([^,]+,?)")) { + tempfield.clear(); + tempsubfield.clear(); + tempmodel.clear(); + processFieldEntry(tempfield, tempsubfield, tempmodel, hre.getMatch(1), infile); + start += hre.getMatchEndIndex(1); + field.insert(field.end(), tempfield.begin(), tempfield.end()); + subfield.insert(subfield.end(), tempsubfield.begin(), tempsubfield.end()); + model.insert(model.end(), tempmodel.begin(), tempmodel.end()); } - - return numbers; } ////////////////////////////// // -// Tool_fb::analyzeChordNumbers -- Analyze chord numbers and improve them -// Set m_convert2To9 to true when a 3 is included in the chord numbers. - -vector Tool_fb::analyzeChordNumbers(const vector& numbers) { +// Tool_extract::processFieldEntry -- +// 3-6 expands to 3 4 5 6 +// $ expands to maximum spine track +// $-1 expands to maximum spine track minus 1, etc. +// - vector analyzedNumbers = numbers; +void Tool_extract::processFieldEntry(vector& field, + vector& subfield, vector& model, const string& astring, + HumdrumFile& infile) { - // Check if compound numbers 3 is withing passed numbers (chord) - auto it = find_if(analyzedNumbers.begin(), analyzedNumbers.end(), [](FiguredBassNumber* number) { - return number->getNumberWithinOctave() == 3; - }); - if (it != analyzedNumbers.end()) { - for (auto &number : analyzedNumbers) { - number->m_convert2To9 = true; - } - } + int finitsize = (int)field.size(); + int maxtrack = infile.getMaxTrack(); - return analyzedNumbers; -} + vector ktracks; + infile.getKernSpineStartList(ktracks); + int maxkerntrack = (int)ktracks.size(); + int modletter; + int subletter; + HumRegex hre; + string buffer = astring; -////////////////////////////// -// -// Tool_fb::getNumberString -- Get only the numbers (without accidentals) of passed FiguredBassNumbers -// + // remove any comma left at end of input astring (or anywhere else) + hre.replaceDestructive(buffer, "", ",", "g"); -string Tool_fb::getNumberString(vector numbers) { - // Sort numbers by size - sort(numbers.begin(), numbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { - return a->getNumberWithinOctave() > b->getNumberWithinOctave(); - }); - // join numbers - string str = ""; - bool first = true; - for (FiguredBassNumber* nr: numbers) { - int num = nr->getNumberWithinOctave(); - if (num > 0) { - if (!first) str += " "; - first = false; - str += to_string(num); - } + // first remove $ symbols and replace with the correct values + if (kernQ) { + removeDollarsFromString(buffer, maxkerntrack); + } else { + removeDollarsFromString(buffer, maxtrack); } - return str; -} - + int zero = 0; + if (hre.search(buffer, "^(\\d+)-(\\d+)$")) { + int firstone = hre.getMatchInt(1); + int lastone = hre.getMatchInt(2); -////////////////////////////// -// -// Tool_fb::getKeySignature -- Get the key signature for a line index of the input file -// + if ((firstone < 1) && (firstone != 0)) { + m_error_text << "Error: range token: \"" << astring << "\"" + << " contains too small a number at start: " << firstone << endl; + m_error_text << "Minimum number allowed is " << 1 << endl; + return; + } + if ((lastone < 1) && (lastone != 0)) { + m_error_text << "Error: range token: \"" << astring << "\"" + << " contains too small a number at end: " << lastone << endl; + m_error_text << "Minimum number allowed is " << 1 << endl; + return; + } + if (firstone > maxtrack) { + m_error_text << "Error: range token: \"" << astring << "\"" + << " contains number too large at start: " << firstone << endl; + m_error_text << "Maximum number allowed is " << maxtrack << endl; + return; + } + if (lastone > maxtrack) { + m_error_text << "Error: range token: \"" << astring << "\"" + << " contains number too large at end: " << lastone << endl; + m_error_text << "Maximum number allowed is " << maxtrack << endl; + return; + } -string Tool_fb::getKeySignature(HumdrumFile& infile, int lineIndex) { - string keySignature = ""; - [&] { - for (int i = 0; i < infile.getLineCount(); i++) { - if (i > lineIndex) { - return; + if (firstone > lastone) { + for (int i=firstone; i>=lastone; i--) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } - HLp line = infile.getLine(i); - for (int j = 0; j < line->getFieldCount(); j++) { - if (line->token(j)->isKeySignature()) { - keySignature = line->getTokenString(j); - } + } else { + for (int i=firstone; i<=lastone; i++) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } } - }(); - return keySignature; -} + } else if (hre.search(buffer, "^(\\d+)([a-z]*)")) { + int value = hre.getMatchInt(1); + modletter = 0; + subletter = 0; + if (hre.getMatch(2) == "a") { + subletter = 'a'; + } + if (hre.getMatch(2) == "b") { + subletter = 'b'; + } + if (hre.getMatch(2) == "c") { + subletter = 'c'; + } + if (hre.getMatch(2) == "d") { + modletter = 'd'; + } + if (hre.getMatch(2) == "n") { + modletter = 'n'; + } + if (hre.getMatch(2) == "r") { + modletter = 'r'; + } + if ((value < 1) && (value != 0)) { + m_error_text << "Error: range token: \"" << astring << "\"" + << " contains too small a number at end: " << value << endl; + m_error_text << "Minimum number allowed is " << 1 << endl; + return; + } + if (value > maxtrack) { + m_error_text << "Error: range token: \"" << astring << "\"" + << " contains number too large at start: " << value << endl; + m_error_text << "Maximum number allowed is " << maxtrack << endl; + return; + } + field.push_back(value); + if (value == 0) { + subfield.push_back(zero); + model.push_back(zero); + } else { + subfield.push_back(subletter); + model.push_back(modletter); + } + } + if (!kernQ) { + return; + } -////////////////////////////// -// -// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent -// TODO: Handle negative values and sustained notes -// + // Insert fields to next **kern spine. + vector newfield; + vector newsubfield; + vector newmodel; -int Tool_fb::getLowestBase40Pitch(vector base40Pitches) { - vector filteredBase40Pitches; - copy_if(base40Pitches.begin(), base40Pitches.end(), std::back_inserter(filteredBase40Pitches), [](int base40Pitch) { - // Ignore if base is a rest or silent note - return (base40Pitch != -1000) && (base40Pitch != -2000) && (base40Pitch != 0); - }); + vector trackstarts; + infile.getTrackStartList(trackstarts); + int spine; - if (filteredBase40Pitches.size() == 0) { - return -2000; + // convert kern tracks into spine tracks: + for (int i=finitsize; i<(int)field.size(); i++) { + if (field[i] > 0) { + spine = ktracks[field[i]-1]->getTrack(); + field[i] = spine; + } } - return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches)); + int startspineindex, stopspineindex; + for (int i=0; i<(int)field.size(); i++) { + newfield.push_back(field[i]); // copy **kern spine index into new list + newsubfield.push_back(subfield[i]); + newmodel.push_back(model[i]); + + // search for non **kern spines after specified **kern spine: + startspineindex = field[i] + 1 - 1; + stopspineindex = maxtrack; + for (int j=startspineindex; jisKern()) { + break; + } + newfield.push_back(j+1); + newsubfield.push_back(zero); + newmodel.push_back(zero); + } + } + + field = newfield; + subfield = newsubfield; + model = newmodel; } ////////////////////////////// // -// Tool_fb::getIntervalQuality -- Return interval quality prefix string +// Tool_extract::removeDollarsFromString -- substitute $ sign for maximum track count. // -string Tool_fb::getIntervalQuality(int basePitchBase40, int targetPitchBase40) { - - int diff = (targetPitchBase40 - basePitchBase40) % 40; - - diff = diff < -2 ? abs(diff) : diff; - - // See https://wiki.ccarh.org/wiki/Base_40 - string quality; - switch (diff) { - // 1 - case -2: - case 38: - quality = "dd"; break; - case -1: - case 39: - quality = "d"; break; - case 0: quality = "P"; break; - case 1: quality = "A"; break; - case 2: quality = "AA"; break; - - // 2 - case 3: quality = "dd"; break; - case 4: quality = "d"; break; - case 5: quality = "m"; break; - case 6: quality = "M"; break; - case 7: quality = "A"; break; - case 8: quality = "AA"; break; - - // 3 - case 9: quality = "dd"; break; - case 10: quality = "d"; break; - case 11: quality = "m"; break; - case 12: quality = "M"; break; - case 13: quality = "A"; break; - case 14: quality = "AA"; break; - - // 4 - case 15: quality = "dd"; break; - case 16: quality = "d"; break; - case 17: quality = "P"; break; - case 18: quality = "A"; break; - case 19: quality = "AA"; break; - - case 20: quality = ""; break; - - // 5 - case 21: quality = "dd"; break; - case 22: quality = "d"; break; - case 23: quality = "P"; break; - case 24: quality = "A"; break; - case 25: quality = "AA"; break; - - // 6 - case 26: quality = "dd"; break; - case 27: quality = "d"; break; - case 28: quality = "m"; break; - case 29: quality = "M"; break; - case 30: quality = "A"; break; - case 31: quality = "AA"; break; +void Tool_extract::removeDollarsFromString(string& buffer, int maxtrack) { + HumRegex hre; + char buf2[128] = {0}; + int value2; - // 7 - case 32: quality = "dd"; break; - case 33: quality = "d"; break; - case 34: quality = "m"; break; - case 35: quality = "M"; break; - case 36: quality = "A"; break; - case 37: quality = "AA"; break; + if (hre.search(buffer, "\\$$")) { + snprintf(buf2, 128, "%d", maxtrack); + hre.replaceDestructive(buffer, buf2, "\\$$"); + } - default: quality = "?"; break; + if (hre.search(buffer, "\\$(?![\\d-])")) { + // don't know how this case could happen, however... + snprintf(buf2, 128, "%d", maxtrack); + hre.replaceDestructive(buffer, buf2, "\\$(?![\\d-])", "g"); } - return quality; + if (hre.search(buffer, "\\$0")) { + // replace $0 with maxtrack (used for reverse orderings) + snprintf(buf2, 128, "%d", maxtrack); + hre.replaceDestructive(buffer, buf2, "\\$0", "g"); + } + while (hre.search(buffer, "\\$(-?\\d+)")) { + value2 = maxtrack - abs(hre.getMatchInt(1)); + snprintf(buf2, 128, "%d", value2); + hre.replaceDestructive(buffer, buf2, "\\$-?\\d+"); + } } ////////////////////////////// // -// FiguredBassNumber::FiguredBassNumber -- Constructor +// Tool_extract::excludeFields -- print all spines except the ones in the list of fields. // -FiguredBassNumber::FiguredBassNumber(int num, string accid, bool showAccid, int voiceIdx, int lineIdx, bool isAtk, bool intervallsatz, string intervalQuality, bool hint) { - m_number = num; - m_accidentals = accid; - m_voiceIndex = voiceIdx; - m_lineIndex = lineIdx; - m_showAccidentals = showAccid; - m_isAttack = isAtk; - m_intervallsatz = intervallsatz; - m_intervalQuality = intervalQuality; - m_hint = hint; +void Tool_extract::excludeFields(HumdrumFile& infile, vector& field, + vector& subfield, vector& model) { + int start = 0; + for (int i=0; igetTrack(), field)) { + continue; + } + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } + if (start != 0) { + m_humdrum_text << endl; + } + } + } } ////////////////////////////// // -// FiguredBassNumber::toString -- Convert FiguredBassNumber to a string (accidental + number) +// Tool_extract::extractFields -- print all spines in the list of fields. // -string FiguredBassNumber::toString(bool compoundQ, bool accidentalsQ, bool hideThreeQ) { - int num = (compoundQ) ? getNumberWithinOctave() : m_number; - if (m_hint) { - return m_intervalQuality + to_string(abs(num)); - } - string accid = (accidentalsQ && m_showAccidentals) ? m_accidentals : ""; - if (((num == 3) || (num == -3)) && accidentalsQ && m_showAccidentals && hideThreeQ) { - return accid; - } - if (num > 0) { - return accid + to_string(num); - } - if (num < 0) { - return accid + "~" + to_string(abs(num)); - } - return ""; -} - +void Tool_extract::extractFields(HumdrumFile& infile, vector& field, + vector& subfield, vector& model) { + HumRegex hre; + int start = 0; + int target; + int subtarget; + int modeltarget; + string spat; + bool foundBarline = true; -////////////////////////////// -// -// FiguredBassNumber::getNumberWithinOctave -- Get a reasonable figured bass number -// Replace 0 with 7 and -7 -// Replace 1 with 8 and -8 -// Replace 2 with 9 if it is a suspension of the ninth -// Allow 1 (unisono) in intervallsatz + for (int i=0; i 0) && (m_number % 7 == 0)) { - return m_number < 0 ? -7 : 7; - } + if (infile[i].isBarline()) { + foundBarline = true; + } - // Replace 1 with 8 and -8 - if (abs(num) == 1) { - // Allow unisono in intervallsatz - if (m_intervallsatz || m_hint) { - if (abs(m_number) == 1) { - return 1; + start = 0; + for (int t=0; t<(int)field.size(); t++) { + target = field[t]; + subtarget = subfield[t]; + modeltarget = model[t]; + if (modeltarget == 0) { + switch (subtarget) { + case 'a': + case 'b': + modeltarget = submodel; + break; + case 'c': + modeltarget = comodel; + } + } + if (target == 0) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + if (!infile[i].isManipulator()) { + if (infile[i].isLocalComment()) { + m_humdrum_text << "!"; + } else if (infile[i].isBarline()) { + m_humdrum_text << infile[i].token(0); + } else if (infile[i].isData()) { + if (foundBarline) { + if (addRestsQ) { + HumNum dur = infile[i].getDurationToBarline(); + m_humdrum_text << Convert::durationToRecip(dur); + } else { + m_humdrum_text << "."; + } + } else { + m_humdrum_text << "."; + } + // interpretations handled in dealWithSpineManipulators() + // [obviously not, so adding a blank one here + } else if (infile[i].isInterpretation()) { + HTp token = infile.token(i, 0); + if (token->isExpansionLabel()) { + m_humdrum_text << token; + } else if (token->isExpansionList()) { + m_humdrum_text << token; + } else { + if (addRestsQ) { + printInterpretationForKernSpine(infile, i); + } else { + m_humdrum_text << "*"; + } + } + } + } + } else { + for (int j=0; jgetTrack() != target) { + continue; + } + switch (subtarget) { + case 'a': + getSearchPat(spat, target, "a"); + if (hre.search(infile.token(i,j)->getSpineInfo(), spat) || + !hre.search(infile.token(i, j)->getSpineInfo(), "\\(")) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } + break; + case 'b': + getSearchPat(spat, target, "b"); + if (hre.search(infile.token(i, j)->getSpineInfo(), spat)) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } else if (!hre.search(infile.token(i, j)->getSpineInfo(), + "\\(")) { + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + dealWithSecondarySubspine(field, subfield, model, t, + infile, i, j, modeltarget); + } + break; + case 'c': + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + dealWithCospine(field, subfield, model, t, infile, i, j, + modeltarget, modeltarget, cointerp); + break; + default: + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(i, j); + } + } } } - return m_number < 0 ? -8 : 8; - } - // Replace 2 with 9 if m_convert2To9 is true (e.g. when a 3 is included in the chord numbers) - if (m_convert2To9 && (num == 2)) { - return 9; - } + if (infile[i].isData()) { + foundBarline = false; + } - return num; + if (start != 0) { + m_humdrum_text << endl; + } + } } ////////////////////////////// // -// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor -// Helper class to store the mappings for abbreviate figured bass numbers +// Tool_extract::printInterpretationForKernSpine -- // -FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector n) { - m_str = s; - m_numbers = n; +void Tool_extract::printInterpretationForKernSpine(HumdrumFile& infile, int index) { + HTp kerntok = NULL; + for (int j=0; jisKern()) { + continue; + } + kerntok = token; + break; + } + + if (kerntok == NULL) { + m_humdrum_text << "*"; + return; + } + + if (*kerntok == "*") { + m_humdrum_text << kerntok; + return; + } + + if (kerntok->isKeySignature()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isKeyDesignation()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isTimeSignature()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isMensurationSymbol()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isTempo()) { + m_humdrum_text << kerntok; + return; + } + if (kerntok->isInstrumentName()) { + m_humdrum_text << "*I\""; + return; + } + if (kerntok->isInstrumentAbbreviation()) { + m_humdrum_text << "*I'"; + return; + } + + m_humdrum_text << "*"; } ////////////////////////////// // -// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers +// Tool_extract::dealWithCospine -- extract the required token(s) from a co-spine. // -const vector FiguredBassAbbreviationMapping::s_mappings = { - FiguredBassAbbreviationMapping("3", {}), - FiguredBassAbbreviationMapping("5", {}), - FiguredBassAbbreviationMapping("5 3", {}), - FiguredBassAbbreviationMapping("6 3", {6}), - FiguredBassAbbreviationMapping("5 4", {4}), - FiguredBassAbbreviationMapping("7 5 3", {7}), - FiguredBassAbbreviationMapping("7 3", {7}), - FiguredBassAbbreviationMapping("7 5", {7}), - FiguredBassAbbreviationMapping("6 5 3", {6, 5}), - FiguredBassAbbreviationMapping("6 4 3", {4, 3}), - FiguredBassAbbreviationMapping("6 4 2", {4, 2}), - FiguredBassAbbreviationMapping("9 5 3", {9}), - FiguredBassAbbreviationMapping("9 5", {9}), - FiguredBassAbbreviationMapping("9 3", {9}), -}; +void Tool_extract::dealWithCospine(vector& field, vector& subfield, vector& model, + int targetindex, HumdrumFile& infile, int line, int cospine, + int comodel, int submodel, const string& cointerp) { + vector cotokens; + cotokens.reserve(50); + string buffer; + int i, j, k; + int index; -#define RUNTOOL(NAME, INFILE, COMMAND, STATUS) \ - Tool_##NAME *tool = new Tool_##NAME; \ - tool->process(COMMAND); \ - tool->run(INFILE); \ - if (tool->hasError()) { \ - status = false; \ - tool->getError(cerr); \ - delete tool; \ - break; \ - } else if (tool->hasHumdrumText()) { \ - INFILE.readString(tool->getHumdrumText()); \ - } \ - delete tool; + if (infile[line].isInterpretation()) { + m_humdrum_text << infile.token(line, cospine); + return; + } -#define RUNTOOL2(NAME, INFILE1, INFILE2, COMMAND, STATUS) \ - Tool_##NAME *tool = new Tool_##NAME; \ - tool->process(COMMAND); \ - tool->run(INFILE1, INFILE2); \ - if (tool->hasError()) { \ - status = false; \ - tool->getError(cerr); \ - delete tool; \ - break; \ - } else if (tool->hasHumdrumText()) { \ - INFILE1.readString(tool->getHumdrumText()); \ - } \ - delete tool; + if (infile[line].isBarline()) { + m_humdrum_text << infile.token(line, cospine); + return; + } -#define RUNTOOLSET(NAME, INFILES, COMMAND, STATUS) \ - Tool_##NAME *tool = new Tool_##NAME; \ - tool->process(COMMAND); \ - tool->run(INFILES); \ - if (tool->hasError()) { \ - status = false; \ - tool->getError(cerr); \ - delete tool; \ - break; \ - } else if (tool->hasHumdrumText()) { \ - INFILES.readString(tool->getHumdrumText()); \ - } \ - delete tool; + if (infile[line].isLocalComment()) { + m_humdrum_text << infile.token(line, cospine); + return; + } -#define RUNTOOLSTREAM(NAME, INFILES, COMMAND, STATUS) \ - Tool_##NAME *tool = new Tool_##NAME; \ - tool->process(COMMAND); \ - tool->run(INFILES); \ - if (tool->hasError()) { \ - status = false; \ - tool->getError(cerr); \ - delete tool; \ - break; \ - } else if (tool->hasHumdrumText()) { \ - INFILES.readString(tool->getHumdrumText()); \ - } \ - delete tool; + int count = infile[line].token(cospine)->getSubtokenCount(); + for (k=0; kgetSubtoken(k); + cotokens.resize(cotokens.size()+1); + index = (int)cotokens.size()-1; + cotokens[index] = buffer; + } + vector spineindex; + vector subspineindex; + spineindex.reserve(infile.getMaxTrack()*2); + spineindex.resize(0); -//////////////////////////////// -// -// Tool_filter::Tool_filter -- Set the recognized options for the tool. -// + subspineindex.reserve(infile.getMaxTrack()*2); + subspineindex.resize(0); -Tool_filter::Tool_filter(void) { - define("debug=b", "print debug statement"); - define("v|variant=s:", "Run filters labeled with the given variant"); -} + for (j=0; jisDataType(cointerp)) { + continue; + } + if (*infile.token(line, j) == ".") { + continue; + } + count = infile[line].token(j)->getSubtokenCount(); + for (k=0; kgetSubtoken(k); + if (comodel == 'r') { + if (buffer == "r") { + continue; + } + } + spineindex.push_back(j); + subspineindex.push_back(k); + } + } + if (debugQ) { + m_humdrum_text << "\n!!codata:\n"; + for (i=0; i<(int)cotokens.size(); i++) { + m_humdrum_text << "!!\t" << i << "\t" << cotokens[i]; + if (i < (int)spineindex.size()) { + m_humdrum_text << "\tspine=" << spineindex[i]; + m_humdrum_text << "\tsubspine=" << subspineindex[i]; + } else { + m_humdrum_text << "\tspine=."; + m_humdrum_text << "\tsubspine=."; + } + m_humdrum_text << endl; + } + } + string buff; -///////////////////////////////// -// -// Tool_filter::run -- Primary interfaces to the tool. -// + int start = 0; + for (i=0; i<(int)field.size(); i++) { + if (infile.token(line, field[i])->isDataType(cointerp)) { + continue; + } -bool Tool_filter::run(const string& indata) { - HumdrumFileSet infiles(indata); - bool status = run(infiles); - return status; + for (j=0; jgetTrack() != field[i]) { + continue; + } + if (subfield[i] == 'a') { + getSearchPat(buff, field[i], "a"); + if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) || + (infile.token(line, j)->getSpineInfo().find(buff) != string::npos)) { + printCotokenInfo(start, infile, line, j, cotokens, spineindex, + subspineindex); + } + } else if (subfield[i] == 'b') { + // this section may need more work... + getSearchPat(buff, field[i], "b"); + if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) || + (strstr(infile.token(line, j)->getSpineInfo().c_str(), buff.c_str()) != NULL)) { + printCotokenInfo(start, infile, line, j, cotokens, spineindex, + subspineindex); + } + } else { + printCotokenInfo(start, infile, line, j, cotokens, spineindex, + subspineindex); + } + } + } } -bool Tool_filter::run(HumdrumFile& infile) { - HumdrumFileSet infiles; - infiles.appendHumdrumPointer(&infile); - bool status = run(infiles); - infiles.clearNoFree(); - return status; -} -bool Tool_filter::runUniversal(HumdrumFileSet& infiles) { - bool status = true; - vector > commands; - getUniversalCommandList(commands, infiles); +////////////////////////////// +// +// Tool_extract::printCotokenInfo -- +// - for (int i=0; i<(int)commands.size(); i++) { - if (commands[i].first == "humdiff") { - RUNTOOLSET(humdiff, infiles, commands[i].second, status); - } else if (commands[i].first == "chooser") { - RUNTOOLSET(chooser, infiles, commands[i].second, status); - } else if (commands[i].first == "myank") { - RUNTOOL(myank, infiles, commands[i].second, status); +void Tool_extract::printCotokenInfo(int& start, HumdrumFile& infile, int line, int spine, + vector& cotokens, vector& spineindex, + vector& subspineindex) { + int i; + int found = 0; + for (i=0; i<(int)spineindex.size(); i++) { + if (spineindex[i] == spine) { + if (start == 0) { + start++; + } else { + m_humdrum_text << subtokenseparator; + } + if (i<(int)cotokens.size()) { + m_humdrum_text << cotokens[i]; + } else { + m_humdrum_text << "."; + } + found = 1; } } - - removeUniversalFilterLines(infiles); - - return status; + if (!found) { + if (start == 0) { + start++; + } else { + m_humdrum_text << subtokenseparator; + } + m_humdrum_text << "."; + } } + +////////////////////////////// // -// In-place processing of file: +// Tool_extract::dealWithSecondarySubspine -- what to print if a secondary spine +// does not exist on a line. // -bool Tool_filter::run(HumdrumFileSet& infiles) { - if (infiles.getCount() == 0) { - return false; - } - - initialize(infiles[0]); - - HumdrumFile& infile = infiles[0]; +void Tool_extract::dealWithSecondarySubspine(vector& field, vector& subfield, + vector& model, int targetindex, HumdrumFile& infile, int line, + int spine, int submodel) { - #ifdef __EMSCRIPTEN__ - bool optionList = getBoolean("options"); - if (optionList) { - printEmscripten(m_humdrum_text); - m_humdrum_text << infile; - } - #endif + int& i = line; + int& j = spine; - bool status = true; - vector > commands; - getCommandList(commands, infile); - for (int i=0; i<(int)commands.size(); i++) { - if (commands[i].first == "addic") { - RUNTOOL(addic, infile, commands[i].second, status); - } else if (commands[i].first == "addkey") { - RUNTOOL(addkey, infile, commands[i].second, status); - } else if (commands[i].first == "addlabels") { - RUNTOOL(addlabels, infile, commands[i].second, status); - } else if (commands[i].first == "addtempo") { - RUNTOOL(addtempo, infile, commands[i].second, status); - } else if (commands[i].first == "autoaccid") { - RUNTOOL(autoaccid, infile, commands[i].second, status); - } else if (commands[i].first == "autobeam") { - RUNTOOL(autobeam, infile, commands[i].second, status); - } else if (commands[i].first == "autostem") { - RUNTOOL(autostem, infile, commands[i].second, status); - } else if (commands[i].first == "binroll") { - RUNTOOL(binroll, infile, commands[i].second, status); - } else if (commands[i].first == "chantize") { - RUNTOOL(chantize, infile, commands[i].second, status); - } else if (commands[i].first == "chint") { - RUNTOOL(chint, infile, commands[i].second, status); - } else if (commands[i].first == "chord") { - RUNTOOL(chord, infile, commands[i].second, status); - } else if (commands[i].first == "cint") { - RUNTOOL(cint, infile, commands[i].second, status); - } else if (commands[i].first == "cmr") { - RUNTOOL(cmr, infile, commands[i].second, status); - } else if (commands[i].first == "composite") { - RUNTOOL(composite, infile, commands[i].second, status); - } else if (commands[i].first == "dissonant") { - RUNTOOL(dissonant, infile, commands[i].second, status); - } else if (commands[i].first == "double") { - RUNTOOL(double, infile, commands[i].second, status); - } else if (commands[i].first == "fb") { - RUNTOOL(fb, infile, commands[i].second, status); - } else if (commands[i].first == "flipper") { - RUNTOOL(flipper, infile, commands[i].second, status); - } else if (commands[i].first == "filter") { - RUNTOOL(filter, infile, commands[i].second, status); - } else if (commands[i].first == "gasparize") { - RUNTOOL(gasparize, infile, commands[i].second, status); - } else if (commands[i].first == "half") { - RUNTOOL(half, infile, commands[i].second, status); - } else if (commands[i].first == "hands") { - RUNTOOL(hands, infile, commands[i].second, status); - } else if (commands[i].first == "homorhythm") { - RUNTOOL(homorhythm, infile, commands[i].second, status); - } else if (commands[i].first == "homorhythm2") { - RUNTOOL(homorhythm2, infile, commands[i].second, status); - } else if (commands[i].first == "hproof") { - RUNTOOL(hproof, infile, commands[i].second, status); - } else if (commands[i].first == "humbreak") { - RUNTOOL(humbreak, infile, commands[i].second, status); - } else if (commands[i].first == "humsheet") { - RUNTOOL(humsheet, infile, commands[i].second, status); - } else if (commands[i].first == "humtr") { - RUNTOOL(humtr, infile, commands[i].second, status); - } else if (commands[i].first == "imitation") { - RUNTOOL(imitation, infile, commands[i].second, status); - } else if (commands[i].first == "instinfo") { - RUNTOOL(instinfo, infile, commands[i].second, status); - } else if (commands[i].first == "kern2mens") { - RUNTOOL(kern2mens, infile, commands[i].second, status); - } else if (commands[i].first == "kernify") { - RUNTOOL(kernify, infile, commands[i].second, status); - } else if (commands[i].first == "kernview") { - RUNTOOL(kernview, infile, commands[i].second, status); - } else if (commands[i].first == "melisma") { - RUNTOOL(melisma, infile, commands[i].second, status); - } else if (commands[i].first == "mens2kern") { - RUNTOOL(mens2kern, infile, commands[i].second, status); - } else if (commands[i].first == "meter") { - RUNTOOL(meter, infile, commands[i].second, status); - } else if (commands[i].first == "metlev") { - RUNTOOL(metlev, infile, commands[i].second, status); - } else if (commands[i].first == "modori") { - RUNTOOL(modori, infile, commands[i].second, status); - } else if (commands[i].first == "msearch") { - RUNTOOL(msearch, infile, commands[i].second, status); - } else if (commands[i].first == "nproof") { - RUNTOOL(nproof, infile, commands[i].second, status); - } else if (commands[i].first == "ordergps") { - RUNTOOL(ordergps, infile, commands[i].second, status); - } else if (commands[i].first == "pbar") { - RUNTOOL(pbar, infile, commands[i].second, status); - } else if (commands[i].first == "phrase") { - RUNTOOL(phrase, infile, commands[i].second, status); - } else if (commands[i].first == "pline") { - RUNTOOL(pline, infile, commands[i].second, status); - } else if (commands[i].first == "prange") { - RUNTOOL(prange, infile, commands[i].second, status); - } else if (commands[i].first == "recip") { - RUNTOOL(recip, infile, commands[i].second, status); - } else if (commands[i].first == "restfill") { - RUNTOOL(restfill, infile, commands[i].second, status); - } else if (commands[i].first == "rphrase") { - RUNTOOL(rphrase, infile, commands[i].second, status); - } else if (commands[i].first == "sab2gs") { - RUNTOOL(sab2gs, infile, commands[i].second, status); - } else if (commands[i].first == "scordatura") { - RUNTOOL(scordatura, infile, commands[i].second, status); - } else if (commands[i].first == "semitones") { - RUNTOOL(semitones, infile, commands[i].second, status); - } else if (commands[i].first == "shed") { - RUNTOOL(shed, infile, commands[i].second, status); - } else if (commands[i].first == "sic") { - RUNTOOL(sic, infile, commands[i].second, status); - } else if (commands[i].first == "simat") { - RUNTOOL2(simat, infile, infile, commands[i].second, status); - } else if (commands[i].first == "slurcheck") { - RUNTOOL(slurcheck, infile, commands[i].second, status); - } else if (commands[i].first == "slur") { - RUNTOOL(slurcheck, infile, commands[i].second, status); - } else if (commands[i].first == "spinetrace") { - RUNTOOL(spinetrace, infile, commands[i].second, status); - } else if (commands[i].first == "strophe") { - RUNTOOL(strophe, infile, commands[i].second, status); - } else if (commands[i].first == "synco") { - RUNTOOL(synco, infile, commands[i].second, status); - } else if (commands[i].first == "tabber") { - RUNTOOL(tabber, infile, commands[i].second, status); - } else if (commands[i].first == "tassoize") { - RUNTOOL(tassoize, infile, commands[i].second, status); - } else if (commands[i].first == "tassoise") { - RUNTOOL(tassoize, infile, commands[i].second, status); - } else if (commands[i].first == "tasso") { - RUNTOOL(tassoize, infile, commands[i].second, status); - } else if (commands[i].first == "textdur") { - RUNTOOL(textdur, infile, commands[i].second, status); - } else if (commands[i].first == "tie") { - RUNTOOL(tie, infile, commands[i].second, status); - } else if (commands[i].first == "tspos") { - RUNTOOL(tspos, infile, commands[i].second, status); - } else if (commands[i].first == "transpose") { - RUNTOOL(transpose, infile, commands[i].second, status); - } else if (commands[i].first == "tremolo") { - RUNTOOL(tremolo, infile, commands[i].second, status); - } else if (commands[i].first == "trillspell") { - RUNTOOL(trillspell, infile, commands[i].second, status); - } else if (commands[i].first == "vcross") { - RUNTOOL(vcross, infile, commands[i].second, status); - - // filters with aliases: - - } else if (commands[i].first == "colortriads") { - RUNTOOL(colortriads, infile, commands[i].second, status); - } else if (commands[i].first == "colourtriads") { - // British spelling - RUNTOOL(colortriads, infile, commands[i].second, status); - - } else if (commands[i].first == "colorthirds") { - RUNTOOL(tspos, infile, commands[i].second, status); - } else if (commands[i].first == "colourthirds") { - // British spelling - RUNTOOL(tspos, infile, commands[i].second, status); - - } else if (commands[i].first == "colorgroups") { - RUNTOOL(colorgroups, infile, commands[i].second, status); - } else if (commands[i].first == "colourgroups") { // British spelling - RUNTOOL(colorgroups, infile, commands[i].second, status); - - } else if (commands[i].first == "deg") { // humlib version of Humdrum Toolkit deg tool - RUNTOOL(deg, infile, commands[i].second, status); - } else if (commands[i].first == "degx") { // humlib cli name - RUNTOOL(deg, infile, commands[i].second, status); - - } else if (commands[i].first == "extract") { // humlib version of Humdrum Toolkit extract tool - RUNTOOL(extract, infile, commands[i].second, status); - } else if (commands[i].first == "extractx") { // humlib cli name - RUNTOOL(extract, infile, commands[i].second, status); - - } else if (commands[i].first == "grep") { - RUNTOOL(grep, infile, commands[i].second, status); - } else if (commands[i].first == "humgrep") { - RUNTOOL(grep, infile, commands[i].second, status); - - } else if (commands[i].first == "myank") { // humlib version of Humdrum Extras myank tool - RUNTOOL(myank, infile, commands[i].second, status); - } else if (commands[i].first == "myankx") { // humlib cli name - RUNTOOL(myank, infile, commands[i].second, status); - - } else if (commands[i].first == "rid") { // humlib version of Humdrum Toolkit deg tool - RUNTOOL(rid, infile, commands[i].second, status); - } else if (commands[i].first == "ridx") { // Humdrum Extra cli name - RUNTOOL(rid, infile, commands[i].second, status); - } else if (commands[i].first == "ridxx") { // humlib cli name - RUNTOOL(rid, infile, commands[i].second, status); - - } else if (commands[i].first == "satb2gs") { // humlib version of Humdrum Extras satg2gs tool - RUNTOOL(satb2gs, infile, commands[i].second, status); - } else if (commands[i].first == "satb2gsx") { // humlib cli name - RUNTOOL(satb2gs, infile, commands[i].second, status); - - } else if (commands[i].first == "thru") { // humlib version of Humdrum Toolkit thru tool - RUNTOOL(thru, infile, commands[i].second, status); - } else if (commands[i].first == "thrux") { // Humdrum Extras cli name - RUNTOOL(thru, infile, commands[i].second, status); - } else if (commands[i].first == "thruxx") { // humlib cli name - RUNTOOL(thru, infile, commands[i].second, status); - - } else if (commands[i].first == "timebase") { // humlib version of Humdrum Toolkit timebase tool - RUNTOOL(timebase, infile, commands[i].second, status); - } else if (commands[i].first == "timebasex") { // humlib cli name - RUNTOOL(timebase, infile, commands[i].second, status); - } else { - cerr << "UNKNOWN FILTER: " << commands[i].first << " OPTIONS: " << commands[i].second << endl; - } - - } - - removeGlobalFilterLines(infile); - - // Re-load the text for each line from their tokens in case any - // updates are needed from token changes. - infile.createLinesFromTokens(); - return status; -} - - - -////////////////////////////// -// -// Tool_filter::removeGlobalFilterLines -- -// - -void Tool_filter::removeGlobalFilterLines(HumdrumFile& infile) { HumRegex hre; - string text; - - string maintag = "!!!filter:"; - string mainXtag = "!!!Xfilter:"; - string maintagQuery = "^!!!filter:"; - - string maintagV; - string mainXtagV; - string maintagQueryV; - - if (m_variant.size() > 0) { - maintagV = "!!!filter-" + m_variant + ":"; - mainXtagV = "!!!Xfilter-" + m_variant + ":"; - maintagQueryV = "^!!!filter-" + m_variant + ":"; - } - - for (int i=0; i 0) { - if (infile.token(i, 0)->compare(0, maintagV.size(), maintagV) == 0) { - text = infile.token(i, 0)->getText(); - hre.replaceDestructive(text, mainXtagV, maintagQueryV); - infile.token(i, 0)->setText(text); - } + } else if (infile[line].isBarline()) { + m_humdrum_text << infile.token(i, j); + } else if (infile[line].isInterpretation()) { + if ((submodel == 'n') || (submodel == 'r')) { + m_humdrum_text << "*"; } else { - if (infile.token(i, 0)->compare(0, maintag.size(), maintag) == 0) { - text = infile.token(i, 0)->getText(); - hre.replaceDestructive(text, mainXtag, maintagQuery); - infile.token(i, 0)->setText(text); - } + m_humdrum_text << infile.token(i, j); } - } -} - - - -////////////////////////////// -// -// Tool_filter::removeUniversalFilterLines -- -// - -void Tool_filter::removeUniversalFilterLines(HumdrumFileSet& infiles) { - HumRegex hre; - string text; - - string maintag = "!!!!filter:"; - string mainXtag = "!!!!Xfilter:"; - string maintagQuery = "^!!!!filter:"; - - string maintagV; - string mainXtagV; - string maintagQueryV; - - if (m_variant.size() > 0) { - maintagV = "!!!!filter-" + m_variant + ":"; - mainXtagV = "!!!!Xfilter-" + m_variant + ":"; - maintagQueryV = "^!!!!filter-" + m_variant + ":"; - } - - for (int i=0; i 0) { - if (token->compare(0, maintagV.size(), maintagV) == 0) { - text = token->getText(); - hre.replaceDestructive(text, mainXtagV, maintagQueryV); - token->setText(text); - infile[j].createLineFromTokens(); - } + } else if (infile[line].isData()) { + if (submodel == 'n') { + m_humdrum_text << "."; + } else if (submodel == 'r') { + if (*infile.token(i, j) == ".") { + m_humdrum_text << "."; + } else if (infile.token(i, j)->find('q') != string::npos) { + m_humdrum_text << "."; + } else if (infile.token(i, j)->find('Q') != string::npos) { + m_humdrum_text << "."; } else { - if (token->compare(0, maintag.size(), maintag) == 0) { - text = token->getText(); - hre.replaceDestructive(text, mainXtag, maintagQuery); - token->setText(text); - infile[j].createLineFromTokens(); + buffer = *infile.token(i, j); + if (hre.search(buffer, "{")) { + m_humdrum_text << "{"; + } + // remove secondary chord notes: + hre.replaceDestructive(buffer, "", " .*"); + // remove unnecessary characters (such as stem direction): + hre.replaceDestructive(buffer, "", + "[^}pPqQA-Ga-g0-9.;%#nr-]", "g"); + // change pitch to rest: + hre.replaceDestructive(buffer, "[A-Ga-g#n-]+", "r"); + // add editorial marking unless -Y option is given: + if (editorialInterpretation != "") { + if (hre.search(buffer, "rr")) { + hre.replaceDestructive(buffer, editorialInterpretation, "(?<=rr)"); + hre.replaceDestructive(buffer, "r", "rr"); + } else { + hre.replaceDestructive(buffer, editorialInterpretation, "(?<=r)"); + } } + m_humdrum_text << buffer; } + } else { + m_humdrum_text << infile.token(i, j); } + } else { + m_error_text << "Should not get to this line of code" << endl; + return; } } + ////////////////////////////// // -// Tool_filter::getCommandList -- +// Tool_extract::getSearchPat -- // -void Tool_filter::getCommandList(vector >& commands, - HumdrumFile& infile) { - - vector refs = infile.getReferenceRecords(); - pair entry; - string tag = "filter"; - if (m_variant.size() > 0) { - tag += "-"; - tag += m_variant; - } - vector clist; - HumRegex hre; - for (int i=0; i<(int)refs.size(); i++) { - string refkey = refs[i]->getGlobalReferenceKey(); - if (refkey != tag) { - continue; - } - string command = refs[i]->getGlobalReferenceValue(); - splitPipeline(clist, command); - for (int j=0; j<(int)clist.size(); j++) { - if (hre.search(clist[j], "^\\s*([^\\s]+)")) { - entry.first = hre.getMatch(1); - entry.second = clist[j]; - commands.push_back(entry); - } - } +void Tool_extract::getSearchPat(string& spat, int target, const string& modifier) { + if (modifier.size() > 20) { + m_error_text << "Error in GetSearchPat" << endl; + return; } + spat.reserve(16); + spat = "\\("; + spat += to_string(target); + spat += "\\)"; + spat += modifier; } ////////////////////////////// // -// Tool_filter::splitPipeline -- +// Tool_extract::dealWithSpineManipulators -- check for proper Humdrum syntax of +// spine manipulators (**, *-, *x, *v, *^) when creating the output. // -void Tool_filter::splitPipeline(vector& clist, const string& command) { - clist.clear(); - clist.resize(1); - clist[0] = ""; - int inDoubleQuotes = -1; - int inSingleQuotes = -1; - char ch = '\0'; - char lastch; - for (int i=0; i<(int)command.size(); i++) { - lastch = ch; - ch = command[i]; +void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, + vector& field, vector& subfield, vector& model) { - if (ch == '"') { - if (lastch == '\\') { - // escaped double quote, so treat as regular character - clist.back() += ch; - continue; - } else if (inDoubleQuotes >= 0) { - // turn off previous double quote sequence - clist.back() += ch; - inDoubleQuotes = -1; - continue; - } else if (inSingleQuotes >= 0) { - // in an active single quote, so this is not a closing double quote - clist.back() += ch; - continue; - } else { - // this is the start of a double quote sequence - clist.back() += ch; - inDoubleQuotes = i; - continue; - } - } + vector vmanip; // counter for *v records on line + vmanip.resize(infile[line].getFieldCount()); + fill(vmanip.begin(), vmanip.end(), 0); - if (ch == '\'') { - if (lastch == '\\') { - // escaped single quote, so treat as regular character - clist.back() += ch; - continue; - } else if (inSingleQuotes >= 0) { - // turn off previous single quote sequence - clist.back() += ch; - inSingleQuotes = -1; - continue; - } else if (inDoubleQuotes >= 0) { - // in an active double quote, so this is not a closing single quote - clist.back() += ch; - continue; - } else { - // this is the start of a single quote sequence - clist.back() += ch; - inSingleQuotes = i; - continue; - } + vector xmanip; // counter for *x record on line + xmanip.resize(infile[line].getFieldCount()); + fill(xmanip.begin(), xmanip.end(), 0); + + int i = 0; + int j; + for (j=0; j<(int)vmanip.size(); j++) { + if (*infile.token(line, j) == "*v") { + vmanip[j] = 1; } + if (*infile.token(line, j) == "*x") { + xmanip[j] = 1; + } + } - if (ch == '|') { - if ((inSingleQuotes > -1) || (inDoubleQuotes > -1)) { - // pipe character - clist.back() += ch; - continue; - } else { - // this is a real pipe - clist.resize(clist.size() + 1); - continue; - } + int counter = 1; + for (i=1; i<(int)xmanip.size(); i++) { + if ((xmanip[i] == 1) && (xmanip[i-1] == 1)) { + xmanip[i] = counter; + xmanip[i-1] = counter; + counter++; } + } - if (isspace(ch) && (!(inSingleQuotes > -1)) && (!(inDoubleQuotes > -1))) { - if (isspace(lastch)) { - // don't repeat spaces outside of quotes. - continue; + counter = 1; + i = 0; + while (i < (int)vmanip.size()) { + if (vmanip[i] == 1) { + while ((i < (int)vmanip.size()) && (vmanip[i] == 1)) { + vmanip[i] = counter; + i++; } + counter++; } - - // regular character - clist.back() += ch; + i++; } - // remove leading and trailing spaces - HumRegex hre; - for (int i=0; i<(int)clist.size(); i++) { - hre.replaceDestructive(clist[i], "", "^\\s+"); - hre.replaceDestructive(clist[i], "", "\\s+$"); + vector fieldoccur; // nth occurance of an input spine in the output + fieldoccur.resize(field.size()); + fill(fieldoccur.begin(), fieldoccur.end(), 0); + + vector trackcounter; // counter of input spines occurances in output + trackcounter.resize(infile.getMaxTrack()+1); + fill(trackcounter.begin(), trackcounter.end(), 0); + + for (i=0; i<(int)field.size(); i++) { + if (field[i] != 0) { + trackcounter[field[i]]++; + fieldoccur[i] = trackcounter[field[i]]; + } } -} + vector tempout; + vector vserial; + vector xserial; + vector fpos; // input column of output spine + tempout.reserve(1000); + tempout.resize(0); + vserial.reserve(1000); + vserial.resize(0); -////////////////////////////// -// -// Tool_filter::getUniversalCommandList -- -// + xserial.reserve(1000); + xserial.resize(0); -void Tool_filter::getUniversalCommandList(vector >& commands, - HumdrumFileSet& infiles) { + fpos.reserve(1000); + fpos.resize(0); - vector refs = infiles.getUniversalReferenceRecords(); - pair entry; - string tag = "filter"; - if (m_variant.size() > 0) { - tag += "-"; - tag += m_variant; - } - vector clist; + string spat; + string spinepat; HumRegex hre; - for (int i=0; i<(int)refs.size(); i++) { - if (refs[i]->getUniversalReferenceKey() != tag) { - continue; - } - string command = refs[i]->getUniversalReferenceValue(); - hre.split(clist, command, "\\s*\\|\\s*"); - for (int j=0; j<(int)clist.size(); j++) { - if (hre.search(clist[j], "^\\s*([^\\s]+)")) { - entry.first = hre.getMatch(1); - entry.second = clist[j]; - commands.push_back(entry); + int subtarget; + int modeltarget; + int xdebug = 0; + int vdebug = 0; + int suppress = 0; + int target; + int tval; + for (int t=0; t<(int)field.size(); t++) { + target = field[t]; + subtarget = subfield[t]; + modeltarget = model[t]; + if (modeltarget == 0) { + switch (subtarget) { + case 'a': + case 'b': + modeltarget = submodel; + break; + case 'c': + modeltarget = comodel; + } + } + suppress = 0; + if (target == 0) { + if (infile.token(line, 0)->compare(0, 2, "**") == 0) { + storeToken(tempout, blankName); + tval = 0; + vserial.push_back(tval); + xserial.push_back(tval); + fpos.push_back(tval); + } else if (*infile.token(line, 0) == "*-") { + storeToken(tempout, "*-"); + tval = 0; + vserial.push_back(tval); + xserial.push_back(tval); + fpos.push_back(tval); + } else { + storeToken(tempout, "*"); + tval = 0; + vserial.push_back(tval); + xserial.push_back(tval); + fpos.push_back(tval); + } + } else { + for (j=0; jgetTrack() != target) { + continue; + } + // filter by subfield + if (subtarget == 'a') { + getSearchPat(spat, target, "b"); + if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { + continue; + } + } else if (subtarget == 'b') { + getSearchPat(spat, target, "a"); + if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) { + continue; } } - } -} + switch (subtarget) { + case 'a': + if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) { + if (*infile.token(line, j) == "*^") { + storeToken(tempout, "*"); + } else { + storeToken(tempout, *infile.token(line, j)); + } + } else { + getSearchPat(spat, target, "a"); + spinepat = infile.token(line, j)->getSpineInfo(); + hre.replaceDestructive(spinepat, "\\(", "\\(", "g"); + hre.replaceDestructive(spinepat, "\\)", "\\)", "g"); -////////////////////////////// -// -// Tool_filter::initialize -- extract time signature lines for -// each **kern spine in file. -// + if ((*infile.token(line, j) == "*v") && + (spinepat == spat)) { + storeToken(tempout, "*"); + } else { + getSearchPat(spat, target, "b"); + if ((spinepat == spat) && + (*infile.token(line, j) == "*v")) { + // do nothing + suppress = 1; + } else { + storeToken(tempout, *infile.token(line, j)); + } + } + } -void Tool_filter::initialize(HumdrumFile& infile) { - m_debugQ = getBoolean("debug"); - m_variant.clear(); - if (getBoolean("variant")) { - m_variant = getString("variant"); - } -} + break; + case 'b': + if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) { + if (*infile.token(line, j) == "*^") { + storeToken(tempout, "*"); + } else { + storeToken(tempout, *infile.token(line, j)); + } + } else { + getSearchPat(spat, target, "b"); + spinepat = infile.token(line, j)->getSpineInfo(); + hre.replaceDestructive(spinepat, "\\(", "\\(", "g"); + hre.replaceDestructive(spinepat, "\\)", "\\)", "g"); + if ((*infile.token(line, j) == "*v") && + (spinepat == spat)) { + storeToken(tempout, "*"); + } else { + getSearchPat(spat, target, "a"); + if ((spinepat == spat) && + (*infile.token(line, j) == "*v")) { + // do nothing + suppress = 1; + } else { + storeToken(tempout, *infile.token(line, j)); + } + } + } + break; + case 'c': + // work on later + storeToken(tempout, *infile.token(line, j)); + break; + default: + storeToken(tempout, *infile.token(line, j)); + } + if (suppress) { + continue; + } -///////////////////////////////// -// -// Tool_fixps::Tool_fixps -- Set the recognized options for the tool. -// + if (tempout[(int)tempout.size()-1] == "*x") { + tval = fieldoccur[t] * 1000 + xmanip[j]; + xserial.push_back(tval); + xdebug = 1; + } else { + tval = 0; + xserial.push_back(tval); + } -Tool_fixps::Tool_fixps(void) { - // define ("n|only-remove-empty-transpositions=b", "Only remove empty transpositions"); -} + if (tempout[(int)tempout.size()-1] == "*v") { + tval = fieldoccur[t] * 1000 + vmanip[j]; + vserial.push_back(tval); + vdebug = 1; + } else { + tval = 0; + vserial.push_back(tval); + } + fpos.push_back(j); + } + } + } -///////////////////////////////// -// -// Tool_fixps::run -- Primary interfaces to the tool. -// + if (debugQ && xdebug) { + m_humdrum_text << "!! *x serials = "; + for (int ii=0; ii<(int)xserial.size(); ii++) { + m_humdrum_text << xserial[ii] << " "; + } + m_humdrum_text << "\n"; + } -bool Tool_fixps::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i 1) { + // check the last item in the list + int index = (int)xserial.size()-1; + if (tempout[index] == "*x") { + if (xserial[index] != xserial[index-1]) { + xserial[index] = 0; + tempout[index] = "*"; + } + } } - return status; -} + // check for proper *v syntax ///////////////////////////////// + vector vsplit; + vsplit.resize((int)vserial.size()); + fill(vsplit.begin(), vsplit.end(), 0); -bool Tool_fixps::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + // identify necessary line splits + for (i=0; i<(int)vserial.size()-1; i++) { + if (!vserial[i]) { + continue; + } + while ((i<(int)vserial.size()-1) && (vserial[i]==vserial[i+1])) { + i++; + } + if ((i<(int)vserial.size()-1) && vserial[i]) { + if (vserial.size() > 1) { + if (vserial[i+1]) { + vsplit[i+1] = 1; + } + } + } } - return status; -} -// -// In-place processing of file: -// + // remove single *v spines: -bool Tool_fixps::run(HumdrumFile& infile) { - processFile(infile); - return true; -} + for (i=0; i<(int)vsplit.size()-1; i++) { + if (vsplit[i] && vsplit[i+1]) { + if (tempout[i] == "*v") { + tempout[i] = "*"; + vsplit[i] = 0; + } + } + } + + if (debugQ) { + m_humdrum_text << "!!vsplit array: "; + for (i=0; i<(int)vsplit.size(); i++) { + m_humdrum_text << " " << vsplit[i]; + } + m_humdrum_text << endl; + } + if (vsplit.size() > 0) { + if (vsplit[(int)vsplit.size()-1]) { + if (tempout[(int)tempout.size()-1] == "*v") { + tempout[(int)tempout.size()-1] = "*"; + vsplit[(int)vsplit.size()-1] = 0; + } + } + } + int vcount = 0; + for (i=0; i<(int)vsplit.size(); i++) { + vcount += vsplit[i]; + } -////////////////////////////// -// -// Tool_fixps::processFile -- -// + if (vcount) { + printMultiLines(vsplit, vserial, tempout); + } -void Tool_fixps::processFile(HumdrumFile& infile) { - removeDuplicateDynamics(infile); - markEmptyVoices(infile); - vector> newlist; - removeEmpties(newlist, infile); - outputNewSpining(newlist, infile); + int start = 0; + for (i=0; i<(int)tempout.size(); i++) { + if (tempout[i] != "") { + if (start != 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << tempout[i]; + start++; + } + } + if (start) { + m_humdrum_text << '\n'; + } } ////////////////////////////// // -// Tool_fixps::outputNewSpining -- +// Tool_extract::printMultiLines -- print separate *v lines. // -void Tool_fixps::outputNewSpining(vector>& newlist, HumdrumFile& infile) { - for (int i=0; i& vsplit, vector& vserial, + vector& tempout) { + int i; + + int splitpoint = -1; + for (i=0; i<(int)vsplit.size(); i++) { + if (vsplit[i]) { + splitpoint = i; + break; } - if ((i > 0) && (!newlist[i].empty()) && newlist[i][0]->isCommentLocal()) { - if (!newlist[i-1].empty() && newlist[i-1][0]->isCommentLocal()) { - if (newlist[i].size() == newlist[i-1].size()) { - bool same = true; - for (int j=0; j<(int)newlist[i].size(); j++) { - if (*(newlist[i][j]) != *(newlist[i-1][j])) { -cerr << "GOT HERE " << i << " " << j << endl; -cerr << infile[i-1] << endl; -cerr << infile[i] << endl; -cerr << endl; - same = false; - break; - } - } - if (same) { - continue; - } + } + + if (debugQ) { + m_humdrum_text << "!!tempout: "; + for (i=0; i<(int)tempout.size(); i++) { + m_humdrum_text << tempout[i] << " "; + } + m_humdrum_text << endl; + } + + if (splitpoint == -1) { + return; + } + + int start = 0; + int printv = 0; + for (i=0; i 0) && !infile[i-1].isManipulator()) { - printNewManipulator(infile, newlist, i); + m_humdrum_text << "*"; } } -} + if (start) { + m_humdrum_text << "\n"; + } -////////////////////////////// -// -// Tool_fixps::printNewManipulator -- -// + vsplit[splitpoint] = 0; -void Tool_fixps::printNewManipulator(HumdrumFile& infile, vector>& newlist, int line) { - HTp token = infile.token(line, 0); - if (*token == "*-") { - m_humdrum_text << infile[line] << endl; - return; - } - if (token->compare(0, 2, "**") == 0) { - m_humdrum_text << infile[line] << endl; - return; - } - m_humdrum_text << "++++++++++++++++++++" << endl; + printMultiLines(vsplit, vserial, tempout); } + + ////////////////////////////// // -// Tool_fixps::removeDuplicateDynamics -- +// Tool_extract::storeToken -- // -void Tool_fixps::removeDuplicateDynamics(HumdrumFile& infile) { - int scount = infile.getStrandCount(); - for (int i=0; iisDataType("**dynam")) { - continue; - } - HTp send = infile.getStrandEnd(i); - HTp current = sstart; - while (current && (current != send)) { - vector subtoks = current->getSubtokens(); - if (subtoks.size() % 2 == 1) { - current = current->getNextToken(); - continue; - } - bool equal = true; - int half = (int)subtoks.size() / 2; - for (int j=0; jsetText(newtext); - } - } - } +void Tool_extract::storeToken(vector& storage, const string& string) { + storage.push_back(string); +} + +void storeToken(vector& storage, int index, const string& string) { + storage[index] = string; } ////////////////////////////// // -// Tool_fixps::removeEmpties -- +// Tool_extract::isInList -- returns true if first number found in list of numbers. +// returns the matching index plus one. // -void Tool_fixps::removeEmpties(vector>& newlist, HumdrumFile& infile) { - newlist.resize(infile.getLineCount()); - for (int i=0; igetValue("delete"); - if (value == "true") { - continue; - } - newlist[i].push_back(token); +int Tool_extract::isInList(int number, vector& listofnum) { + int i; + for (i=0; i<(int)listofnum.size(); i++) { + if (listofnum[i] == number) { + return i+1; } } + return 0; + } ////////////////////////////// // -// Tool_fixps::markEmptyVoices -- +// Tool_extract::getTraceData -- // -void Tool_fixps::markEmptyVoices(HumdrumFile& infile) { - HLp barline = NULL; - for (int i=0; icompare(0, 2, "**")) { - barline = &infile[i]; - } - continue; - } - if (infile[i].isBarline()) { - barline = &infile[i]; - } - if (!infile[i].isData()) { - continue; - } - if (!barline) { +void Tool_extract::getTraceData(vector& startline, vector >& fields, + const string& tracefile, HumdrumFile& infile) { + char buffer[1024] = {0}; + HumRegex hre; + int linenum; + startline.reserve(10000); + startline.resize(0); + fields.reserve(10000); + fields.resize(0); + + ifstream input; + input.open(tracefile.c_str()); + if (!input.is_open()) { + m_error_text << "Error: cannot open file for reading: " << tracefile << endl; + return; + } + + string temps; + vector field; + vector subfield; + vector model; + + input.getline(buffer, 1024); + while (!input.eof()) { + if (hre.search(buffer, "^\\s*$")) { continue; } - // check on the data line if: - // * it is in the first subspine - // * it is an invisible rest - // * it takes the full duration of the measure - // If so, then mark the tokens for deletion in that layer. - for (int j=0; jgetTrack(); - int subtrack = token->getSubtrack(); - if (subtrack != 1) { - continue; - } - if (token->find("yy") == string::npos) { - continue; - } - if (!token->isRest()) { - continue; - } - HumNum duration = token->getDuration(); - HumNum bardur = token->getDurationToBarline(); - HTp current = token; - while (current) { - subtrack = current->getSubtrack(); - if (subtrack != 1) { - break; - } - current->setValue("delete", "true"); - if (current->isBarline()) { - break; - } - current = current->getNextToken(); - } - current = token; - current = current->getPreviousToken(); - while (current) { - if (current->isManipulator()) { - break; - } - if (current->isBarline()) { - break; - } - subtrack = current->getSubtrack(); - if (subtrack != 1) { - break; - } - current->setValue("delete", "true"); - current = current->getPreviousToken(); - } + if (!hre.search(buffer, "(\\d+)")) { + continue; + } + linenum = hre.getMatchInt(1); + linenum--; // adjust so that line 0 is the first line in the file + temps = buffer; + hre.replaceDestructive(temps, "", "\\d+"); + hre.replaceDestructive(temps, "", "[^,\\s\\d\\$\\-].*"); // remove any possible comments + hre.replaceDestructive(temps, "", "\\s", "g"); + if (hre.search(temps, "^\\s*$")) { + // no field data to process online + continue; } + startline.push_back(linenum); + string ttemp = temps; + fillFieldData(field, subfield, model, ttemp, infile); + fields.push_back(field); + input.getline(buffer, 1024); } } - - -///////////////////////////////// +////////////////////////////// // -// Tool_flipper::Tool_flipper -- Set the recognized options for the tool. +// Tool_extract::extractTrace -- // -Tool_flipper::Tool_flipper(void) { - define("k|keep=b", "keep *flip/*Xflip instructions"); - define("a|all=b", "flip globally, not just inside *flip/*Xflip regions"); - define("s|strophe=b", "flip inside of strophes as well"); - define("S|strophe-only|only-strophe=b", "flip only inside of strophes as well"); - define("i|interp=s:kern", "flip only in this interpretation"); -} - - - -///////////////////////////////// -// -// Tool_flipper::run -- Do the main work of the tool. -// +void Tool_extract::extractTrace(HumdrumFile& infile, const string& tracefile) { + vector startline; + vector > fields; + getTraceData(startline, fields, tracefile, infile); + int i, j; -bool Tool_flipper::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i& field) { + int j; + int t; + int start = 0; + int target; + + start = 0; + for (t=0; t<(int)field.size(); t++) { + target = field[t]; + for (j=0; jgetTrack() != target) { + continue; + } + if (start != 0) { + m_humdrum_text << '\t'; + } + start = 1; + m_humdrum_text << infile.token(line, j); + } + } + if (start != 0) { + m_humdrum_text << endl; } } @@ -84041,241 +84510,228 @@ void Tool_flipper::initialize(void) { ////////////////////////////// // -// Tool_flipper::processFile -- +// Tool_extract::example -- example usage of the sonority program // -void Tool_flipper::processFile(HumdrumFile& infile) { - - m_fliplines.resize(infile.getLineCount()); - fill(m_fliplines.begin(), m_fliplines.end(), false); +void Tool_extract::example(void) { + m_free_text << + " \n" + << endl; +} - m_flipState.resize(infile.getMaxTrack()+1); - if (m_allQ) { - fill(m_flipState.begin(), m_flipState.end(), true); - } else { - fill(m_flipState.begin(), m_flipState.end(), false); - } - m_strophe.resize(infile.getMaxTrack()+1); - fill(m_strophe.begin(), m_strophe.end(), false); - for (int i=0; igetTrack(); - m_strophe[track] = true; - } else if (*token == "*Xstrophe") { - track = token->getTrack(); - m_strophe[track] = false; + interpstate = 1; + if (!interpQ) { + interpQ = getBoolean("I"); + interpstate = 0; + interps = getString("I"); + } + if (interps.size() > 0) { + if (interps[0] != '*') { + // Automatically add ** if not given on exclusive interpretation + string tstring = "**"; + interps = tstring + interps; } } + removerestQ = getBoolean("no-rest"); + noEmptyQ = getBoolean("no-empty"); + emptyQ = getBoolean("empty"); + fieldQ = getBoolean("f"); + debugQ = getBoolean("debug"); + countQ = getBoolean("count"); + traceQ = getBoolean("trace"); + tracefile = getString("trace"); + reverseQ = getBoolean("reverse"); + expandQ = getBoolean("expand") || getBoolean("E"); + submodel = getString("model").c_str()[0]; + cointerp = getString("cointerp"); + comodel = getString("cospine-model").c_str()[0]; - if (m_allQ) { - // state always stays on in this case - return; + if (getBoolean("no-editoral-rests")) { + editorialInterpretation = ""; } - for (int i=0; igetTrack(); - m_flipState[track] = true; - m_fliplines[i] = true; - } else if (*token == "*Xflip") { - track = token->getTrack(); - m_flipState[track] = false; - m_fliplines[i] = true; - } + if (interpQ) { + fieldQ = true; } -} - - - -////////////////////////////// -// -// Tool_flipper::processLine -- -// + if (emptyQ) { + fieldQ = true; + } -void Tool_flipper::processLine(HumdrumFile& infile, int index) { - if (!infile[index].hasSpines()) { - return; + if (noEmptyQ) { + fieldQ = true; } - if (infile[index].isInterpretation()) { - checkForFlipChanges(infile, index); + + if (expandQ) { + fieldQ = true; + expandInterp = getString("expand-interp"); } - vector> flipees; - extractFlipees(flipees, infile, index); - if (!flipees.empty()) { - int status = flipSubspines(flipees); - if (status) { - infile[index].createLineFromTokens(); + if (!reverseQ) { + reverseQ = getBoolean("R"); + if (reverseQ) { + reverseInterp = getString("R"); } } -} + if (reverseQ) { + fieldQ = true; + } + if (excludeQ) { + fieldstring = getString("x"); + } else if (fieldQ) { + fieldstring = getString("f"); + } else if (kernQ) { + fieldstring = getString("k"); + fieldQ = true; + } else if (rkernQ) { + fieldstring = getString("K"); + fieldQ = true; + fieldstring = reverseFieldString(fieldstring, infile.getMaxTrack()); + } -////////////////////////////// -// -// Tool_flipper::flipSubspines -- -// + spineListQ = getBoolean("spine-list"); + grepQ = getBoolean("grep"); + grepString = getString("grep"); -bool Tool_flipper::flipSubspines(vector>& flipees) { - bool regenerateLine = false; - for (int i=0; i<(int)flipees.size(); i++) { - if (flipees[i].size() > 1) { - flipSpineTokens(flipees[i]); - regenerateLine = true; + if (getBoolean("name")) { + blankName = getString("name"); + if (blankName == "") { + blankName = "**blank"; + } else if (blankName.compare(0, 2, "**") != 0) { + if (blankName.compare(0, 1, "*") != 0) { + blankName = "**" + blankName; + } else { + blankName = "*" + blankName; + } + } + if (blankName == "**kern") { + addRestsQ = true; } } - return regenerateLine; + } ////////////////////////////// // -// Tool_flipper::flipSpineTokens -- +// Tool_extract::reverseFieldString -- No dollar expansion for now. // -void Tool_flipper::flipSpineTokens(vector& subtokens) { - if (subtokens.size() < 2) { - return; +string Tool_extract::reverseFieldString(const string& input, int maxval) { + string output; + string number; + for (int i=0; i<(int)input.size(); i++) { + if (isdigit(input[i])) { + number += input[i]; + continue; + } else { + if (!number.empty()) { + int value = (int)strtol(number.c_str(), NULL, 10); + value = maxval - value + 1; + output += to_string(value); + output += input[i]; + number.clear(); + } + } } - int count = (int)subtokens.size(); - count = count / 2; - HTp tok1; - HTp tok2; - string str1; - string str2; - for (int i=0; isetText(str2); - tok2->setText(str1); + if (!number.empty()) { + int value = (int)strtol(number.c_str(), NULL, 10); + value = maxval - value + 1; + output += to_string(value); } + return output; } ////////////////////////////// // -// Tool_flipper::extractFlipees -- +// Tool_fb::Tool_fb -- Set the recognized options for the tool. // -void Tool_flipper::extractFlipees(vector>& flipees, - HumdrumFile& infile, int index) { - flipees.clear(); - - HLp line = &infile[index]; - int track; - int lastInsertTrack = -1; - for (int i=0; igetFieldCount(); i++) { - HTp token = line->token(i); - track = token->getTrack(); - if ((!m_stropheQ) && m_strophe[track]) { - continue; - } - if (!m_flipState[track]) { - continue; - } - if (m_kernQ) { - if (!token->isKern()) { - continue; - } - } else { - if (!token->isDataType(m_interp)) { - continue; - } - } - if (lastInsertTrack != track) { - flipees.resize(flipees.size() + 1); - lastInsertTrack = track; - } - flipees.back().push_back(token); - } +Tool_fb::Tool_fb(void) { + define("c|compound=b", "output reasonable figured bass numbers within octave"); + define("a|accidentals|accid|acc=b", "display accidentals in front of the numbers"); + define("b|base|base-track=i:1", "number of the base kern track (compare with -k)"); + define("i|intervallsatz=b", "display numbers under their voice instead of under the base staff"); + define("o|sort|order=b", "sort figured bass numbers by size"); + define("l|lowest=b", "use lowest note as base note"); + define("n|normalize=b", "remove number 8 and doubled numbers; adds -co"); + define("r|reduce|abbreviate|abbr=b", "use abbreviated figures; adds -nco"); + define("t|ties=b", "hide numbers without attack or changing base (needs -i)"); + define("f|figuredbass=b", "shortcut for -acorn3"); + define("3|hide-three=b", "hide number 3 if it has an accidental"); + define("m|negative=b", "show negative numbers"); + define("above=b", "show numbers above the staff (**fba)"); + define("rate=s:", "rate to display the numbers (use a **recip value, e.g. 4, 4.)"); + define("k|kern-tracks=s", "process only the specified kern spines"); + define("s|spine-tracks|spine|spines|track|tracks=s", "Process only the specified spines"); + define("hint=b", "determine harmonic intervals with interval quality"); } - - -///////////////////////////////// +////////////////////////////// // -// Tool_gasparize::Tool_gasparize -- Set the recognized options for the tool. +// Tool_fb::run -- Do the main work of the tool. // -Tool_gasparize::Tool_gasparize(void) { - define("R|no-reference-records=b", "do not add reference records"); - define("r|only-add-reference-records=b", "only add reference records"); - - define("B|do-not-delete-breaks=b", "do not delete system/page break markers"); - define("b|only-delete-breaks=b", "only delete breaks"); +bool Tool_fb::run(HumdrumFileSet &infiles) { + bool status = true; + for (int i = 0; i < infiles.getCount(); i++) { + status &= run(infiles[i]); + } + return status; +} - define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations"); - define("a|only-fix-instrument-abbreviations=b", "only fix instrument abbreviations"); - - define("E|do-not-fix-editorial-accidentals=b", "do not fix instrument abbreviations"); - define("e|only-fix-editorial-accidentals=b", "only fix editorial accidentals"); - - define("T|do-not-add-terminal-longs=b", "do not add terminal long markers"); - define("t|only-add-terminal-longs=b", "only add terminal longs"); - - define("no-ties=b", "do not fix tied notes"); - - define("N|do-not-remove-empty-transpositions=b", "do not remove empty transposition instructions"); - define ("n|only-remove-empty-transpositions=b", "only remove empty transpositions"); -} - - - -///////////////////////////////// -// -// Tool_gasparize::run -- Primary interfaces to the tool. -// - -bool Tool_gasparize::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i numbers; + vector kernspines = infile.getKernSpineStartList(); + int maxTrack = infile.getMaxTrack(); -////////////////////////////// -// -// Tool_gasparize::removeArticulations -- -// + // Do nothing if base track not withing kern track range + if (m_baseTrackQ < 1 || m_baseTrackQ > maxTrack) { + return; + } -void Tool_gasparize::removeArticulations(HumdrumFile& infile) { - HumRegex hre; - for (int i=0; iisKern()) { - continue; - } - if (token->isNull()) { + m_selectedKernSpines.resize(maxTrack + 1); // +1 is needed since track=0 is not used + // By default, process all tracks: + fill(m_selectedKernSpines.begin(), m_selectedKernSpines.end(), true); + // Otherwise, select which **kern track, or spine tracks to process selectively: + + // Calculate which input spines to process based on -s or -k option: + if (!m_kernTracks.empty()) { + vector ktracks = Convert::extractIntegerList(m_kernTracks, maxTrack); + fill(m_selectedKernSpines.begin(), m_selectedKernSpines.end(), false); + for (int i=0; i<(int)ktracks.size(); i++) { + int index = ktracks[i] - 1; + if ((index < 0) || (index >= (int)kernspines.size())) { continue; } - bool changed = false; - string text = token->getText(); - if (text.find("'") != string::npos) { - // remove staccatos - changed = true; - hre.replaceDestructive(text, "", "'", "g"); - } - if (text.find("~") != string::npos) { - // remove tenutos - changed = true; - hre.replaceDestructive(text, "", "~", "g"); - } - if (changed) { - token->setText(text); - } + int track = kernspines.at(ktracks[i] - 1)->getTrack(); + m_selectedKernSpines.at(track) = true; } + } else if (!m_spineTracks.empty()) { + infile.makeBooleanTrackList(m_selectedKernSpines, m_spineTracks); } -} - + vector> lastNumbers = {}; + lastNumbers.resize((int)grid.getVoiceCount()); + vector> currentNumbers = {}; -////////////////////////////// -// -// Tool_gasparize::adjustSystemDecoration -- -// !!!system-decoration: [(s1)(s2)(s3)(s4)] -// to: -// !!!system-decoration: [*] -// + // Interate through the NoteGrid and fill the numbers vector with + // all generated FiguredBassNumbers + for (int i=0; i<(int)grid.getSliceCount(); i++) { + currentNumbers.clear(); + currentNumbers.resize((int)grid.getVoiceCount()); -void Tool_gasparize::adjustSystemDecoration(HumdrumFile& infile) { - for (int i=infile.getLineCount() - 1; i>=0; i--) { - if (!infile[i].isReference()) { - continue; - } - HTp token = infile.token(i, 0); - if (token->compare(0, 21, "!!!system-decoration:") == 0) { - token->setText("!!!system-decoration: [*]"); - break; - } - } -} + // Reset usedBaseKernTrack + int usedBaseKernTrack = m_baseTrackQ; + // Overwrite usedBaseKernTrack with the lowest voice index of the lowest pitched note + if (m_lowestQ) { + int lowestNotePitch = 99999; + for (int k=0; k<(int)grid.getVoiceCount(); k++) { + NoteCell* checkCell = grid.cell(k, i); + HTp currentToken = checkCell->getToken(); + int initialTokenTrack = currentToken->getTrack(); + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); -////////////////////////////// -// -// Tool_gasparize::deleteDummyTranspositions -- Somehow empty -// transpositions that go to the same pitch can appear in the -// MusicXML data, so remove them here. Example: -// *Trd0c0 -// + if (abs(lowest) < lowestNotePitch) { + lowestNotePitch = abs(lowest); + usedBaseKernTrack = k + 1; + } -void Tool_gasparize::deleteDummyTranspositions(HumdrumFile& infile) { - vector ldel; - for (int i=0; igetNextField(); + if (nextToken && (initialTokenTrack == nextToken->getTrack())) { + currentToken = nextToken; + } else { + // Break loop if nextToken is not the same track as initialTokenTrack + break; + } + } while (currentToken); + } } - if (!infile[i].isInterpretation()) { + + NoteCell* baseCell = grid.cell(usedBaseKernTrack - 1, i); + + // Ignore grace notes + if (baseCell->getToken()->getOwner()->getDuration() == 0) { continue; } - bool empty = true; - for (int j=0; jgetLineIndex()); + + // Hide numbers if they do not match rhythmic position of --rate + if (!m_rateQ.empty()) { + // Get time signatures + vector> timeSigs; + infile.getTimeSigs(timeSigs, baseCell->getToken()->getTrack()); + // Ignore numbers if they don't fit + if (hideNumbersForTokenLine(baseCell->getToken(), timeSigs[baseCell->getLineIndex()])) { continue; } - if (!token->isKern()) { - empty = false; - continue; + } + + + HTp currentToken = baseCell->getToken(); + int initialTokenTrack = baseCell->getToken()->getTrack(); + int lowestBaseNoteBase40Pitch = 9999; + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + + // Ignore if base is a rest or silent note + if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) { + if(abs(lowest) < lowestBaseNoteBase40Pitch) { + lowestBaseNoteBase40Pitch = abs(lowest); + } } - if (*token == "*Trd0c0") { - token->setText("*"); + + HTp nextToken = currentToken->getNextField(); + if (nextToken && (initialTokenTrack == nextToken->getTrack())) { + currentToken = nextToken; } else { - empty = false; + // Break loop if nextToken is not the same track as initialTokenTrack + break; } + } while (currentToken); + + // Ignore if base is a rest or silent note + if ((lowestBaseNoteBase40Pitch == 0) || (lowestBaseNoteBase40Pitch == -1000) || (lowestBaseNoteBase40Pitch == -2000) || (lowestBaseNoteBase40Pitch == 9999)) { + continue; } - if (empty) { - ldel.push_back(i); - } - } - if (ldel.size() == 1) { - infile.deleteLine(ldel[0]); - } else if (ldel.size() > 1) { - cerr << "Warning: multiple transposition lines, not deleting them" << endl; - } + // Interate through each voice + for (int j=0; j<(int)grid.getVoiceCount(); j++) { + NoteCell* targetCell = grid.cell(j, i); -} + // Ignore voice if track is not active by --kern-tracks or --spine-tracks + if (m_selectedKernSpines.at(targetCell->getToken()->getTrack()) == false) { + continue; + } + HTp currentToken = targetCell->getToken(); + int initialTokenTrack = targetCell->getToken()->getTrack(); + vector chordNumbers = {}; -////////////////////////////// -// -// Tool_gasparize::fixEditorialAccidentals -- checkDataLine() does -// all of the work for this function, which only manages -// key signature and barline processing. -// Rules for accidentals in Tasso in Music Project: -// (1) Only note accidentals printed in the source editions -// are displayed as regular accidentals. These accidentals -// are postfixed with an "X" in the **kern data. -// (2) Editorial accidentals are given an "i" marker but not -// a "X" marker in the **kern data. This editorial accidental -// is displayed above the note. -// This algorithm makes adjustments to the input data because -// Sibelius will drop editorial information after the frist -// editorial accidental on that pitch in the measure. -// (3) If a note is the same pitch as a previous note in the -// measure and the previous note has an editorial accidental, -// then make the note an editorial note. However, if the -// accidental state of the note matches the key-signature, -// then do not add an editorial accidental, and there will be -// no accidental displayed on the note. In that case, add a "y" -// after the accidental to indicate that it is interpreted -// and not visible in the original score. -// + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + for (int subtokenBase40: resolvedToken->getBase40Pitches()) { -void Tool_gasparize::fixEditorialAccidentals(HumdrumFile& infile) { - removeDoubledAccidentals(infile); + // Ignore if target is a rest or silent note + if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) { + continue; + } - m_pstates.resize(infile.getMaxTrack() + 1); - m_estates.resize(infile.getMaxTrack() + 1); - m_kstates.resize(infile.getMaxTrack() + 1); + // Ignore if same pitch as base voice + if ((abs(lowestBaseNoteBase40Pitch) == abs(subtokenBase40)) && (baseCell->getToken()->getTrack() == initialTokenTrack)) { + continue; + } - for (int i=0; i<(int)m_pstates.size(); i++) { - m_pstates[i].resize(70); - fill(m_pstates[i].begin(), m_pstates[i].end(), 0); - m_kstates[i].resize(70); - fill(m_kstates[i].begin(), m_kstates[i].end(), 0); - m_estates[i].resize(70); - fill(m_estates[i].begin(), m_estates[i].end(), false); + // Create FiguredBassNumber + FiguredBassNumber* number = createFiguredBassNumber(abs(lowestBaseNoteBase40Pitch), abs(subtokenBase40), targetCell->getVoiceIndex(), targetCell->getLineIndex(), targetCell->isAttack(), keySignature); + + currentNumbers[j].push_back(number->m_number); + chordNumbers.push_back(number); + } + + HTp nextToken = currentToken->getNextField(); + if (nextToken && (initialTokenTrack == nextToken->getTrack())) { + currentToken = nextToken; + } else { + // Break loop if nextToken is not the same track as initialTokenTrack + break; + } + } while (currentToken); + + // Sort chord numbers by size + sort(chordNumbers.begin(), chordNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { + return a->m_number > b->m_number; + }); + + // Then add to numbers vector + for (FiguredBassNumber* num: chordNumbers) { + if (lastNumbers[j].size() != 0) { + // If a number belongs to a sustained note but the base note did change + // the new numbers need to be displayable + num->m_baseOfSustainedNoteDidChange = !num->m_isAttack && std::find(lastNumbers[j].begin(), lastNumbers[j].end(), num->m_number) == lastNumbers[j].end(); + } + numbers.push_back(num); + } + } + + // Set current numbers as the new last numbers + lastNumbers = currentNumbers; } - for (int i=0; i trackData = getTrackDataForVoice(voiceIndex, numbers, infile.getLineCount()); + if (voiceIndex + 1 < grid.getVoiceCount()) { + int trackIndex = kernspines[voiceIndex + 1]->getTrack(); + infile.insertDataSpineBefore(trackIndex, trackData, ".", exinterp); + } else { + infile.appendDataSpine(trackData, ".", exinterp); + } + } + } else { + // Create **fb spine and bind it to the base voice + vector trackData = getTrackData(numbers, infile.getLineCount()); + if (m_baseTrackQ < grid.getVoiceCount()) { + int trackIndex = kernspines[m_baseTrackQ]->getTrack(); + infile.insertDataSpineBefore(trackIndex, trackData, ".", exinterp); + } else { + infile.appendDataSpine(trackData, ".", exinterp); } } + + // Enables usage in verovio (`!!!filter: fb`) + m_humdrum_text << infile; } ////////////////////////////// // -// Tool_gasparize::removeDoubledAccidentals -- Often caused by transposition -// differences between parts in the MusicXML export from Finale. Also some -// strange double sharps appear randomly. +// Tool_fb::hideNumbersForTokenLine -- Checks if rhythmic position of line should display numbers // -void Tool_gasparize::removeDoubledAccidentals(HumdrumFile& infile) { - HumRegex hre; - for (int i=0; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - if (token->isRest()) { - continue; - } - if (token->find("--") != string::npos) { - string text = *token; - hre.replaceDestructive(text, "-", "--", "g"); - } else if (token->find("--") != string::npos) { - string text = *token; - hre.replaceDestructive(text, "#", "##", "g"); - } +bool Tool_fb::hideNumbersForTokenLine(HTp token, pair timeSig) { + // Get note duration from --rate option + HumNum rateDuration = Convert::recipToDuration(m_rateQ); + if (rateDuration.toFloat() != 0) { + double timeSigBarDuration = timeSig.first * Convert::recipToDuration(to_string(timeSig.second.getInteger())).toFloat(); + double durationFromBarline = token->getDurationFromBarline().toFloat(); + // Handle upbeats + if (token->getBarlineDuration().toFloat() < timeSigBarDuration) { + // Fix durationFromBarline when current bar duration is shorter than + // the bar duration of the time signature + durationFromBarline = timeSigBarDuration - token->getDurationToBarline().toFloat(); } + // Checks if rhythmic position is divisible by rateDuration + return fmod(durationFromBarline, rateDuration.toFloat()) != 0; } + return false; } ////////////////////////////// // -// Tool_gasparize::addTerminalLongs -- Convert all last notes to terminal longs -// Also probably add terminal longs before double barlines as in JRP. +// Tool_fb::getTrackData -- Create **fb spine data with formatted numbers for all voices // -void Tool_gasparize::addTerminalLongs(HumdrumFile& infile) { - int scount = infile.getStrandCount(); - for (int i=0; iisKern()) { - continue; +vector Tool_fb::getTrackData(const vector& numbers, int lineCount) { + vector trackData; + trackData.resize(lineCount); + + for (int i = 0; i < lineCount; i++) { + vector sliceNumbers = filterFiguredBassNumbersForLine(numbers, i); + if (sliceNumbers.size() > 0) { + trackData[i] = formatFiguredBassNumbers(sliceNumbers); } - while (cur) { - if (!cur->isData()) { - cur = cur->getPreviousToken(); - continue; - } - if (cur->isNull()) { - cur = cur->getPreviousToken(); - continue; - } - if (cur->isRest()) { - cur = cur->getPreviousToken(); - continue; - } - if (cur->isSecondaryTiedNote()) { - cur = cur->getPreviousToken(); - continue; - } - if (cur->find("l") != string::npos) { - // already marked so do not do it again - break; - } - // mark this note with "l" - string newtext = *cur; - newtext += "l"; - cur->setText(newtext); - break; + } + + return trackData; +} + + + +////////////////////////////// +// +// Tool_fb::getTrackDataForVoice -- Create **fb spine data with formatted numbers for passed voiceIndex +// + +vector Tool_fb::getTrackDataForVoice(int voiceIndex, const vector& numbers, int lineCount) { + vector trackData; + trackData.resize(lineCount); + + for (int i = 0; i < lineCount; i++) { + vector sliceNumbers = filterFiguredBassNumbersForLineAndVoice(numbers, i, voiceIndex); + if (sliceNumbers.size() > 0) { + trackData[i] = formatFiguredBassNumbers(sliceNumbers); } } + + return trackData; } ////////////////////////////// // -// Tool_gasparize::fixInstrumentAbbreviations -- +// Tool_fb::createFiguredBassNumber -- Create FiguredBassNumber from a NoteCell. +// The figured bass number (num) is calculated with a base and target NoteCell +// as well as a passed key signature. // -void Tool_gasparize::fixInstrumentAbbreviations(HumdrumFile& infile) { - int iline = -1; - int aline = -1; +FiguredBassNumber* Tool_fb::createFiguredBassNumber(int basePitchBase40, int targetPitchBase40, int voiceIndex, int lineIndex, bool isAttack, string keySignature) { - vector kerns = infile.getKernSpineStartList(); - if (kerns.empty()) { - return; + // Calculate figured bass number + int baseDiatonicPitch = Convert::base40ToDiatonic(basePitchBase40); + int targetDiatonicPitch = Convert::base40ToDiatonic(targetPitchBase40); + int diff = abs(targetDiatonicPitch) - abs(baseDiatonicPitch); + int num; + + if ((baseDiatonicPitch == 0) || (targetDiatonicPitch == 0)) { + num = 0; + } else if (diff == 0) { + num = 1; + } else if (diff > 0) { + num = diff + 1; + } else { + num = diff - 1; } - HTp cur = kerns[0]; - while (cur) { - if (cur->isData()) { - break; - } - if (cur->compare(0, 3, "*I\"") == 0) { - iline = cur->getLineIndex(); - } else if (cur->compare(0, 3, "*I'") == 0) { - aline = cur->getLineIndex(); - } - cur = cur->getNextToken(); + // Transform key signature to lower case + transform(keySignature.begin(), keySignature.end(), keySignature.begin(), [](unsigned char c) { + return tolower(c); + }); + + char targetPitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(targetPitchBase40)); + int targetAccidNr = Convert::base40ToAccidental(targetPitchBase40); + string targetAccid; + for (int i=0; iisKern()) { - continue; - } - if (!hre.search(*infile.token(iline, j), "([A-Za-z][A-Za-z .0-9]+)")) { - continue; - } - string name = hre.getMatch(1); - string abbr = "*I'"; - if (name == "Basso Continuo") { - abbr += "BC"; - } else if (name == "Basso continuo") { - abbr += "BC"; - } else if (name == "basso continuo") { - abbr += "BC"; + + // Show accidentlas when pitch class of base and target is equal but alteration is different + if (basePitchName == targetPitchName) { + if (baseAccidNr == targetAccidNr) { + showAccid = false; } else { - abbr += toupper(name[0]); + accid = (targetAccidNr == 0) ? "n" : targetAccid; + showAccid = true; } - // check for numbers after the end of the name and add to abbreviation - infile.token(aline, j)->setText(abbr); } + + string intervalQuality = getIntervalQuality(basePitchBase40, targetPitchBase40); + + FiguredBassNumber* number = new FiguredBassNumber(num, accid, showAccid, voiceIndex, lineIndex, isAttack, m_intervallsatzQ, intervalQuality, m_hintQ); + + return number; } ////////////////////////////// // -// Tool_gasparize::convertBreaks -- +// Tool_fb::filterNegativeNumbers -- Hide negative numbers if m_showNegativeQ if not true // -void Tool_gasparize::convertBreaks(HumdrumFile& infile) { - HumRegex hre; - for (int i=infile.getLineCount()-1; i>= 0; i--) { - if (!infile[i].isGlobalComment()) { - continue; - } - if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) { - string text = "!!LO:LB:g=original"; - infile[i].setText(text); - } - else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) { - string text = "!!LO:PB:g=original"; - infile[i].setText(text); - } - } +vector Tool_fb::filterNegativeNumbers(vector numbers) { + + vector filteredNumbers; + + bool mQ = m_showNegativeQ; + copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [mQ](FiguredBassNumber* num) { + return mQ ? true : (num->m_number > 0); + }); + + return filteredNumbers; } ////////////////////////////// // -// Tool_gasparize::deleteBreaks -- +// Tool_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music. // -void Tool_gasparize::deleteBreaks(HumdrumFile& infile) { - HumRegex hre; - for (int i=infile.getLineCount()-1; i>= 0; i--) { - if (!infile[i].isGlobalComment()) { - continue; - } - if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) { - infile.deleteLine(i); - } - else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) { - infile.deleteLine(i); - } - } +vector Tool_fb::filterFiguredBassNumbersForLine(vector numbers, int lineIndex) { + + vector filteredNumbers; + + // filter numbers with passed lineIndex + copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex](FiguredBassNumber* num) { + return num->m_lineIndex == lineIndex; + }); + + // sort by voiceIndex + sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { + return a->m_voiceIndex > b->m_voiceIndex; + }); + + return filterNegativeNumbers(filteredNumbers); } -//////////////////////////////// + +////////////////////////////// // -// Tool_gasparize::addBibliographicRecords -- +// Tool_fb::filterFiguredBassNumbersForLineAndVoice -- // -// !!!COM: -// !!!CDT: -// !!!OTL: -// !!!AGN: -// !!!SCT: -// !!!SCA: -// !!!voices: + +vector Tool_fb::filterFiguredBassNumbersForLineAndVoice(vector numbers, int lineIndex, int voiceIndex) { + + vector filteredNumbers; + + // filter numbers with passed lineIndex and passed voiceIndex + copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex, voiceIndex](FiguredBassNumber* num) { + return (num->m_lineIndex == lineIndex) && (num->m_voiceIndex == voiceIndex); + }); + + // sort by voiceIndex (probably not needed here) + sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { + return a->m_voiceIndex > b->m_voiceIndex; + }); + + return filterNegativeNumbers(filteredNumbers); +} + + + +////////////////////////////// // -// At end: -// !!!RDF**kern: l = terminal long -// !!!RDF**kern: i = editorial accidental -// !!!EED: -// !!!EEV: $DATE +// Tool_fb::formatFiguredBassNumbers -- Create a **fb data record string out of the passed FiguredBassNumber objects // -void Tool_gasparize::addBibliographicRecords(HumdrumFile& infile) { - vector refinfo = infile.getReferenceRecords(); - map refs; - for (int i=0; i<(int)refinfo.size(); i++) { - string key = refinfo[i]->getReferenceKey(); - refs[key] = refinfo[i]; - } +string Tool_fb::formatFiguredBassNumbers(const vector& numbers) { - // header records - if (refs.find("voices") == refs.end()) { - if (infile.token(0, 0)->find("!!!OTL") != string::npos) { - infile.insertLine(1, "!!!voices:"); - } else { - infile.insertLine(0, "!!!voices:"); - } - } - if (refs.find("SCA") == refs.end()) { - if (infile.token(0, 0)->find("!!!OTL") != string::npos) { - infile.insertLine(1, "!!!SCA:"); - } else { - infile.insertLine(0, "!!!SCA:"); - } - } - if (refs.find("SCT") == refs.end()) { - if (infile.token(0, 0)->find("!!!OTL") != string::npos) { - infile.insertLine(1, "!!!SCT:"); - } else { - infile.insertLine(0, "!!!SCT:"); - } + vector formattedNumbers; + + // Normalize numbers (remove 8 and 1, sort by size, remove duplicate numbers) + if (m_normalizeQ) { + bool aQ = m_accidentalsQ; + // remove 8 and 1 but keep them if they have an accidental + copy_if(numbers.begin(), numbers.end(), back_inserter(formattedNumbers), [aQ](FiguredBassNumber* num) { + return ((num->getNumberWithinOctave() != 8) && (num->getNumberWithinOctave() != 1)) || (aQ && num->m_showAccidentals); + }); + // sort by size + sort(formattedNumbers.begin(), formattedNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { + return a->getNumberWithinOctave() < b->getNumberWithinOctave(); + }); + // remove duplicate numbers + formattedNumbers.erase(unique(formattedNumbers.begin(), formattedNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) { + return a->getNumberWithinOctave() == b->getNumberWithinOctave(); + }), formattedNumbers.end()); + } else { + formattedNumbers = numbers; } - if (refs.find("AGN") == refs.end()) { - if (infile.token(0, 0)->find("!!!OTL") != string::npos) { - infile.insertLine(1, "!!!AGN:"); - } else { - infile.insertLine(0, "!!!AGN:"); - } + + // Hide numbers if they have no attack + if (m_intervallsatzQ && m_attackQ) { + vector attackNumbers; + copy_if(formattedNumbers.begin(), formattedNumbers.end(), back_inserter(attackNumbers), [](FiguredBassNumber* num) { + return num->m_isAttack || num->m_baseOfSustainedNoteDidChange; + }); + formattedNumbers = attackNumbers; } - if (refs.find("OTL") == refs.end()) { - infile.insertLine(0, "!!!OTL:"); + // Analysze before sorting + if (m_compoundQ) { + formattedNumbers = analyzeChordNumbers(formattedNumbers); } - if (refs.find("CDT") == refs.end()) { - infile.insertLine(0, "!!!CDT: ~1450-~1517"); + + // Sort numbers by size + if (m_sortQ) { + bool cQ = m_compoundQ; + sort(formattedNumbers.begin(), formattedNumbers.end(), [cQ](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { + // sort by getNumberWithinOctave if compoundQ is true otherwise sort by number + return (cQ) ? a->getNumberWithinOctave() > b->getNumberWithinOctave() : a->m_number > b->m_number; + }); } - if (refs.find("COM") == refs.end()) { - infile.insertLine(0, "!!!COM: Gaspar van Weerbeke"); + + if (m_reduceQ) { + // Overwrite formattedNumbers with abbreviated numbers + formattedNumbers = getAbbreviatedNumbers(formattedNumbers); } - // trailer records - bool foundi = false; - bool foundj = false; - bool foundl = false; - for (int i=0; ifind("!!!RDF**kern:") == string::npos) { - continue; - } - if (token->find("terminal breve") != string::npos) { - foundl = true; - } else if (token->find("editorial accidental") != string::npos) { - if (token->find("i =") != string::npos) { - foundi = true; - } else if (token->find("j =") != string::npos) { - foundj = true; - } + // join numbers + string str = ""; + bool first = true; + for (FiguredBassNumber* number: formattedNumbers) { + string num = number->toString(m_compoundQ, m_accidentalsQ, m_hideThreeQ); + if (num.length() > 0) { + if (!first) str += " "; + first = false; + str += num; } } - if (!foundj) { - infile.appendLine("!!!RDF**kern: j = editorial accidental, optional, paren up"); - } - if (!foundi) { - infile.appendLine("!!!RDF**kern: i = editorial accidental"); - } - if (!foundl) { - infile.appendLine("!!!RDF**kern: l = terminal long"); - } + return str; +} - if (refs.find("PTL") == refs.end()) { - infile.appendLine("!!!PTL: Gaspar van Weerbeke: Collected Works. V. Settings of Liturgical Texts, Songs, and Instrumental Works"); - } - if (refs.find("PPR") == refs.end()) { - infile.appendLine("!!!PPR: American Institute of Musicology"); - } - if (refs.find("PC#") == refs.end()) { - infile.appendLine("!!!PC#: Corpus Mensurabilis Musicae 106/V"); - } - if (refs.find("PDT") == refs.end()) { - infile.appendLine("!!!PDT: {YEAR}"); - } - if (refs.find("PED") == refs.end()) { - infile.appendLine("!!!PED: Kolb, Paul"); - infile.appendLine("!!!PED: Pavanello, Agnese"); - } - if (refs.find("YEC") == refs.end()) { - infile.appendLine("!!!YEC: Copyright {YEAR}, Kolb, Paul"); - infile.appendLine("!!!YEC: Copyright {YEAR}, Pavanello, Agnese"); - } - if (refs.find("YEM") == refs.end()) { - infile.appendLine("!!!YEM: CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-nc/4.0/legalcode)"); - } - if (refs.find("EED") == refs.end()) { - infile.appendLine("!!!EED: Zybina, Karina"); - infile.appendLine("!!!EED: Mair-Gruber, Roland"); + + +////////////////////////////// +// +// Tool_fb::getAbbreviatedNumbers -- Get abbreviated figured bass numbers +// If no abbreviation is found all numbers will be shown + +vector Tool_fb::getAbbreviatedNumbers(const vector& numbers) { + + vector abbreviatedNumbers; + + string numberString = getNumberString(numbers); + + // Check if an abbreviation exists for passed numbers + auto it = find_if(FiguredBassAbbreviationMapping::s_mappings.begin(), FiguredBassAbbreviationMapping::s_mappings.end(), [&numberString](const FiguredBassAbbreviationMapping& abbr) { + return abbr.m_str == numberString; + }); + + if (it != FiguredBassAbbreviationMapping::s_mappings.end()) { + const FiguredBassAbbreviationMapping& abbr = *it; + bool aQ = m_accidentalsQ; + // Store numbers to display by the abbreviation mapping in abbreviatedNumbers + copy_if(numbers.begin(), numbers.end(), back_inserter(abbreviatedNumbers), [&abbr, aQ](FiguredBassNumber* num) { + const vector& nums = abbr.m_numbers; + // Show numbers if they are part of the abbreviation mapping or if they have an accidental + return (find(nums.begin(), nums.end(), num->getNumberWithinOctave()) != nums.end()) || (num->m_showAccidentals && aQ); + }); + + return abbreviatedNumbers; } - if (refs.find("EEV") == refs.end()) { - string date = getDate(); - string line = "!!!EEV: " + date; - infile.appendLine(line); + + return numbers; +} + + + +////////////////////////////// +// +// Tool_fb::analyzeChordNumbers -- Analyze chord numbers and improve them +// Set m_convert2To9 to true when a 3 is included in the chord numbers. + +vector Tool_fb::analyzeChordNumbers(const vector& numbers) { + + vector analyzedNumbers = numbers; + + // Check if compound numbers 3 is withing passed numbers (chord) + auto it = find_if(analyzedNumbers.begin(), analyzedNumbers.end(), [](FiguredBassNumber* number) { + return number->getNumberWithinOctave() == 3; + }); + if (it != analyzedNumbers.end()) { + for (auto &number : analyzedNumbers) { + number->m_convert2To9 = true; + } } + + return analyzedNumbers; } -//////////////////////////////// +////////////////////////////// // -// Tool_gasparize::checkDataLine -- +// Tool_fb::getNumberString -- Get only the numbers (without accidentals) of passed FiguredBassNumbers // -void Tool_gasparize::checkDataLine(HumdrumFile& infile, int lineindex) { - HumdrumLine& line = infile[lineindex]; - - HumRegex hre; - HTp token; - bool haseditQ; - int base7; - int accid; - int track; - bool removeQ; - for (int i=0; igetTrack(); - if (!token->isKern()) { - continue; - } - if (token->isNull()) { - continue; - } - if (token->isRest()) { - continue; - } - if (token->find('j') != string::npos) { - continue; - } - if (token->isSecondaryTiedNote()) { - continue; - } - - base7 = Convert::kernToBase7(token); - accid = Convert::kernToAccidentalCount(token); - haseditQ = false; - removeQ = false; - - // Hard-wired to "i" as editorial accidental marker - if (token->find("ni") != string::npos) { - haseditQ = true; - } else if (token->find("-i") != string::npos) { - haseditQ = true; - } else if (token->find("#i") != string::npos) { - haseditQ = true; - } else if (token->find("nXi") != string::npos) { - haseditQ = true; - removeQ = true; - } else if (token->find("-Xi") != string::npos) { - haseditQ = true; - removeQ = true; - } else if (token->find("#Xi") != string::npos) { - haseditQ = true; - removeQ = true; - } - - if (removeQ) { - string temp = *token; - hre.replaceDestructive(temp, "", "X"); - token->setText(temp); - } - - bool explicitQ = false; - if (token->find("#X") != string::npos) { - explicitQ = true; - } else if (token->find("-X") != string::npos) { - explicitQ = true; - } else if (token->find("nX") != string::npos) { - explicitQ = true; - } else if (token->find("n") != string::npos) { - // add an explicit accidental marker - explicitQ = true; - string text = *token; - hre.replaceDestructive(text, "nX", "n"); - token->setText(text); +string Tool_fb::getNumberString(vector numbers) { + // Sort numbers by size + sort(numbers.begin(), numbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { + return a->getNumberWithinOctave() > b->getNumberWithinOctave(); + }); + // join numbers + string str = ""; + bool first = true; + for (FiguredBassNumber* nr: numbers) { + int num = nr->getNumberWithinOctave(); + if (num > 0) { + if (!first) str += " "; + first = false; + str += to_string(num); } + } + return str; +} - if (haseditQ) { - // Store new editorial pitch state. - m_estates.at(track).at(base7) = true; - m_pstates.at(track).at(base7) = accid; - continue; - } - if (explicitQ) { - // No need to make editorial since it is visible. - m_estates.at(track).at(base7) = false; - m_pstates.at(track).at(base7) = accid; - continue; - } - if (accid == m_kstates.at(track).at(base7)) { - // !m_estates.at(track).at(base7)) { - // add !m_estates.at(track).at(base) as a condition if - // you want editorial accidentals to be added to return the - // note to the accidental in the key. - // - // The accidental matches the key-signature state, - // so it should not be made editorial eventhough - // it is not visible. - m_pstates.at(track).at(base7) = accid; +////////////////////////////// +// +// Tool_fb::getKeySignature -- Get the key signature for a line index of the input file +// - // Add a "y" marker of there is an interpreted accidental - // state (flat or sharp) that is part of the key signature. - int hasaccid = false; - if (token->find("#") != string::npos) { - hasaccid = true; - } else if (token->find("-") != string::npos) { - hasaccid = true; - } - int hashide = false; - if (token->find("-y") != string::npos) { - hashide = true; - } - else if (token->find("#y") != string::npos) { - hashide = true; +string Tool_fb::getKeySignature(HumdrumFile& infile, int lineIndex) { + string keySignature = ""; + [&] { + for (int i = 0; i < infile.getLineCount(); i++) { + if (i > lineIndex) { + return; } - if (hasaccid && !hashide) { - string text = *token; - hre.replaceDestructive(text, "#y", "#"); - hre.replaceDestructive(text, "-y", "-"); - token->setText(text); + HLp line = infile.getLine(i); + for (int j = 0; j < line->getFieldCount(); j++) { + if (line->token(j)->isKeySignature()) { + keySignature = line->getTokenString(j); + } } - - continue; } + }(); + return keySignature; +} - // At this point the previous note with this pitch class - // had an editorial accidental, and this note also has the - // same accidental, or there was a previous visual accidental - // outside of the key signature that will cause this note to have - // an editorial accidental mark applied (Sibelius will drop - // secondary editorial accidentals in a measure when exporting, - // MusicXML, which is why this function is needed). - m_estates[track][base7] = true; - m_pstates[track][base7] = accid; - string text = token->getText(); - HumRegex hre; - hre.replaceDestructive(text, "#", "##+", "g"); - hre.replaceDestructive(text, "-", "--+", "g"); - string output = ""; - bool foundQ = false; - for (int j=0; j<(int)text.size(); j++) { - if (text[j] == 'n') { - output += "ni"; - foundQ = true; - } else if (text[j] == '#') { - output += "#i"; - foundQ = true; - } else if (text[j] == '-') { - output += "-i"; - foundQ = true; - } else { - output += text[j]; - } - } +////////////////////////////// +// +// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent +// TODO: Handle negative values and sustained notes +// - if (foundQ) { - token->setText(output); - continue; - } +int Tool_fb::getLowestBase40Pitch(vector base40Pitches) { + vector filteredBase40Pitches; + copy_if(base40Pitches.begin(), base40Pitches.end(), std::back_inserter(filteredBase40Pitches), [](int base40Pitch) { + // Ignore if base is a rest or silent note + return (base40Pitch != -1000) && (base40Pitch != -2000) && (base40Pitch != 0); + }); - // The note is natural, but has no natural sign. - // add the natural sign and editorial mark. - for (int j=(int)output.size()-1; j>=0; j--) { - if ((tolower(output[j]) >= 'a') && (tolower(output[j]) <= 'g')) { - output.insert(j+1, "ni"); - break; - } - } - token->setText(output); + if (filteredBase40Pitches.size() == 0) { + return -2000; } + + return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches)); } -//////////////////////////////// +////////////////////////////// // -// Tool_gasparize::updateKeySignatures -- Fill in the accidental -// states for each diatonic pitch. +// Tool_fb::getIntervalQuality -- Return interval quality prefix string // -void Tool_gasparize::updateKeySignatures(HumdrumFile& infile, int lineindex) { - HumdrumLine& line = infile[lineindex]; - int track; - for (int i=0; iisKeySignature()) { - continue; - } - HTp token = line.token(i); - track = token->getTrack(); - string text = token->getText(); - fill(m_kstates[track].begin(), m_kstates[track].end(), 0); - for (int j=3; j<(int)text.size()-1; j++) { - if (text[j] == ']') { - break; - } - switch (text[j]) { - case 'a': case 'A': - switch (text[j+1]) { - case '#': m_kstates[track][5] = +1; - break; - case '-': m_kstates[track][5] = -1; - break; - } - break; +string Tool_fb::getIntervalQuality(int basePitchBase40, int targetPitchBase40) { - case 'b': case 'B': - switch (text[j+1]) { - case '#': m_kstates[track][6] = +1; - break; - case '-': m_kstates[track][6] = -1; - break; - } - break; + int diff = (targetPitchBase40 - basePitchBase40) % 40; - case 'c': case 'C': - switch (text[j+1]) { - case '#': m_kstates[track][0] = +1; - break; - case '-': m_kstates[track][0] = -1; - break; - } - break; + diff = diff < -2 ? abs(diff) : diff; - case 'd': case 'D': - switch (text[j+1]) { - case '#': m_kstates[track][1] = +1; - break; - case '-': m_kstates[track][1] = -1; - break; - } - break; + // See https://wiki.ccarh.org/wiki/Base_40 + string quality; + switch (diff) { + // 1 + case -2: + case 38: + quality = "dd"; break; + case -1: + case 39: + quality = "d"; break; + case 0: quality = "P"; break; + case 1: quality = "A"; break; + case 2: quality = "AA"; break; - case 'e': case 'E': - switch (text[j+1]) { - case '#': m_kstates[track][2] = +1; - break; - case '-': m_kstates[track][2] = -1; - break; - } - break; + // 2 + case 3: quality = "dd"; break; + case 4: quality = "d"; break; + case 5: quality = "m"; break; + case 6: quality = "M"; break; + case 7: quality = "A"; break; + case 8: quality = "AA"; break; - case 'f': case 'F': - switch (text[j+1]) { - case '#': m_kstates[track][3] = +1; - break; - case '-': m_kstates[track][3] = -1; - break; - } - break; + // 3 + case 9: quality = "dd"; break; + case 10: quality = "d"; break; + case 11: quality = "m"; break; + case 12: quality = "M"; break; + case 13: quality = "A"; break; + case 14: quality = "AA"; break; - case 'g': case 'G': - switch (text[j+1]) { - case '#': m_kstates[track][4] = +1; - break; - case '-': m_kstates[track][4] = -1; - break; - } - break; - } - for (int j=0; j<7; j++) { - if (m_kstates[track][j] == 0) { - continue; - } - for (int k=1; k<10; k++) { - m_kstates[track][j+k*7] = m_kstates[track][j]; - } - } - } - } + // 4 + case 15: quality = "dd"; break; + case 16: quality = "d"; break; + case 17: quality = "P"; break; + case 18: quality = "A"; break; + case 19: quality = "AA"; break; - // initialize m_pstates with contents of m_kstates - for (int i=0; i<(int)m_kstates.size(); i++) { - for (int j=0; j<(int)m_kstates[i].size(); j++) { - m_pstates[i][j] = m_kstates[i][j]; - } + case 20: quality = ""; break; + + // 5 + case 21: quality = "dd"; break; + case 22: quality = "d"; break; + case 23: quality = "P"; break; + case 24: quality = "A"; break; + case 25: quality = "AA"; break; + + // 6 + case 26: quality = "dd"; break; + case 27: quality = "d"; break; + case 28: quality = "m"; break; + case 29: quality = "M"; break; + case 30: quality = "A"; break; + case 31: quality = "AA"; break; + + // 7 + case 32: quality = "dd"; break; + case 33: quality = "d"; break; + case 34: quality = "m"; break; + case 35: quality = "M"; break; + case 36: quality = "A"; break; + case 37: quality = "AA"; break; + + default: quality = "?"; break; } + return quality; + } -//////////////////////////////// +////////////////////////////// // -// Tool_gasparize::clearStates -- +// FiguredBassNumber::FiguredBassNumber -- Constructor // -void Tool_gasparize::clearStates(void) { - for (int i=0; i<(int)m_pstates.size(); i++) { - fill(m_pstates[i].begin(), m_pstates[i].end(), 0); - } - for (int i=0; i<(int)m_estates.size(); i++) { - fill(m_estates[i].begin(), m_estates[i].end(), false); - } +FiguredBassNumber::FiguredBassNumber(int num, string accid, bool showAccid, int voiceIdx, int lineIdx, bool isAtk, bool intervallsatz, string intervalQuality, bool hint) { + m_number = num; + m_accidentals = accid; + m_voiceIndex = voiceIdx; + m_lineIndex = lineIdx; + m_showAccidentals = showAccid; + m_isAttack = isAtk; + m_intervallsatz = intervallsatz; + m_intervalQuality = intervalQuality; + m_hint = hint; } + ////////////////////////////// // -// Tool_gasparize::getDate -- +// FiguredBassNumber::toString -- Convert FiguredBassNumber to a string (accidental + number) // -string Tool_gasparize::getDate(void) { - time_t t = time(NULL); - tm* timeptr = localtime(&t); - stringstream ss; - int year = timeptr->tm_year + 1900; - int month = timeptr->tm_mon + 1; - int day = timeptr->tm_mday; - ss << year << "/"; - if (month < 10) { - ss << "0"; +string FiguredBassNumber::toString(bool compoundQ, bool accidentalsQ, bool hideThreeQ) { + int num = (compoundQ) ? getNumberWithinOctave() : m_number; + if (m_hint) { + return m_intervalQuality + to_string(abs(num)); } - ss << month << "/"; - if (day < 10) { - ss << "0"; + string accid = (accidentalsQ && m_showAccidentals) ? m_accidentals : ""; + if (((num == 3) || (num == -3)) && accidentalsQ && m_showAccidentals && hideThreeQ) { + return accid; } - ss << day; - return ss.str(); + if (num > 0) { + return accid + to_string(num); + } + if (num < 0) { + return accid + "~" + to_string(abs(num)); + } + return ""; } ////////////////////////////// // -// Tool_gasparize::fixTies -- -// If a tie is unclosed or if a note is followed by an invisible rest, then fix. -// - -void Tool_gasparize::fixTies(HumdrumFile& infile) { - int strands = infile.getStrandCount(); - for (int i=0; iisKern()) { - continue; - } - HTp send = infile.getStrandEnd(i); - fixTiesForStrand(sstart, send); - } - fixTieStartEnd(infile); -} +// FiguredBassNumber::getNumberWithinOctave -- Get a reasonable figured bass number +// Replace 0 with 7 and -7 +// Replace 1 with 8 and -8 +// Replace 2 with 9 if it is a suspension of the ninth +// Allow 1 (unisono) in intervallsatz +int FiguredBassNumber::getNumberWithinOctave(void) { + int num = m_number % 7; + // Replace 0 with 7 and -7 + if ((abs(m_number) > 0) && (m_number % 7 == 0)) { + return m_number < 0 ? -7 : 7; + } -void Tool_gasparize::fixTieStartEnd(HumdrumFile& infile) { - int strands = infile.getStrandCount(); - for (int i=0; iisKern()) { - continue; + // Replace 1 with 8 and -8 + if (abs(num) == 1) { + // Allow unisono in intervallsatz + if (m_intervallsatz || m_hint) { + if (abs(m_number) == 1) { + return 1; + } } - HTp send = infile.getStrandEnd(i); - fixTiesStartEnd(sstart, send); + return m_number < 0 ? -8 : 8; + } + + // Replace 2 with 9 if m_convert2To9 is true (e.g. when a 3 is included in the chord numbers) + if (m_convert2To9 && (num == 2)) { + return 9; } + + return num; } -void Tool_gasparize::fixTiesStartEnd(HTp starts, HTp ends) { - HTp current = starts; - HumRegex hre; - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if ((current->find('[') != string::npos) && - (current->find(']') != string::npos) && - (current->find(' ') == string::npos)) { - string text = *current; - hre.replaceDestructive(text, "", "\\[", "g"); - hre.replaceDestructive(text, "_", "\\]", "g"); - current->setText(text); - } - current = current->getNextToken(); - } +////////////////////////////// +// +// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor +// Helper class to store the mappings for abbreviate figured bass numbers +// + +FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector n) { + m_str = s; + m_numbers = n; } + ////////////////////////////// // -// Tool_gasparize::fixTiesForStrand -- +// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers // -void Tool_gasparize::fixTiesForStrand(HTp sstart, HTp send) { - if (!sstart) { - return; - } - HTp current = sstart; - HTp last = NULL; - current = current->getNextToken(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (last == NULL) { - last = current; - current = current->getNextToken(); - continue; - } - if (current->find("yy") != string::npos) { - fixTieToInvisibleRest(last, current); - } else if (((last->find("[") != string::npos) || (last->find("_") != string::npos)) - && ((current->find("]") == string::npos) && (current->find("_") == string::npos))) { - fixHangingTie(last, current); - } - last = current; - current = current->getNextToken(); - } -} +const vector FiguredBassAbbreviationMapping::s_mappings = { + FiguredBassAbbreviationMapping("3", {}), + FiguredBassAbbreviationMapping("5", {}), + FiguredBassAbbreviationMapping("5 3", {}), + FiguredBassAbbreviationMapping("6 3", {6}), + FiguredBassAbbreviationMapping("5 4", {4}), + FiguredBassAbbreviationMapping("7 5 3", {7}), + FiguredBassAbbreviationMapping("7 3", {7}), + FiguredBassAbbreviationMapping("7 5", {7}), + FiguredBassAbbreviationMapping("6 5 3", {6, 5}), + FiguredBassAbbreviationMapping("6 4 3", {4, 3}), + FiguredBassAbbreviationMapping("6 4 2", {4, 2}), + FiguredBassAbbreviationMapping("9 5 3", {9}), + FiguredBassAbbreviationMapping("9 5", {9}), + FiguredBassAbbreviationMapping("9 3", {9}), +}; -////////////////////////////// +#define RUNTOOL(NAME, INFILE, COMMAND, STATUS) \ + Tool_##NAME *tool = new Tool_##NAME; \ + tool->process(COMMAND); \ + tool->run(INFILE); \ + if (tool->hasError()) { \ + status = false; \ + tool->getError(cerr); \ + delete tool; \ + break; \ + } else if (tool->hasHumdrumText()) { \ + INFILE.readString(tool->getHumdrumText()); \ + } \ + delete tool; + +#define RUNTOOL2(NAME, INFILE1, INFILE2, COMMAND, STATUS) \ + Tool_##NAME *tool = new Tool_##NAME; \ + tool->process(COMMAND); \ + tool->run(INFILE1, INFILE2); \ + if (tool->hasError()) { \ + status = false; \ + tool->getError(cerr); \ + delete tool; \ + break; \ + } else if (tool->hasHumdrumText()) { \ + INFILE1.readString(tool->getHumdrumText()); \ + } \ + delete tool; + +#define RUNTOOLSET(NAME, INFILES, COMMAND, STATUS) \ + Tool_##NAME *tool = new Tool_##NAME; \ + tool->process(COMMAND); \ + tool->run(INFILES); \ + if (tool->hasError()) { \ + status = false; \ + tool->getError(cerr); \ + delete tool; \ + break; \ + } else if (tool->hasHumdrumText()) { \ + INFILES.readString(tool->getHumdrumText()); \ + } \ + delete tool; + +#define RUNTOOLSTREAM(NAME, INFILES, COMMAND, STATUS) \ + Tool_##NAME *tool = new Tool_##NAME; \ + tool->process(COMMAND); \ + tool->run(INFILES); \ + if (tool->hasError()) { \ + status = false; \ + tool->getError(cerr); \ + delete tool; \ + break; \ + } else if (tool->hasHumdrumText()) { \ + INFILES.readString(tool->getHumdrumText()); \ + } \ + delete tool; + + + +//////////////////////////////// // -// Tool_gasparize::fixTieToInvisibleRest -- +// Tool_filter::Tool_filter -- Set the recognized options for the tool. // -void Tool_gasparize::fixTieToInvisibleRest(HTp first, HTp second) { - if (second->find("yy") == string::npos) { - return; - } - if ((first->find("[") == string::npos) && (first->find("_") == string::npos)) { - string ftext = *first; - ftext = "[" + ftext; - first->setText(ftext); - } - HumRegex hre; - if (!hre.search(first, "([A-Ga-g#n-]+)")) { - return; - } - string pitch = hre.getMatch(1); - pitch += "]"; - string text = *second; - hre.replaceDestructive(text, pitch, "ryy"); - second->setText(text); +Tool_filter::Tool_filter(void) { + define("debug=b", "print debug statement"); + define("v|variant=s:", "Run filters labeled with the given variant"); } -////////////////////////////// +///////////////////////////////// // -// Tool_gasparize::fixHangingTie -- Not dealing with chain of missing ties. +// Tool_filter::run -- Primary interfaces to the tool. // -void Tool_gasparize::fixHangingTie(HTp first, HTp second) { - string text = *second; - text += "]"; - second->setText(text); +bool Tool_filter::run(const string& indata) { + HumdrumFileSet infiles(indata); + bool status = run(infiles); + return status; } +bool Tool_filter::run(HumdrumFile& infile) { + HumdrumFileSet infiles; + infiles.appendHumdrumPointer(&infile); + bool status = run(infiles); + infiles.clearNoFree(); + return status; +} -////////////////////////////// -// -// Tool_gasparize::addMensurations -- Add mensurations. -// +bool Tool_filter::runUniversal(HumdrumFileSet& infiles) { + bool status = true; + vector > commands; + getUniversalCommandList(commands, infiles); -void Tool_gasparize::addMensurations(HumdrumFile& infile) { - HumRegex hre; - for (int i=infile.getLineCount() - 1; i>=0; i--) { - if (!infile[i].isInterpretation()) { - continue; - } - for (int j=0; jfind("met") != string::npos) { - return; + + initialize(infiles[0]); + + HumdrumFile& infile = infiles[0]; + + #ifdef __EMSCRIPTEN__ + bool optionList = getBoolean("options"); + if (optionList) { + printEmscripten(m_humdrum_text); + m_humdrum_text << infile; } - int fieldcount = infile[index].getFieldCount(); - string line = "*"; - HTp token = infile[index].token(0); - if (token->isKern()) { - if (top == 2) { - line += "met(C|)"; + #endif + + bool status = true; + vector > commands; + getCommandList(commands, infile); + for (int i=0; i<(int)commands.size(); i++) { + if (commands[i].first == "addic") { + RUNTOOL(addic, infile, commands[i].second, status); + } else if (commands[i].first == "addkey") { + RUNTOOL(addkey, infile, commands[i].second, status); + } else if (commands[i].first == "addlabels") { + RUNTOOL(addlabels, infile, commands[i].second, status); + } else if (commands[i].first == "addtempo") { + RUNTOOL(addtempo, infile, commands[i].second, status); + } else if (commands[i].first == "autoaccid") { + RUNTOOL(autoaccid, infile, commands[i].second, status); + } else if (commands[i].first == "autobeam") { + RUNTOOL(autobeam, infile, commands[i].second, status); + } else if (commands[i].first == "autostem") { + RUNTOOL(autostem, infile, commands[i].second, status); + } else if (commands[i].first == "binroll") { + RUNTOOL(binroll, infile, commands[i].second, status); + } else if (commands[i].first == "chantize") { + RUNTOOL(chantize, infile, commands[i].second, status); + } else if (commands[i].first == "chint") { + RUNTOOL(chint, infile, commands[i].second, status); + } else if (commands[i].first == "chord") { + RUNTOOL(chord, infile, commands[i].second, status); + } else if (commands[i].first == "cint") { + RUNTOOL(cint, infile, commands[i].second, status); + } else if (commands[i].first == "cmr") { + RUNTOOL(cmr, infile, commands[i].second, status); + } else if (commands[i].first == "composite") { + RUNTOOL(composite, infile, commands[i].second, status); + } else if (commands[i].first == "dissonant") { + RUNTOOL(dissonant, infile, commands[i].second, status); + } else if (commands[i].first == "double") { + RUNTOOL(double, infile, commands[i].second, status); + } else if (commands[i].first == "fb") { + RUNTOOL(fb, infile, commands[i].second, status); + } else if (commands[i].first == "flipper") { + RUNTOOL(flipper, infile, commands[i].second, status); + } else if (commands[i].first == "filter") { + RUNTOOL(filter, infile, commands[i].second, status); + } else if (commands[i].first == "gasparize") { + RUNTOOL(gasparize, infile, commands[i].second, status); + } else if (commands[i].first == "half") { + RUNTOOL(half, infile, commands[i].second, status); + } else if (commands[i].first == "hands") { + RUNTOOL(hands, infile, commands[i].second, status); + } else if (commands[i].first == "homorhythm") { + RUNTOOL(homorhythm, infile, commands[i].second, status); + } else if (commands[i].first == "homorhythm2") { + RUNTOOL(homorhythm2, infile, commands[i].second, status); + } else if (commands[i].first == "hproof") { + RUNTOOL(hproof, infile, commands[i].second, status); + } else if (commands[i].first == "humbreak") { + RUNTOOL(humbreak, infile, commands[i].second, status); + } else if (commands[i].first == "humsheet") { + RUNTOOL(humsheet, infile, commands[i].second, status); + } else if (commands[i].first == "humtr") { + RUNTOOL(humtr, infile, commands[i].second, status); + } else if (commands[i].first == "imitation") { + RUNTOOL(imitation, infile, commands[i].second, status); + } else if (commands[i].first == "instinfo") { + RUNTOOL(instinfo, infile, commands[i].second, status); + } else if (commands[i].first == "kern2mens") { + RUNTOOL(kern2mens, infile, commands[i].second, status); + } else if (commands[i].first == "kernify") { + RUNTOOL(kernify, infile, commands[i].second, status); + } else if (commands[i].first == "kernview") { + RUNTOOL(kernview, infile, commands[i].second, status); + } else if (commands[i].first == "melisma") { + RUNTOOL(melisma, infile, commands[i].second, status); + } else if (commands[i].first == "mens2kern") { + RUNTOOL(mens2kern, infile, commands[i].second, status); + } else if (commands[i].first == "meter") { + RUNTOOL(meter, infile, commands[i].second, status); + } else if (commands[i].first == "metlev") { + RUNTOOL(metlev, infile, commands[i].second, status); + } else if (commands[i].first == "modori") { + RUNTOOL(modori, infile, commands[i].second, status); + } else if (commands[i].first == "msearch") { + RUNTOOL(msearch, infile, commands[i].second, status); + } else if (commands[i].first == "nproof") { + RUNTOOL(nproof, infile, commands[i].second, status); + } else if (commands[i].first == "ordergps") { + RUNTOOL(ordergps, infile, commands[i].second, status); + } else if (commands[i].first == "pbar") { + RUNTOOL(pbar, infile, commands[i].second, status); + } else if (commands[i].first == "phrase") { + RUNTOOL(phrase, infile, commands[i].second, status); + } else if (commands[i].first == "pline") { + RUNTOOL(pline, infile, commands[i].second, status); + } else if (commands[i].first == "prange") { + RUNTOOL(prange, infile, commands[i].second, status); + } else if (commands[i].first == "recip") { + RUNTOOL(recip, infile, commands[i].second, status); + } else if (commands[i].first == "restfill") { + RUNTOOL(restfill, infile, commands[i].second, status); + } else if (commands[i].first == "rphrase") { + RUNTOOL(rphrase, infile, commands[i].second, status); + } else if (commands[i].first == "sab2gs") { + RUNTOOL(sab2gs, infile, commands[i].second, status); + } else if (commands[i].first == "scordatura") { + RUNTOOL(scordatura, infile, commands[i].second, status); + } else if (commands[i].first == "semitones") { + RUNTOOL(semitones, infile, commands[i].second, status); + } else if (commands[i].first == "shed") { + RUNTOOL(shed, infile, commands[i].second, status); + } else if (commands[i].first == "sic") { + RUNTOOL(sic, infile, commands[i].second, status); + } else if (commands[i].first == "simat") { + RUNTOOL2(simat, infile, infile, commands[i].second, status); + } else if (commands[i].first == "slurcheck") { + RUNTOOL(slurcheck, infile, commands[i].second, status); + } else if (commands[i].first == "slur") { + RUNTOOL(slurcheck, infile, commands[i].second, status); + } else if (commands[i].first == "spinetrace") { + RUNTOOL(spinetrace, infile, commands[i].second, status); + } else if (commands[i].first == "strophe") { + RUNTOOL(strophe, infile, commands[i].second, status); + } else if (commands[i].first == "synco") { + RUNTOOL(synco, infile, commands[i].second, status); + } else if (commands[i].first == "tabber") { + RUNTOOL(tabber, infile, commands[i].second, status); + } else if (commands[i].first == "tandeminfo") { + RUNTOOL(tandeminfo, infile, commands[i].second, status); + } else if (commands[i].first == "tassoize") { + RUNTOOL(tassoize, infile, commands[i].second, status); + } else if (commands[i].first == "tassoise") { + RUNTOOL(tassoize, infile, commands[i].second, status); + } else if (commands[i].first == "tasso") { + RUNTOOL(tassoize, infile, commands[i].second, status); + } else if (commands[i].first == "textdur") { + RUNTOOL(textdur, infile, commands[i].second, status); + } else if (commands[i].first == "tie") { + RUNTOOL(tie, infile, commands[i].second, status); + } else if (commands[i].first == "tspos") { + RUNTOOL(tspos, infile, commands[i].second, status); + } else if (commands[i].first == "transpose") { + RUNTOOL(transpose, infile, commands[i].second, status); + } else if (commands[i].first == "tremolo") { + RUNTOOL(tremolo, infile, commands[i].second, status); + } else if (commands[i].first == "trillspell") { + RUNTOOL(trillspell, infile, commands[i].second, status); + } else if (commands[i].first == "vcross") { + RUNTOOL(vcross, infile, commands[i].second, status); + + // filters with aliases: + + } else if (commands[i].first == "colortriads") { + RUNTOOL(colortriads, infile, commands[i].second, status); + } else if (commands[i].first == "colourtriads") { + // British spelling + RUNTOOL(colortriads, infile, commands[i].second, status); + + } else if (commands[i].first == "colorthirds") { + RUNTOOL(tspos, infile, commands[i].second, status); + } else if (commands[i].first == "colourthirds") { + // British spelling + RUNTOOL(tspos, infile, commands[i].second, status); + + } else if (commands[i].first == "colorgroups") { + RUNTOOL(colorgroups, infile, commands[i].second, status); + } else if (commands[i].first == "colourgroups") { // British spelling + RUNTOOL(colorgroups, infile, commands[i].second, status); + + } else if (commands[i].first == "deg") { // humlib version of Humdrum Toolkit deg tool + RUNTOOL(deg, infile, commands[i].second, status); + } else if (commands[i].first == "degx") { // humlib cli name + RUNTOOL(deg, infile, commands[i].second, status); + + } else if (commands[i].first == "extract") { // humlib version of Humdrum Toolkit extract tool + RUNTOOL(extract, infile, commands[i].second, status); + } else if (commands[i].first == "extractx") { // humlib cli name + RUNTOOL(extract, infile, commands[i].second, status); + + } else if (commands[i].first == "grep") { + RUNTOOL(grep, infile, commands[i].second, status); + } else if (commands[i].first == "humgrep") { + RUNTOOL(grep, infile, commands[i].second, status); + + } else if (commands[i].first == "myank") { // humlib version of Humdrum Extras myank tool + RUNTOOL(myank, infile, commands[i].second, status); + } else if (commands[i].first == "myankx") { // humlib cli name + RUNTOOL(myank, infile, commands[i].second, status); + + } else if (commands[i].first == "rid") { // humlib version of Humdrum Toolkit deg tool + RUNTOOL(rid, infile, commands[i].second, status); + } else if (commands[i].first == "ridx") { // Humdrum Extra cli name + RUNTOOL(rid, infile, commands[i].second, status); + } else if (commands[i].first == "ridxx") { // humlib cli name + RUNTOOL(rid, infile, commands[i].second, status); + + } else if (commands[i].first == "satb2gs") { // humlib version of Humdrum Extras satg2gs tool + RUNTOOL(satb2gs, infile, commands[i].second, status); + } else if (commands[i].first == "satb2gsx") { // humlib cli name + RUNTOOL(satb2gs, infile, commands[i].second, status); + + } else if (commands[i].first == "thru") { // humlib version of Humdrum Toolkit thru tool + RUNTOOL(thru, infile, commands[i].second, status); + } else if (commands[i].first == "thrux") { // Humdrum Extras cli name + RUNTOOL(thru, infile, commands[i].second, status); + } else if (commands[i].first == "thruxx") { // humlib cli name + RUNTOOL(thru, infile, commands[i].second, status); + + } else if (commands[i].first == "timebase") { // humlib version of Humdrum Toolkit timebase tool + RUNTOOL(timebase, infile, commands[i].second, status); + } else if (commands[i].first == "timebasex") { // humlib cli name + RUNTOOL(timebase, infile, commands[i].second, status); } else { - line += "met(O)"; - } - } - for (int i=1; iisKern()) { - if (top == 2) { - line += "met(C|)"; - } else { - line += "met(O)"; - } + cerr << "UNKNOWN FILTER: " << commands[i].first << " OPTIONS: " << commands[i].second << endl; } + } - infile.insertLine(index+1, line); + + removeGlobalFilterLines(infile); + + // Re-load the text for each line from their tokens in case any + // updates are needed from token changes. + infile.createLinesFromTokens(); + return status; } -/////////////////////////////// + +////////////////////////////// // -// Tool_gasparize::createEditText -- Convert markers into *edit interps. +// Tool_filter::removeGlobalFilterLines -- // -void Tool_gasparize::createEditText(HumdrumFile& infile) { - // previous process manipulated the structure so reanalyze here for now: - infile.analyzeBaseFromTokens(); - infile.analyzeStructureNoRhythm(); +void Tool_filter::removeGlobalFilterLines(HumdrumFile& infile) { + HumRegex hre; + string text; - int strands = infile.getStrandCount(); - for (int i=0; iisDataType("**text")) { + string maintag = "!!!filter:"; + string mainXtag = "!!!Xfilter:"; + string maintagQuery = "^!!!filter:"; + + string maintagV; + string mainXtagV; + string maintagQueryV; + + if (m_variant.size() > 0) { + maintagV = "!!!filter-" + m_variant + ":"; + mainXtagV = "!!!Xfilter-" + m_variant + ":"; + maintagQueryV = "^!!!filter-" + m_variant + ":"; + } + + for (int i=0; i 0) { + if (infile.token(i, 0)->compare(0, maintagV.size(), maintagV) == 0) { + text = infile.token(i, 0)->getText(); + hre.replaceDestructive(text, mainXtagV, maintagQueryV); + infile.token(i, 0)->setText(text); + } + } else { + if (infile.token(i, 0)->compare(0, maintag.size(), maintag) == 0) { + text = infile.token(i, 0)->getText(); + hre.replaceDestructive(text, mainXtag, maintagQuery); + infile.token(i, 0)->setText(text); + } } } } + ////////////////////////////// // -// Tool_gasparize::addEditStylingForText -- +// Tool_filter::removeUniversalFilterLines -- // -bool Tool_gasparize::addEditStylingForText(HumdrumFile& infile, HTp sstart, HTp send) { - HTp current = send->getPreviousToken(); - bool output = false; - string state = ""; - string laststate = ""; +void Tool_filter::removeUniversalFilterLines(HumdrumFileSet& infiles) { HumRegex hre; - HTp lastdata = NULL; - bool italicQ = false; - while (current && (current != sstart)) { - if (!current->isData()) { - current = current->getPreviousToken(); - continue; - } - if (current->isNull()) { - current = current->getPreviousToken(); - continue; - } - italicQ = false; - string text = current->getText(); - if (text.find("") != string::npos) { - italicQ = true; - hre.replaceDestructive(text, "", "", "g"); - hre.replaceDestructive(text, "", "", "g"); - current->setText(text); - } else { -} - if (laststate == "") { - if (italicQ) { - laststate = "italic"; - } else { - laststate = "regular"; - } - current = current->getPreviousToken(); - continue; - } else { - if (italicQ) { - state = "italic"; - } else { - state = "regular"; + string text; + + string maintag = "!!!!filter:"; + string mainXtag = "!!!!Xfilter:"; + string maintagQuery = "^!!!!filter:"; + + string maintagV; + string mainXtagV; + string maintagQueryV; + + if (m_variant.size() > 0) { + maintagV = "!!!!filter-" + m_variant + ":"; + mainXtagV = "!!!!Xfilter-" + m_variant + ":"; + maintagQueryV = "^!!!!filter-" + m_variant + ":"; + } + + for (int i=0; igetLineIndex() - 1, lastdata->getFieldIndex())) { - string line = getEditLine("*edit", lastdata->getFieldIndex(), lastdata->getOwner()); - infile.insertLine(lastdata->getLineIndex(), line); + HTp token = infile.token(j, 0); + if (m_variant.size() > 0) { + if (token->compare(0, maintagV.size(), maintagV) == 0) { + text = token->getText(); + hre.replaceDestructive(text, mainXtagV, maintagQueryV); + token->setText(text); + infile[j].createLineFromTokens(); } - } else if (lastdata && (laststate == "regular")) { - output = true; - if (!insertEditText("*Xedit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) { - string line = getEditLine("*Xedit", lastdata->getFieldIndex(), lastdata->getOwner()); - infile.insertLine(lastdata->getLineIndex(), line); + } else { + if (token->compare(0, maintag.size(), maintag) == 0) { + text = token->getText(); + hre.replaceDestructive(text, mainXtag, maintagQuery); + token->setText(text); + infile[j].createLineFromTokens(); } } } - laststate = state; - lastdata = current; - current = current->getPreviousToken(); - } - - if (lastdata && italicQ) { - // add *edit before first syllable in **text. - output = true; - if (!insertEditText("*edit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) { - string line = getEditLine("*edit", lastdata->getFieldIndex(), lastdata->getOwner()); - infile.insertLine(lastdata->getLineIndex(), line); - } } - - return output; } ////////////////////////////// // -// Tool_gasparize::insertEditText -- +// Tool_filter::getCommandList -- // -bool Tool_gasparize::insertEditText(const string& text, HumdrumFile& infile, int line, int field) { - if (!infile[line].isInterpretation()) { - return false; +void Tool_filter::getCommandList(vector >& commands, + HumdrumFile& infile) { + + vector refs = infile.getReferenceRecords(); + pair entry; + string tag = "filter"; + if (m_variant.size() > 0) { + tag += "-"; + tag += m_variant; } - HTp token; - for (int i=0; iisNull()) { + vector clist; + HumRegex hre; + for (int i=0; i<(int)refs.size(); i++) { + string refkey = refs[i]->getGlobalReferenceKey(); + if (refkey != tag) { continue; } - if (token->find("edit") != string::npos) { - break; - } - return false; - } - token = infile.token(line, field); - token->setText(text); - - return true; -} - - - -///////////////////// -// -// Tool_gasparize::getEditLine -- -// - -string Tool_gasparize::getEditLine(const string& text, int fieldindex, HLp line) { - string output; - for (int i=0; igetFieldCount()) { - output += "\t"; - } - } - output += text; - if (fieldindex < line->getFieldCount()) { - output += "\t"; - } - for (int i=fieldindex+1; igetFieldCount(); i++) { - output += "*"; - if (i < line->getFieldCount()) { - output += "\t"; + string command = refs[i]->getGlobalReferenceValue(); + splitPipeline(clist, command); + for (int j=0; j<(int)clist.size(); j++) { + if (hre.search(clist[j], "^\\s*([^\\s]+)")) { + entry.first = hre.getMatch(1); + entry.second = clist[j]; + commands.push_back(entry); + } } } - return output; } ////////////////////////////// // -// adjustIntrumentNames -- +// Tool_filter::splitPipeline -- // -void Tool_gasparize::adjustIntrumentNames(HumdrumFile& infile) { - int instrumentLine = -1; - int abbrLine = -1; - for (int i=0; icompare(0, 3, "*I\"") == 0) { - instrumentLine = i; - } - if (token->compare(0, 3, "*I'") == 0) { - abbrLine = i; +void Tool_filter::splitPipeline(vector& clist, const string& command) { + clist.clear(); + clist.resize(1); + clist[0] = ""; + int inDoubleQuotes = -1; + int inSingleQuotes = -1; + char ch = '\0'; + char lastch; + for (int i=0; i<(int)command.size(); i++) { + lastch = ch; + ch = command[i]; + + if (ch == '"') { + if (lastch == '\\') { + // escaped double quote, so treat as regular character + clist.back() += ch; + continue; + } else if (inDoubleQuotes >= 0) { + // turn off previous double quote sequence + clist.back() += ch; + inDoubleQuotes = -1; + continue; + } else if (inSingleQuotes >= 0) { + // in an active single quote, so this is not a closing double quote + clist.back() += ch; + continue; + } else { + // this is the start of a double quote sequence + clist.back() += ch; + inDoubleQuotes = i; + continue; } } - } - if (instrumentLine < 0) { - return; - } - for (int i=0; isetText("*I\"Contratenor 1"); - } else if (*token == "*I\"CTI") { - token->setText("*I\"Contratenor 1"); - } else if (*token == "*I\"CTII") { - token->setText("*I\"Contratenor 2"); - } else if (*token == "*I\"CT II") { - token->setText("*I\"Contratenor 2"); - } else if (*token == "*I\"CT") { - token->setText("*I\"Contratenor"); - } else if (*token == "*I\"S") { - token->setText("*I\"Superius"); - } else if (*token == "*I\"A") { - token->setText("*I\"Altus"); - } else if (*token == "*I\"T") { - token->setText("*I\"Tenor"); - } else if (*token == "*I\"B") { - token->setText("*I\"Bassus"); - } else if (*token == "*I\"V") { - token->setText("*I\"Quintus"); - } else if (*token == "*I\"VI") { - token->setText("*I\"Sextus"); - } - } - if (abbrLine >= 0) { - return; - } - string abbr; - HumRegex hre; - for (int i=0; i=0; i--) { - if (!infile[i].isInterpretation()) { - continue; - } - for (int j=0; jisKern()) { + } else if (inSingleQuotes >= 0) { + // turn off previous single quote sequence + clist.back() += ch; + inSingleQuotes = -1; + continue; + } else if (inDoubleQuotes >= 0) { + // in an active double quote, so this is not a closing single quote + clist.back() += ch; + continue; + } else { + // this is the start of a single quote sequence + clist.back() += ch; + inSingleQuotes = i; continue; - } - if (hre.search(token, "^\\*[A-Ga-g][#n-]*:$")) { - // suppress the key desingation - infile.deleteLine(i); - break; } } - } - -} - - -////////////////////////////// -// -// Tool_gasparize::fixBarlines -- Add final double barline and convert -// any intermediate final barlines to double barlines. -// - -void Tool_gasparize::fixBarlines(HumdrumFile& infile) { - fixFinalBarline(infile); - HumRegex hre; - for (int i=0; ifind("==") == string::npos) { + if (ch == '|') { + if ((inSingleQuotes > -1) || (inDoubleQuotes > -1)) { + // pipe character + clist.back() += ch; + continue; + } else { + // this is a real pipe + clist.resize(clist.size() + 1); continue; } - if (hre.search(token, "^==(\\d*)")) { - string text = "="; - text += hre.getMatch(1); - text += "||"; - token->setText(text); + } + + if (isspace(ch) && (!(inSingleQuotes > -1)) && (!(inDoubleQuotes > -1))) { + if (isspace(lastch)) { + // don't repeat spaces outside of quotes. + continue; } } + + // regular character + clist.back() += ch; } + + // remove leading and trailing spaces + HumRegex hre; + for (int i=0; i<(int)clist.size(); i++) { + hre.replaceDestructive(clist[i], "", "^\\s+"); + hre.replaceDestructive(clist[i], "", "\\s+$"); + } + } ////////////////////////////// // -// Tool_gasparize::fixFinalBarline -- +// Tool_filter::getUniversalCommandList -- // -void Tool_gasparize::fixFinalBarline(HumdrumFile& infile) { - for (int i=infile.getLineCount() - 1; i>=0; i--) { - if (infile[i].isData()) { - break; - } - if (!infile[i].isBarline()) { +void Tool_filter::getUniversalCommandList(vector >& commands, + HumdrumFileSet& infiles) { + + vector refs = infiles.getUniversalReferenceRecords(); + pair entry; + string tag = "filter"; + if (m_variant.size() > 0) { + tag += "-"; + tag += m_variant; + } + vector clist; + HumRegex hre; + for (int i=0; i<(int)refs.size(); i++) { + if (refs[i]->getUniversalReferenceKey() != tag) { continue; } - for (int j=0; jsetText("=="); + string command = refs[i]->getUniversalReferenceValue(); + hre.split(clist, command, "\\s*\\|\\s*"); + for (int j=0; j<(int)clist.size(); j++) { + if (hre.search(clist[j], "^\\s*([^\\s]+)")) { + entry.first = hre.getMatch(1); + entry.second = clist[j]; + commands.push_back(entry); } } } @@ -85802,76 +86284,16 @@ void Tool_gasparize::fixFinalBarline(HumdrumFile& infile) { ////////////////////////////// // -// Tool_gasparize::createJEditorialAccidentals -- -// convert -// !LO:TX:a:t=( ) -// 4F# +// Tool_filter::initialize -- extract time signature lines for +// each **kern spine in file. // -void Tool_gasparize::createJEditorialAccidentals(HumdrumFile& infile) { - int strands = infile.getStrandCount(); - for (int i=0; iisKern()) { - continue; - } - HTp send = infile.getStrandEnd(i); - createJEditorialAccidentals(sstart, send); - } -} - -void Tool_gasparize::createJEditorialAccidentals(HTp sstart, HTp send) { - HTp current = sstart->getNextToken(); - HumRegex hre; - while (current && (current != send)) { - if (!current->isCommentLocal()) { - current = current->getNextToken(); - continue; - } - if (hre.search(current, "^!LO:TX:a:t=\\(\\s*\\)$")) { - current->setText("!"); - convertNextNoteToJAccidental(current); - } - current = current->getNextToken(); - } -} - -void Tool_gasparize::convertNextNoteToJAccidental(HTp current) { - current = current->getNextToken(); - HumRegex hre; - while (current) { - if (!current->isData()) { - // Does not handle LO for non-data. - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - break; - } - if (current->isRest()) { - break; - } - string text = *current; - if (hre.search(text, "i")) { - hre.replaceDestructive(text, "j", "i"); - current->setText(text); - break; - } else if (hre.search(text, "[-#n]")) { - hre.replaceDestructive(text, "$1j", "(.*[-#n]+)"); - current->setText(text); - break; - } else { - // Need to add a natural sign as well. - hre.replaceDestructive(text, "$1nj", "(.*[A-Ga-g]+)"); - current->setText(text); - break; - } - break; +void Tool_filter::initialize(HumdrumFile& infile) { + m_debugQ = getBoolean("debug"); + m_variant.clear(); + if (getBoolean("variant")) { + m_variant = getString("variant"); } - current = current->getNextToken(); } @@ -85880,21 +86302,21 @@ void Tool_gasparize::convertNextNoteToJAccidental(HTp current) { ///////////////////////////////// // -// Tool_grep::Tool_grep -- Set the recognized options for the tool. +// Tool_fixps::Tool_fixps -- Set the recognized options for the tool. // -Tool_grep::Tool_grep(void) { - define("v|remove-matching-lines=b", "remove lines that match regex"); - define("e|regex|regular-expression=s", "regular expression to search with"); +Tool_fixps::Tool_fixps(void) { + // define ("n|only-remove-empty-transpositions=b", "Only remove empty transpositions"); } + ///////////////////////////////// // -// Tool_grep::run -- Do the main work of the tool. +// Tool_fixps::run -- Primary interfaces to the tool. // -bool Tool_grep::run(HumdrumFileSet& infiles) { +bool Tool_fixps::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i> newlist; + removeEmpties(newlist, infile); + outputNewSpining(newlist, infile); } ////////////////////////////// // -// Tool_grep::processFile -- +// Tool_fixps::outputNewSpining -- // -void Tool_grep::processFile(HumdrumFile& infile) { - HumRegex hre; - bool match; +void Tool_fixps::outputNewSpining(vector>& newlist, HumdrumFile& infile) { for (int i=0; i 0) && (!newlist[i].empty()) && newlist[i][0]->isCommentLocal()) { + if (!newlist[i-1].empty() && newlist[i-1][0]->isCommentLocal()) { + if (newlist[i].size() == newlist[i-1].size()) { + bool same = true; + for (int j=0; j<(int)newlist[i].size(); j++) { + if (*(newlist[i][j]) != *(newlist[i-1][j])) { +cerr << "GOT HERE " << i << " " << j << endl; +cerr << infile[i-1] << endl; +cerr << infile[i] << endl; +cerr << endl; + same = false; + break; + } + } + if (same) { + continue; + } + } + } + } + if (!infile[i].isManipulator()) { + m_humdrum_text << newlist[i].at(0); + for (int j=1; j<(int)newlist[i].size(); j++) { + m_humdrum_text << "\t"; + m_humdrum_text << newlist[i].at(j); + } + m_humdrum_text << endl; + continue; + } + if ((i > 0) && !infile[i-1].isManipulator()) { + printNewManipulator(infile, newlist, i); } - m_humdrum_text << infile[i] << "\n"; - } -} - - - - -///////////////////////////////// -// -// Tool_half::Tool_half -- Set the recognized options for the tool. -// - -Tool_half::Tool_half(void) { - define("l|lyric-beam-break=b", "Break beams at syllable starts"); -} - - - -///////////////////////////////// -// -// Tool_half::run -- Primary interfaces to the tool. -// - -bool Tool_half::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i>& newlist, int line) { + HTp token = infile.token(line, 0); + if (*token == "*-") { + m_humdrum_text << infile[line] << endl; + return; + } + if (token->compare(0, 2, "**") == 0) { + m_humdrum_text << infile[line] << endl; + return; + } + m_humdrum_text << "++++++++++++++++++++" << endl; } - - ////////////////////////////// // -// Tool_half::adjustBeams -- +// Tool_fixps::removeDuplicateDynamics -- // -void Tool_half::adjustBeams(HumdrumFile& infile) { - Tool_autobeam autobeam; - vector argv; - argv.push_back("autobeam"); - if (m_lyricBreakQ) { - argv.push_back("-l"); +void Tool_fixps::removeDuplicateDynamics(HumdrumFile& infile) { + int scount = infile.getStrandCount(); + for (int i=0; iisDataType("**dynam")) { + continue; + } + HTp send = infile.getStrandEnd(i); + HTp current = sstart; + while (current && (current != send)) { + vector subtoks = current->getSubtokens(); + if (subtoks.size() % 2 == 1) { + current = current->getNextToken(); + continue; + } + bool equal = true; + int half = (int)subtoks.size() / 2; + for (int j=0; jsetText(newtext); + } + } } - autobeam.process(argv); - autobeam.run(infile); } ////////////////////////////// // -// Tool_half::halfRhythms -- +// Tool_fixps::removeEmpties -- // -void Tool_half::halfRhythms(HumdrumFile& infile) { - HumRegex hre; +void Tool_fixps::removeEmpties(vector>& newlist, HumdrumFile& infile) { + newlist.resize(infile.getLineCount()); for (int i=0; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - - string text = *token; - // extract duration without dot - HumNum durnodot = Convert::recipToDurationNoDots(text); - durnodot /= 2; - string newrhythm = Convert::durationToRecip(durnodot); - hre.replaceDestructive(text, newrhythm, "\\d+%?\\d*"); - token->setText(text); - } - } else if (infile[i].isInterpretation()) { - // half time signatures - for (int j=0; jsetText(text); - } else { - string text = *token; - string replacement = "/" + to_string(bot1); - replacement += "%" + to_string(bot2); - hre.replaceDestructive(text, replacement, "/\\d+"); - token->setText(text); - } - } else if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) { - int bot = hre.getMatchInt(2); - if (bot == 4) { - bot = 8; - } else if (bot == 2) { - bot = 4; - } else if (bot == 3) { - bot = 6; - } else if (bot == 1) { - bot = 2; - } else if (bot == 0) { - bot = 1; - } else { - cerr << "Warning: ignored time signature: " << token << endl; - } - string text = *token; - string replacement = "/" + to_string(bot); - hre.replaceDestructive(text, replacement, "/\\d+"); - token->setText(text); - } + if (!infile[i].hasSpines()) { + continue; + } + if (infile[i].isManipulator()) { + continue; + } + for (int j=0; jgetValue("delete"); + if (value == "true") { + continue; } + newlist[i].push_back(token); } } } @@ -86149,69 +86508,111 @@ void Tool_half::halfRhythms(HumdrumFile& infile) { ////////////////////////////// // -// Tool_half::terminalLongToTerminalBreve -- +// Tool_fixps::markEmptyVoices -- // -void Tool_half::terminalLongToTerminalBreve(HumdrumFile& infile) { - HumRegex hre; +void Tool_fixps::markEmptyVoices(HumdrumFile& infile) { + HLp barline = NULL; for (int i=0; ifind("terminal long") == string::npos) { + if (infile[i].isManipulator()) { continue; } - string text = *token; - hre.replaceDestructive(text, "terminal breve", "terminal long", "g"); - token->setText(text); + if (infile[i].isInterpretation()) { + if (infile.token(i, 0)->compare(0, 2, "**")) { + barline = &infile[i]; + } + continue; + } + if (infile[i].isBarline()) { + barline = &infile[i]; + } + if (!infile[i].isData()) { + continue; + } + if (!barline) { + continue; + } + // check on the data line if: + // * it is in the first subspine + // * it is an invisible rest + // * it takes the full duration of the measure + // If so, then mark the tokens for deletion in that layer. + for (int j=0; jgetTrack(); + int subtrack = token->getSubtrack(); + if (subtrack != 1) { + continue; + } + if (token->find("yy") == string::npos) { + continue; + } + if (!token->isRest()) { + continue; + } + HumNum duration = token->getDuration(); + HumNum bardur = token->getDurationToBarline(); + HTp current = token; + while (current) { + subtrack = current->getSubtrack(); + if (subtrack != 1) { + break; + } + current->setValue("delete", "true"); + if (current->isBarline()) { + break; + } + current = current->getNextToken(); + } + current = token; + current = current->getPreviousToken(); + while (current) { + if (current->isManipulator()) { + break; + } + if (current->isBarline()) { + break; + } + subtrack = current->getSubtrack(); + if (subtrack != 1) { + break; + } + current->setValue("delete", "true"); + current = current->getPreviousToken(); + } + } } -} - - +} -///////////////////////////////// -// -// Tool_hands::Tool_hands -- Set the recognized options for the tool. -// -Tool_hands::Tool_hands(void) { - define("c|color=b", "color right-hand notes red and left-hand notes blue"); - define("lcolor|left-color=s:dodgerblue", "color of left-hand notes"); - define("rcolor|right-color=s:crimson", "color of right-hand notes"); - define("l|left-only=b", "remove right-hand notes"); - define("r|right-only=b", "remove left-hand notes"); - define("m|mark=b", "mark left and right-hand notes"); - define("a|attacks-only=b", "only mark note attacks and not note sustains"); -} -////////////////////////////// +///////////////////////////////// // -// Tool_hands::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_flipper::Tool_flipper -- Set the recognized options for the tool. // -void Tool_hands::initialize(void) { - m_colorQ = getBoolean("color"); - m_leftColor = getString("left-color"); - m_rightColor = getString("right-color"); - m_leftOnlyQ = getBoolean("left-only"); - m_rightOnlyQ = getBoolean("right-only"); - m_markQ = getBoolean("mark"); - m_attacksOnlyQ = getBoolean("attacks-only"); +Tool_flipper::Tool_flipper(void) { + define("k|keep=b", "keep *flip/*Xflip instructions"); + define("a|all=b", "flip globally, not just inside *flip/*Xflip regions"); + define("s|strophe=b", "flip inside of strophes as well"); + define("S|strophe-only|only-strophe=b", "flip only inside of strophes as well"); + define("i|interp=s:kern", "flip only in this interpretation"); } ///////////////////////////////// // -// Tool_hands::run -- Do the main work of the tool. +// Tool_flipper::run -- Do the main work of the tool. // -bool Tool_hands::run(HumdrumFileSet& infiles) { +bool Tool_flipper::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; igetExclusiveInterpretation(); - int hasHandMarkup = xtok->getValueInt("auto", "hand"); - if (!hasHandMarkup) { - continue; - } - HTp send = infile.getStrandEnd(i); - removeNotes(sstart, send, htype); - counter++; - } +void Tool_flipper::processFile(HumdrumFile& infile) { - - if (counter) { - infile.createLinesFromTokens(); + m_fliplines.resize(infile.getLineCount()); + fill(m_fliplines.begin(), m_fliplines.end(), false); + + m_flipState.resize(infile.getMaxTrack()+1); + if (m_allQ) { + fill(m_flipState.begin(), m_flipState.end(), true); + } else { + fill(m_flipState.begin(), m_flipState.end(), false); } -} + m_strophe.resize(infile.getMaxTrack()+1); + fill(m_strophe.begin(), m_strophe.end(), false); -void Tool_hands::removeNotes(HTp sstart, HTp send, const string& htype) { - HTp current = sstart; - while (current && (current != send)) { - if (!current->isData() || current->isNull()) { - current = current->getNextToken(); - continue; + for (int i=0; igetValue("auto", "hand"); - if (ttype != htype) { - current = current->getNextToken(); - continue; - } - string text = *current; - hre.replaceDestructive(text, "", "[^0-9.%q ]", "g"); - hre.replaceDestructive(text, "ryy ", " ", "g"); - text += "ryy"; - current->setText(text); - current = current->getNextToken(); + if (m_keepQ) { + m_humdrum_text << infile; } } @@ -86330,124 +86709,196 @@ void Tool_hands::removeNotes(HTp sstart, HTp send, const string& htype) { ////////////////////////////// // -// Tool_hands::markNotes -- +// Tool_flipper::checkForFlipChanges -- // -void Tool_hands::markNotes(HumdrumFile& infile) { - HumRegex hre; - - int counter = 0; - int scount = infile.getStrandCount(); - for (int i=0; igetExclusiveInterpretation(); - int hasHandMarkup = xtok->getValueInt("auto", "hand"); - if (!hasHandMarkup) { - continue; - } - HTp send = infile.getStrandEnd(i); - markNotes(sstart, send); - counter++; +void Tool_flipper::checkForFlipChanges(HumdrumFile& infile, int index) { + if (!infile[index].isInterpretation()) { + return; } - if (counter) { - infile.appendLine("!!!RDF**kern: " + m_leftMarker + " = marked note, color=\"" + m_leftColor + "\", left-hand note"); - infile.appendLine("!!!RDF**kern: " + m_rightMarker + " = marked note, color=\"" + m_rightColor + "\", right-hand note"); - infile.createLinesFromTokens(); + int track; + + for (int i=0; igetTrack(); + m_strophe[track] = true; + } else if (*token == "*Xstrophe") { + track = token->getTrack(); + m_strophe[track] = false; + } } -} -void Tool_hands::markNotes(HTp sstart, HTp send) { - HTp current = sstart; - while (current && (current != send)) { - if (!current->isData() || current->isNull() || current->isRest()) { - current = current->getNextToken(); - continue; - } + if (m_allQ) { + // state always stays on in this case + return; + } - HumRegex hre; - string text = *current; - string htype = current->getValue("auto", "hand"); - if (htype == "LH") { - hre.replaceDestructive(text, " " + m_leftMarker, " +", "g"); - text = m_leftMarker + text; - } else if (htype == "RH") { - hre.replaceDestructive(text, " " + m_rightMarker, " +", "g"); - text = m_rightMarker + text; + for (int i=0; igetTrack(); + m_flipState[track] = true; + m_fliplines[i] = true; + } else if (*token == "*Xflip") { + track = token->getTrack(); + m_flipState[track] = false; + m_fliplines[i] = true; } - current->setText(text); - current = current->getNextToken(); } + } ////////////////////////////// // -// Tool_hands::colorHands -- Convert for example *LH into *color:dodgerblue. +// Tool_flipper::processLine -- // -void Tool_hands::colorHands(HumdrumFile& infile) { - string left = "*color:" + m_leftColor; - string right = "*color:" + m_rightColor; - for (int i=0; i> flipees; + extractFlipees(flipees, infile, index); + if (!flipees.empty()) { + int status = flipSubspines(flipees); + if (status) { + infile[index].createLineFromTokens(); + } + } +} + + + +////////////////////////////// +// +// Tool_flipper::flipSubspines -- +// + +bool Tool_flipper::flipSubspines(vector>& flipees) { + bool regenerateLine = false; + for (int i=0; i<(int)flipees.size(); i++) { + if (flipees[i].size() > 1) { + flipSpineTokens(flipees[i]); + regenerateLine = true; + } + } + return regenerateLine; +} + + +////////////////////////////// +// +// Tool_flipper::flipSpineTokens -- +// + +void Tool_flipper::flipSpineTokens(vector& subtokens) { + if (subtokens.size() < 2) { + return; + } + int count = (int)subtokens.size(); + count = count / 2; + HTp tok1; + HTp tok2; + string str1; + string str2; + for (int i=0; isetText(str2); + tok2->setText(str1); + } +} + + + +////////////////////////////// +// +// Tool_flipper::extractFlipees -- +// + +void Tool_flipper::extractFlipees(vector>& flipees, + HumdrumFile& infile, int index) { + flipees.clear(); + + HLp line = &infile[index]; + int track; + int lastInsertTrack = -1; + for (int i=0; igetFieldCount(); i++) { + HTp token = line->token(i); + track = token->getTrack(); + if ((!m_stropheQ) && m_strophe[track]) { continue; } - bool changed = false; - for (int j=0; jisKern()) { continue; } - if (*token == "*LH") { - token->setText(left); - changed = true; - } - if (*token == "*RH") { - token->setText(right); - changed = true; + } else { + if (!token->isDataType(m_interp)) { + continue; } } - if (changed) { - infile[i].createLineFromTokens(); + if (lastInsertTrack != track) { + flipees.resize(flipees.size() + 1); + lastInsertTrack = track; } + flipees.back().push_back(token); } } + ///////////////////////////////// // -// Tool_homorhythm::Tool_homorhythm -- Set the recognized options for the tool. +// Tool_gasparize::Tool_gasparize -- Set the recognized options for the tool. // -Tool_homorhythm::Tool_homorhythm(void) { - define("a|append=b", "append analysis to end of input data"); - define("attacks=b", "append attack counts for each sonority"); - define("p|prepend=b", "prepend analysis to end of input data"); - define("r|raw-sonority=b", "display individual sonority scores only"); - define("raw-score=b", "display accumulated scores"); - define("M|no-marks=b", "do not mark homorhythm section notes"); - define("f|fraction=b", "calculate fraction of music that is homorhythm"); - define("v|voice=b", "display voice information or fraction results"); - define("F|filename=b", "show filename for f option"); - define("n|t|threshold=d:4.0", "threshold score sum required for homorhythm texture detection"); - define("s|score=d:1.0", "score assigned to a sonority with three or more attacks"); - define("m|intermediate-score=d:0.5", "score to give sonority between two adjacent attack sonoroties"); - define("l|letter=b", "display letter scoress before calculations"); +Tool_gasparize::Tool_gasparize(void) { + define("R|no-reference-records=b", "do not add reference records"); + define("r|only-add-reference-records=b", "only add reference records"); + + define("B|do-not-delete-breaks=b", "do not delete system/page break markers"); + define("b|only-delete-breaks=b", "only delete breaks"); + + define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations"); + define("a|only-fix-instrument-abbreviations=b", "only fix instrument abbreviations"); + + define("E|do-not-fix-editorial-accidentals=b", "do not fix instrument abbreviations"); + define("e|only-fix-editorial-accidentals=b", "only fix editorial accidentals"); + + define("T|do-not-add-terminal-longs=b", "do not add terminal long markers"); + define("t|only-add-terminal-longs=b", "only add terminal longs"); + + define("no-ties=b", "do not fix tied notes"); + + define("N|do-not-remove-empty-transpositions=b", "do not remove empty transposition instructions"); + define ("n|only-remove-empty-transpositions=b", "only remove empty transpositions"); } ///////////////////////////////// // -// Tool_homorhythm::run -- Do the main work of the tool. +// Tool_gasparize::run -- Primary interfaces to the tool. // -bool Tool_homorhythm::run(HumdrumFileSet& infiles) { +bool Tool_gasparize::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i m_score) { - m_intermediate_score = m_score; + if (getBoolean("do-not-add-terminal-longs")) { terminalsQ = false; } + if (getBoolean("only-add-terminal-longs")) { + abbreviationsQ = false; + accidentalsQ = false; + referencesQ = false; + terminalsQ = true; + breaksQ = false; + transpositionsQ = false; } -} + if (getBoolean("do-not-remove-empty-transpositions")) { transpositionsQ = false; } + if (getBoolean("no-ties")) { tieQ = false; } + if (getBoolean("only-remove-empty-transpositions")) { + abbreviationsQ = false; + accidentalsQ = false; + referencesQ = false; + terminalsQ = false; + breaksQ = false; + transpositionsQ = true; + } -////////////////////////////// -// -// Tool_homorhythm::processFile -- -// + if (articulationsQ) { removeArticulations(infile); } + if (fixbarlinesQ) { fixBarlines(infile); } + if (tieQ) { fixTies(infile); } + if (abbreviationsQ) { fixInstrumentAbbreviations(infile); } + if (accidentalsQ) { fixEditorialAccidentals(infile); } + if (parenthesesQ) { createJEditorialAccidentals(infile); } + if (referencesQ) { addBibliographicRecords(infile); } + if (breaksQ) { deleteBreaks(infile); } + if (terminalsQ) { addTerminalLongs(infile); } + if (transpositionsQ) { deleteDummyTranspositions(infile); } + if (mensurationQ) { addMensurations(infile); } + if (teditQ) { createEditText(infile); } + if (instrumentQ) { adjustIntrumentNames(infile); } + if (removekeydesigQ) { removeKeyDesignations(infile); } -void Tool_homorhythm::processFile(HumdrumFile& infile) { - vector data; - data.reserve(infile.getLineCount()); + adjustSystemDecoration(infile); - m_homorhythm.clear(); - m_homorhythm.resize(infile.getLineCount()); + // Input lyrics may contain "=" signs which are to be converted into + // spaces in **text data, and into elisions when displaying with verovio. + Tool_shed shed; + vector argv; + argv.push_back("shed"); + argv.push_back("-x"); // only apply to **text spines + argv.push_back("text"); + argv.push_back("-e"); + argv.push_back("s/=/ /g"); + shed.process(argv); + shed.run(infile); +} - m_notecount.clear(); - m_notecount.resize(infile.getLineCount()); - fill(m_notecount.begin(), m_notecount.end(), 0); - m_attacks.clear(); - m_attacks.resize(infile.getLineCount()); - fill(m_attacks.begin(), m_attacks.end(), 0); - m_notes.clear(); - m_notes.resize(infile.getLineCount()); +////////////////////////////// +// +// Tool_gasparize::removeArticulations -- +// +void Tool_gasparize::removeArticulations(HumdrumFile& infile) { + HumRegex hre; for (int i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + bool changed = false; + string text = token->getText(); + if (text.find("'") != string::npos) { + // remove staccatos + changed = true; + hre.replaceDestructive(text, "", "'", "g"); + } + if (text.find("~") != string::npos) { + // remove tenutos + changed = true; + hre.replaceDestructive(text, "", "~", "g"); + } + if (changed) { + token->setText(text); + } } - m_homorhythm[data[i]] = "NY"; // not homphonic by will get intermediate score. } +} - vector score(infile.getLineCount(), 0); - vector raw(infile.getLineCount(), 0); - double sum = 0.0; - for (int i=0; i<(int)data.size(); i++) { - if (m_homorhythm[data[i]].find("Y") != string::npos) { - if (m_homorhythm[data[i]].find("N") != string::npos) { - // sonority between two homorhythm-like sonorities. - // maybe also differentiate based on metric position. - sum += m_intermediate_score; - raw[data[i]] = m_intermediate_score; - } else { - sum += m_score; - raw[data[i]] = m_score; - } - } else { - sum = 0.0; - } - score[data[i]] = sum; - } - for (int i=(int)data.size()-2; i>=0; i--) { - if (score[data[i]] == 0) { +////////////////////////////// +// +// Tool_gasparize::adjustSystemDecoration -- +// !!!system-decoration: [(s1)(s2)(s3)(s4)] +// to: +// !!!system-decoration: [*] +// + +void Tool_gasparize::adjustSystemDecoration(HumdrumFile& infile) { + for (int i=infile.getLineCount() - 1; i>=0; i--) { + if (!infile[i].isReference()) { continue; } - if (score[data[i+1]] > score[data[i]]) { - score[data[i]] = score[data[i+1]]; + HTp token = infile.token(i, 0); + if (token->compare(0, 21, "!!!system-decoration:") == 0) { + token->setText("!!!system-decoration: [*]"); + break; } } +} - if (getBoolean("raw-score")) { - addAccumulatedScores(infile, score); - } - - if (getBoolean("raw-sonority")) { - addRawAnalysis(infile, raw); - } - if (getBoolean("raw-score")) { - addAccumulatedScores(infile, score); - } - if (getBoolean("fraction")) { - addFractionAnalysis(infile, score); - } - if (getBoolean("attacks")) { - addAttacks(infile, m_attacks); - } +////////////////////////////// +// +// Tool_gasparize::deleteDummyTranspositions -- Somehow empty +// transpositions that go to the same pitch can appear in the +// MusicXML data, so remove them here. Example: +// *Trd0c0 +// - if (!getBoolean("fraction")) { - // Color the notes within homorhythm textures. - // mark homorhythm regions in red, - // non-homorhythm sonorities within these regions in green - // and non-homorhythm regions in black. - if (m_letterQ) { - infile.appendDataSpine(m_homorhythm, "", "**hp"); +void Tool_gasparize::deleteDummyTranspositions(HumdrumFile& infile) { + vector ldel; + for (int i=0; i= m_threshold) { - if (m_attacks[data[i]] < (int)m_notes[data[i]].size() - 1) { - m_homorhythm[data[i]] = "dodgerblue"; - } else { - m_homorhythm[data[i]] = "red"; - } + if (!infile[i].isInterpretation()) { + continue; + } + bool empty = true; + for (int j=0; jisKern()) { + empty = false; + continue; + } + if (*token == "*Trd0c0") { + token->setText("*"); } else { - m_homorhythm[data[i]] = "black"; + empty = false; } } - infile.appendDataSpine(m_homorhythm, "", "**color"); + if (empty) { + ldel.push_back(i); + } + } - // problem with **color spine in javascript, so output via humdrum text - m_humdrum_text << infile; + if (ldel.size() == 1) { + infile.deleteLine(ldel[0]); + } else if (ldel.size() > 1) { + cerr << "Warning: multiple transposition lines, not deleting them" << endl; } } - ////////////////////////////// // -// Tool_homorhythm::addAccumulatedScores -- +// Tool_gasparize::fixEditorialAccidentals -- checkDataLine() does +// all of the work for this function, which only manages +// key signature and barline processing. +// Rules for accidentals in Tasso in Music Project: +// (1) Only note accidentals printed in the source editions +// are displayed as regular accidentals. These accidentals +// are postfixed with an "X" in the **kern data. +// (2) Editorial accidentals are given an "i" marker but not +// a "X" marker in the **kern data. This editorial accidental +// is displayed above the note. +// This algorithm makes adjustments to the input data because +// Sibelius will drop editorial information after the frist +// editorial accidental on that pitch in the measure. +// (3) If a note is the same pitch as a previous note in the +// measure and the previous note has an editorial accidental, +// then make the note an editorial note. However, if the +// accidental state of the note matches the key-signature, +// then do not add an editorial accidental, and there will be +// no accidental displayed on the note. In that case, add a "y" +// after the accidental to indicate that it is interpreted +// and not visible in the original score. // -void Tool_homorhythm::addAccumulatedScores(HumdrumFile& infile, vector& score) { - infile.appendDataSpine(score, "", "**score", false); +void Tool_gasparize::fixEditorialAccidentals(HumdrumFile& infile) { + removeDoubledAccidentals(infile); + + m_pstates.resize(infile.getMaxTrack() + 1); + m_estates.resize(infile.getMaxTrack() + 1); + m_kstates.resize(infile.getMaxTrack() + 1); + + for (int i=0; i<(int)m_pstates.size(); i++) { + m_pstates[i].resize(70); + fill(m_pstates[i].begin(), m_pstates[i].end(), 0); + m_kstates[i].resize(70); + fill(m_kstates[i].begin(), m_kstates[i].end(), 0); + m_estates[i].resize(70); + fill(m_estates[i].begin(), m_estates[i].end(), false); + } + + for (int i=0; i& raw) { - infile.appendDataSpine(raw, "", "**raw", false); -} - - - -////////////////////////////// -// -// Tool_homorhythm::addAttacks -- -// - -void Tool_homorhythm::addAttacks(HumdrumFile& infile, vector& attacks) { - infile.appendDataSpine(attacks, "", "**atks"); -} - - - -////////////////////////////// -// -// Tool_homorhythm::addFractionAnalysis -- +// Tool_gasparize::removeDoubledAccidentals -- Often caused by transposition +// differences between parts in the MusicXML export from Finale. Also some +// strange double sharps appear randomly. // -void Tool_homorhythm::addFractionAnalysis(HumdrumFile& infile, vector& score) { - double sum = 0.0; +void Tool_gasparize::removeDoubledAccidentals(HumdrumFile& infile) { + HumRegex hre; for (int i=0; i m_threshold) { - sum += infile[i].getDuration().getFloat(); - } - } - double total = infile.getScoreDuration().getFloat(); - int ocount = getOriginalVoiceCount(infile); - double fraction = sum / total; - double percent = int(fraction * 1000.0 + 0.5)/10.0; - if (getBoolean("filename")) { - m_free_text << infile.getFilename() << "\t"; - } - if (getBoolean("voice")) { - m_free_text << ocount; - m_free_text << "\t"; - m_free_text << m_voice_count; - m_free_text << "\t"; - if (ocount == m_voice_count) { - m_free_text << "complete" << "\t"; - } else { - m_free_text << "incomplete" << "\t"; + for (int j=0; jisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + if (token->isRest()) { + continue; + } + if (token->find("--") != string::npos) { + string text = *token; + hre.replaceDestructive(text, "-", "--", "g"); + } else if (token->find("--") != string::npos) { + string text = *token; + hre.replaceDestructive(text, "#", "##", "g"); + } } } - if (m_voice_count < 2) { - m_free_text << -1; - } else { - m_free_text << percent; - } - m_free_text << endl; } ////////////////////////////// // -// Tool_homorhythm::getOriginalVoiceCount -- +// Tool_gasparize::addTerminalLongs -- Convert all last notes to terminal longs +// Also probably add terminal longs before double barlines as in JRP. // -int Tool_homorhythm::getOriginalVoiceCount(HumdrumFile& infile) { - HumRegex hre; - for (int i=0; iisKern()) { + continue; + } + while (cur) { + if (!cur->isData()) { + cur = cur->getPreviousToken(); + continue; } - return count; + if (cur->isNull()) { + cur = cur->getPreviousToken(); + continue; + } + if (cur->isRest()) { + cur = cur->getPreviousToken(); + continue; + } + if (cur->isSecondaryTiedNote()) { + cur = cur->getPreviousToken(); + continue; + } + if (cur->find("l") != string::npos) { + // already marked so do not do it again + break; + } + // mark this note with "l" + string newtext = *cur; + newtext += "l"; + cur->setText(newtext); + break; } } - return 0; } ////////////////////////////// // -// Tool_homorhythm::getExtantVoiceCount -- +// Tool_gasparize::fixInstrumentAbbreviations -- // -int Tool_homorhythm::getExtantVoiceCount(HumdrumFile& infile) { - vector spines = infile.getKernSpineStartList(); - return (int)spines.size(); -} - +void Tool_gasparize::fixInstrumentAbbreviations(HumdrumFile& infile) { + int iline = -1; + int aline = -1; + vector kerns = infile.getKernSpineStartList(); + if (kerns.empty()) { + return; + } -////////////////////////////// -// -// Tool_homorhythm::analyzeLine -- -// + HTp cur = kerns[0]; + while (cur) { + if (cur->isData()) { + break; + } + if (cur->compare(0, 3, "*I\"") == 0) { + iline = cur->getLineIndex(); + } else if (cur->compare(0, 3, "*I'") == 0) { + aline = cur->getLineIndex(); + } + cur = cur->getNextToken(); + } -void Tool_homorhythm::analyzeLine(HumdrumFile& infile, int line) { - m_notes[line].reserve(10); - HPNote note; - if (!infile[line].isData()) { + if (iline < 0) { + // no names to create abbreviations for return; } - int nullQ = 0; - for (int i=0; iisKern()) { + if (aline < 0) { + // not creating a new abbreviation for now + // (could add later). + return; + } + if (infile[iline].getFieldCount() != infile[aline].getFieldCount()) { + // no spine splitting between the two lines. + return; + } + // Maybe also require them to be adjacent to each other. + HumRegex hre; + for (int j=0; j<(int)infile[iline].getFieldCount(); j++) { + if (!infile.token(iline, j)->isKern()) { continue; } - if (token->isRest()) { + if (!hre.search(*infile.token(iline, j), "([A-Za-z][A-Za-z .0-9]+)")) { continue; } - if (token->isNull()) { - nullQ = 1; - token = token->resolveNull(); - if (!token) { - continue; - } - if (token->isRest()) { - continue; - } - } else { - nullQ = 0; - } - int track = token->getTrack(); - vector subtokens = token->getSubtokens(); - for (int j=0; j<(int)subtokens.size(); j++) { - note.track = track; - note.line = token->getLineIndex(); - note.field = token->getFieldIndex(); - note.subfield = j; - note.token = token; - note.text = subtokens[j]; - note.duration = Convert::recipToDuration(note.text); - if (nullQ) { - note.attack = false; - note.nullQ = true; - } else { - note.nullQ = false; - if ((note.text.find("_") != string::npos) || - (note.text.find("]") != string::npos)) { - note.attack = false; - } else { - note.attack = true; - } - } - m_notes[line].push_back(note); - } - } - - // There must be at least three attacks to be considered homorhythm - // maybe adjust to N-1 or three voices, or a similar rule. - vector adurs; - for (int i=0; i<(int)m_notes[line].size(); i++) { - if (m_notes[line][i].attack) { - adurs.push_back(m_notes[line][i].duration); - m_attacks[line]++; - } - } - // if ((int)m_attacks[line] >= (int)m_notes[line].size() - 1) { - if ((int)m_attacks[line] >= 3) { - string value = "Y"; - // value += to_string(m_attacks[line]); - m_homorhythm[line] = value; - } else if ((m_voice_count == 3) && (m_attacks[line] == 2)) { - if ((adurs.size() >= 2) && (adurs[0] == adurs[1])) { - m_homorhythm[line] = "Y"; + string name = hre.getMatch(1); + string abbr = "*I'"; + if (name == "Basso Continuo") { + abbr += "BC"; + } else if (name == "Basso continuo") { + abbr += "BC"; + } else if (name == "basso continuo") { + abbr += "BC"; } else { - m_homorhythm[line] = "N"; + abbr += toupper(name[0]); } - } else { - string value = "N"; - // value += to_string(m_attacks[line]); - m_homorhythm[line] = value; - } - // redundant or three-or-more case: - if (m_notes[line].size() <= 2) { - m_homorhythm[line] = "N"; + // check for numbers after the end of the name and add to abbreviation + infile.token(aline, j)->setText(abbr); } } - -///////////////////////////////// -// -// Tool_homorhythm2::Tool_homorhythm -- Set the recognized options for the tool. -// - -Tool_homorhythm2::Tool_homorhythm2(void) { - define("t|threshold=d:1.6", "threshold score sum required for homorhythm texture detection"); - define("u|threshold2=d:1.3", "threshold score sum required for semi-homorhythm texture detection"); - define("s|score=b", "show numeric scores"); - define("n|length=i:4", "sonority length to calculate"); - define("f|fraction=b", "report fraction of music that is homorhythm"); -} - - - -///////////////////////////////// +////////////////////////////// // -// Tool_homorhythm2::run -- Do the main work of the tool. +// Tool_gasparize::convertBreaks -- // -bool Tool_homorhythm2::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i= 0; i--) { + if (!infile[i].isGlobalComment()) { + continue; + } + if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) { + string text = "!!LO:LB:g=original"; + infile[i].setText(text); + } + else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) { + string text = "!!LO:PB:g=original"; + infile[i].setText(text); + } } - return status; -} - - -bool Tool_homorhythm2::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; } ////////////////////////////// // -// Tool_homorhythm2::initialize -- +// Tool_gasparize::deleteBreaks -- // -void Tool_homorhythm2::initialize(void) { - m_threshold = getDouble("threshold"); - if (m_threshold < 0.0) { - m_threshold = 0.0; - } - m_threshold2 = getDouble("threshold2"); - if (m_threshold2 < 0.0) { - m_threshold2 = 0.0; - } - if (m_threshold < m_threshold2) { - double temp = m_threshold; - m_threshold = m_threshold2; - m_threshold2 = temp; +void Tool_gasparize::deleteBreaks(HumdrumFile& infile) { + HumRegex hre; + for (int i=infile.getLineCount()-1; i>= 0; i--) { + if (!infile[i].isGlobalComment()) { + continue; + } + if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) { + infile.deleteLine(i); + } + else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) { + infile.deleteLine(i); + } } - } - -////////////////////////////// +//////////////////////////////// // -// Tool_homorhythm2::processFile -- +// Tool_gasparize::addBibliographicRecords -- +// +// !!!COM: +// !!!CDT: +// !!!OTL: +// !!!AGN: +// !!!SCT: +// !!!SCA: +// !!!voices: +// +// At end: +// !!!RDF**kern: l = terminal long +// !!!RDF**kern: i = editorial accidental +// !!!EED: +// !!!EEV: $DATE // -void Tool_homorhythm2::processFile(HumdrumFile& infile) { - infile.analyzeStructure(); - NoteGrid grid(infile); - m_score.resize(infile.getLineCount()); - fill(m_score.begin(), m_score.end(), 0.0); - - double score; - int count; - int wsize = getInteger("length"); +void Tool_gasparize::addBibliographicRecords(HumdrumFile& infile) { + vector refinfo = infile.getReferenceRecords(); + map refs; + for (int i=0; i<(int)refinfo.size(); i++) { + string key = refinfo[i]->getReferenceKey(); + refs[key] = refinfo[i]; + } - for (int i=0; iisRest()) { - continue; - } - NoteCell* cell2 = grid.cell(k, i+m); - if (cell2->isRest()) { - continue; - } - count++; - if (cell1->isAttack() && cell2->isAttack()) { - score += 1.0; - } - } - } + // header records + if (refs.find("voices") == refs.end()) { + if (infile.token(0, 0)->find("!!!OTL") != string::npos) { + infile.insertLine(1, "!!!voices:"); + } else { + infile.insertLine(0, "!!!voices:"); } - int index = grid.getLineIndex(i); - m_score[index] = score / count; } - - for (int i=grid.getSliceCount()-1; i>=wsize; i--) { - score = 0; - count = 0; - for (int j=0; jisRest()) { - continue; - } - NoteCell* cell2 = grid.cell(k, i-m); - if (cell2->isRest()) { - continue; - } - count++; - if (cell1->isAttack() && cell2->isAttack()) { - score += 1.0; - } - } - } + if (refs.find("SCA") == refs.end()) { + if (infile.token(0, 0)->find("!!!OTL") != string::npos) { + infile.insertLine(1, "!!!SCA:"); + } else { + infile.insertLine(0, "!!!SCA:"); } - int index = grid.getLineIndex(i); - m_score[index] += score / count; } - - - for (int i=0; i<(int)m_score.size(); i++) { - m_score[i] = int(m_score[i] * 100.0 + 0.5) / 100.0; + if (refs.find("SCT") == refs.end()) { + if (infile.token(0, 0)->find("!!!OTL") != string::npos) { + infile.insertLine(1, "!!!SCT:"); + } else { + infile.insertLine(0, "!!!SCT:"); + } + } + if (refs.find("AGN") == refs.end()) { + if (infile.token(0, 0)->find("!!!OTL") != string::npos) { + infile.insertLine(1, "!!!AGN:"); + } else { + infile.insertLine(0, "!!!AGN:"); + } } + if (refs.find("OTL") == refs.end()) { + infile.insertLine(0, "!!!OTL:"); + } + if (refs.find("CDT") == refs.end()) { + infile.insertLine(0, "!!!CDT: ~1450-~1517"); + } + if (refs.find("COM") == refs.end()) { + infile.insertLine(0, "!!!COM: Gaspar van Weerbeke"); + } - vector color(infile.getLineCount());; + // trailer records + bool foundi = false; + bool foundj = false; + bool foundl = false; for (int i=0; i= m_threshold) { - color[i] = "red"; - } else if (m_score[i] >= m_threshold2) { - color[i] = "orange"; - } else { - color[i] = "black"; + HTp token = infile.token(i, 0); + if (token->find("!!!RDF**kern:") == string::npos) { + continue; } - } - - if (getBoolean("fraction")) { - HumNum sum = 0; - HumNum total = infile.getScoreDuration(); - for (int i=0; i<(int)m_score.size(); i++) { - if (m_score[i] >= m_threshold2) { - sum += infile[i].getDuration(); + if (token->find("terminal breve") != string::npos) { + foundl = true; + } else if (token->find("editorial accidental") != string::npos) { + if (token->find("i =") != string::npos) { + foundi = true; + } else if (token->find("j =") != string::npos) { + foundj = true; } } - HumNum fraction = sum / total; - m_free_text << int(fraction.getFloat() * 1000.0 + 0.5) / 10.0 << endl; - } else { - if (getBoolean("score")) { - infile.appendDataSpine(m_score, ".", "**cdata", false); - } - infile.appendDataSpine(color, ".", "**color", true); - infile.createLinesFromTokens(); - - // problem within emscripten-compiled version, so force to output as string: - m_humdrum_text << infile; + } + if (!foundj) { + infile.appendLine("!!!RDF**kern: j = editorial accidental, optional, paren up"); + } + if (!foundi) { + infile.appendLine("!!!RDF**kern: i = editorial accidental"); + } + if (!foundl) { + infile.appendLine("!!!RDF**kern: l = terminal long"); } + if (refs.find("PTL") == refs.end()) { + infile.appendLine("!!!PTL: Gaspar van Weerbeke: Collected Works. V. Settings of Liturgical Texts, Songs, and Instrumental Works"); + } + if (refs.find("PPR") == refs.end()) { + infile.appendLine("!!!PPR: American Institute of Musicology"); + } + if (refs.find("PC#") == refs.end()) { + infile.appendLine("!!!PC#: Corpus Mensurabilis Musicae 106/V"); + } + if (refs.find("PDT") == refs.end()) { + infile.appendLine("!!!PDT: {YEAR}"); + } + if (refs.find("PED") == refs.end()) { + infile.appendLine("!!!PED: Kolb, Paul"); + infile.appendLine("!!!PED: Pavanello, Agnese"); + } + if (refs.find("YEC") == refs.end()) { + infile.appendLine("!!!YEC: Copyright {YEAR}, Kolb, Paul"); + infile.appendLine("!!!YEC: Copyright {YEAR}, Pavanello, Agnese"); + } + if (refs.find("YEM") == refs.end()) { + infile.appendLine("!!!YEM: CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-nc/4.0/legalcode)"); + } + if (refs.find("EED") == refs.end()) { + infile.appendLine("!!!EED: Zybina, Karina"); + infile.appendLine("!!!EED: Mair-Gruber, Roland"); + } + if (refs.find("EEV") == refs.end()) { + string date = getDate(); + string line = "!!!EEV: " + date; + infile.appendLine(line); + } } - - - -///////////////////////////////// -// -// Tool_gridtest::Tool_hproof -- Set the recognized options for the tool. -// - -Tool_hproof::Tool_hproof(void) { - // put option definitions here -} - - - -/////////////////////////////// +//////////////////////////////// // -// Tool_hproof::run -- Primary interfaces to the tool. +// Tool_gasparize::checkDataLine -- // -bool Tool_hproof::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; igetTrack(); + if (!token->isKern()) { + continue; + } + if (token->isNull()) { + continue; + } + if (token->isRest()) { + continue; + } + if (token->find('j') != string::npos) { + continue; + } + if (token->isSecondaryTiedNote()) { + continue; + } + base7 = Convert::kernToBase7(token); + accid = Convert::kernToAccidentalCount(token); + haseditQ = false; + removeQ = false; -bool Tool_hproof::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - out << infile; - return status; -} + // Hard-wired to "i" as editorial accidental marker + if (token->find("ni") != string::npos) { + haseditQ = true; + } else if (token->find("-i") != string::npos) { + haseditQ = true; + } else if (token->find("#i") != string::npos) { + haseditQ = true; + } else if (token->find("nXi") != string::npos) { + haseditQ = true; + removeQ = true; + } else if (token->find("-Xi") != string::npos) { + haseditQ = true; + removeQ = true; + } else if (token->find("#Xi") != string::npos) { + haseditQ = true; + removeQ = true; + } + if (removeQ) { + string temp = *token; + hre.replaceDestructive(temp, "", "X"); + token->setText(temp); + } -bool Tool_hproof::run(HumdrumFile& infile) { - markNonChordTones(infile); - infile.appendLine("!!!RDF**kern: N = marked note, color=chocolate (non-chord tone)"); - infile.appendLine("!!!RDF**kern: Z = marked note, color=black (chord tone)"); - infile.createLinesFromTokens(); - return true; -} + bool explicitQ = false; + if (token->find("#X") != string::npos) { + explicitQ = true; + } else if (token->find("-X") != string::npos) { + explicitQ = true; + } else if (token->find("nX") != string::npos) { + explicitQ = true; + } else if (token->find("n") != string::npos) { + // add an explicit accidental marker + explicitQ = true; + string text = *token; + hre.replaceDestructive(text, "nX", "n"); + token->setText(text); + } + if (haseditQ) { + // Store new editorial pitch state. + m_estates.at(track).at(base7) = true; + m_pstates.at(track).at(base7) = accid; + continue; + } + if (explicitQ) { + // No need to make editorial since it is visible. + m_estates.at(track).at(base7) = false; + m_pstates.at(track).at(base7) = accid; + continue; + } -////////////////////////////// -// -// Tool_hproof::markNonChordTones -- Mark -// + if (accid == m_kstates.at(track).at(base7)) { + // !m_estates.at(track).at(base7)) { + // add !m_estates.at(track).at(base) as a condition if + // you want editorial accidentals to be added to return the + // note to the accidental in the key. + // + // The accidental matches the key-signature state, + // so it should not be made editorial eventhough + // it is not visible. + m_pstates.at(track).at(base7) = accid; -void Tool_hproof::markNonChordTones(HumdrumFile& infile) { - vector list; - infile.getSpineStartList(list); - vector hlist; - for (auto it : list) { - if (*it == "**harm") { - hlist.push_back(it); - } - if (*it == "**rhrm") { - hlist.push_back(it); - } - } - if (hlist.empty()) { - cerr << "Warning: No **harm or **rhrm spines in data" << endl; - return; - } + // Add a "y" marker of there is an interpreted accidental + // state (flat or sharp) that is part of the key signature. + int hasaccid = false; + if (token->find("#") != string::npos) { + hasaccid = true; + } else if (token->find("-") != string::npos) { + hasaccid = true; + } + int hashide = false; + if (token->find("-y") != string::npos) { + hashide = true; + } + else if (token->find("#y") != string::npos) { + hashide = true; + } + if (hasaccid && !hashide) { + string text = *token; + hre.replaceDestructive(text, "#y", "#"); + hre.replaceDestructive(text, "-y", "-"); + token->setText(text); + } - processHarmSpine(infile, hlist[0]); -} + continue; + } + // At this point the previous note with this pitch class + // had an editorial accidental, and this note also has the + // same accidental, or there was a previous visual accidental + // outside of the key signature that will cause this note to have + // an editorial accidental mark applied (Sibelius will drop + // secondary editorial accidentals in a measure when exporting, + // MusicXML, which is why this function is needed). + m_estates[track][base7] = true; + m_pstates[track][base7] = accid; -////////////////////////////// -// -// processHarmSpine -- -// + string text = token->getText(); + HumRegex hre; + hre.replaceDestructive(text, "#", "##+", "g"); + hre.replaceDestructive(text, "-", "--+", "g"); + string output = ""; + bool foundQ = false; + for (int j=0; j<(int)text.size(); j++) { + if (text[j] == 'n') { + output += "ni"; + foundQ = true; + } else if (text[j] == '#') { + output += "#i"; + foundQ = true; + } else if (text[j] == '-') { + output += "-i"; + foundQ = true; + } else { + output += text[j]; + } + } -void Tool_hproof::processHarmSpine(HumdrumFile& infile, HTp hstart) { - string key = "*C:"; // assume C major if no key designation - HTp token = hstart; - HTp ntoken = token->getNextNNDT(); - while (token) { - markNotesInRange(infile, token, ntoken, key); - if (!ntoken) { - break; + if (foundQ) { + token->setText(output); + continue; } - if (ntoken && token) { - getNewKey(token, ntoken, key); + + // The note is natural, but has no natural sign. + // add the natural sign and editorial mark. + for (int j=(int)output.size()-1; j>=0; j--) { + if ((tolower(output[j]) >= 'a') && (tolower(output[j]) <= 'g')) { + output.insert(j+1, "ni"); + break; + } } - token = ntoken; - ntoken = ntoken->getNextNNDT(); + token->setText(output); } } -////////////////////////////// +//////////////////////////////// // -// Tool_hproof::getNewKey -- +// Tool_gasparize::updateKeySignatures -- Fill in the accidental +// states for each diatonic pitch. // -void Tool_hproof::getNewKey(HTp token, HTp ntoken, string& key) { - token = token->getNextToken(); - while (token && (token != ntoken)) { - if (token->isKeyDesignation()) { - key = *token; +void Tool_gasparize::updateKeySignatures(HumdrumFile& infile, int lineindex) { + HumdrumLine& line = infile[lineindex]; + int track; + for (int i=0; iisKeySignature()) { + continue; } - token = token->getNextToken(); - } -} + HTp token = line.token(i); + track = token->getTrack(); + string text = token->getText(); + fill(m_kstates[track].begin(), m_kstates[track].end(), 0); + for (int j=3; j<(int)text.size()-1; j++) { + if (text[j] == ']') { + break; + } + switch (text[j]) { + case 'a': case 'A': + switch (text[j+1]) { + case '#': m_kstates[track][5] = +1; + break; + case '-': m_kstates[track][5] = -1; + break; + } + break; + case 'b': case 'B': + switch (text[j+1]) { + case '#': m_kstates[track][6] = +1; + break; + case '-': m_kstates[track][6] = -1; + break; + } + break; + case 'c': case 'C': + switch (text[j+1]) { + case '#': m_kstates[track][0] = +1; + break; + case '-': m_kstates[track][0] = -1; + break; + } + break; -////////////////////////////// -// -// Tool_hproof::markNotesInRange -- -// + case 'd': case 'D': + switch (text[j+1]) { + case '#': m_kstates[track][1] = +1; + break; + case '-': m_kstates[track][1] = -1; + break; + } + break; -void Tool_hproof::markNotesInRange(HumdrumFile& infile, HTp ctoken, HTp ntoken, const string& key) { - if (!ctoken) { - return; - } - int startline = ctoken->getLineIndex(); - int stopline = infile.getLineCount(); - if (ntoken) { - stopline = ntoken->getLineIndex(); - } - vector cts; - cts = Convert::harmToBase40(ctoken, key); - for (int i=startline; iisKern()) { - continue; - } - HTp tok = infile.token(i, j); - if (tok->isNull()) { - continue; + case 'e': case 'E': + switch (text[j+1]) { + case '#': m_kstates[track][2] = +1; + break; + case '-': m_kstates[track][2] = -1; + break; + } + break; + + case 'f': case 'F': + switch (text[j+1]) { + case '#': m_kstates[track][3] = +1; + break; + case '-': m_kstates[track][3] = -1; + break; + } + break; + + case 'g': case 'G': + switch (text[j+1]) { + case '#': m_kstates[track][4] = +1; + break; + case '-': m_kstates[track][4] = -1; + break; + } + break; } - if (tok->isRest()) { - continue; + for (int j=0; j<7; j++) { + if (m_kstates[track][j] == 0) { + continue; + } + for (int k=1; k<10; k++) { + m_kstates[track][j+k*7] = m_kstates[track][j]; + } } - markHarmonicTones(tok, cts); } } -// cerr << "TOK\t" << ctoken << "\tLINES\t" << startline << "\t" << stopline << "\t"; -// for (int i=0; i& cts) { - int count = tok->getSubtokenCount(); - vector notes = cts; - string output; - for (int i=0; igetSubtoken(i); - int pitch = Convert::kernToBase40(subtok); - if (i > 0) { - output += " "; - } - bool found = false; - for (int j=0; j<(int)cts.size(); j++) { - if (pitch % 40 == cts[j] % 40) { - output += subtok; - output += "Z"; - found = true; - break; - } - } - if (!found) { - output += subtok; - output += "N"; - } +void Tool_gasparize::clearStates(void) { + for (int i=0; i<(int)m_pstates.size(); i++) { + fill(m_pstates[i].begin(), m_pstates[i].end(), 0); + } + for (int i=0; i<(int)m_estates.size(); i++) { + fill(m_estates[i].begin(), m_estates[i].end(), false); } - tok->setText(output); } - - - -///////////////////////////////// +////////////////////////////// // -// Tool_humbreak::Tool_humbreak -- Set the recognized options for the tool. +// Tool_gasparize::getDate -- // -Tool_humbreak::Tool_humbreak(void) { - define("m|measures=s", "measures numbers to place linebreaks before"); - define("p|page-breaks=s", "measure numbers to place page breaks before"); - define("g|group=s:original", "line/page break group"); - define("r|remove|remove-breaks=b", "remove line/page breaks"); - define("l|page-to-line-breaks=b", "convert page breaks to line breaks"); +string Tool_gasparize::getDate(void) { + time_t t = time(NULL); + tm* timeptr = localtime(&t); + stringstream ss; + int year = timeptr->tm_year + 1900; + int month = timeptr->tm_mon + 1; + int day = timeptr->tm_mday; + ss << year << "/"; + if (month < 10) { + ss << "0"; + } + ss << month << "/"; + if (day < 10) { + ss << "0"; + } + ss << day; + return ss.str(); } -///////////////////////////////// +////////////////////////////// // -// Tool_humbreak::run -- Do the main work of the tool. +// Tool_gasparize::fixTies -- +// If a tie is unclosed or if a note is followed by an invisible rest, then fix. // -bool Tool_humbreak::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisKern()) { + continue; + } + HTp send = infile.getStrandEnd(i); + fixTiesForStrand(sstart, send); } - return status; + fixTieStartEnd(infile); } -bool Tool_humbreak::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + +void Tool_gasparize::fixTieStartEnd(HumdrumFile& infile) { + int strands = infile.getStrandCount(); + for (int i=0; iisKern()) { + continue; + } + HTp send = infile.getStrandEnd(i); + fixTiesStartEnd(sstart, send); } - return status; } -bool Tool_humbreak::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} - -bool Tool_humbreak::run(HumdrumFile& infile) { - processFile(infile); - return true; +void Tool_gasparize::fixTiesStartEnd(HTp starts, HTp ends) { + HTp current = starts; + HumRegex hre; + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if ((current->find('[') != string::npos) && + (current->find(']') != string::npos) && + (current->find(' ') == string::npos)) { + string text = *current; + hre.replaceDestructive(text, "", "\\[", "g"); + hre.replaceDestructive(text, "_", "\\]", "g"); + current->setText(text); + } + current = current->getNextToken(); + } } - ////////////////////////////// // -// Tool_humbreak::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_gasparize::fixTiesForStrand -- // -void Tool_humbreak::initialize(void) { - string systemMeasures = getString("measures"); - string pageMeasures = getString("page-breaks"); - m_group = getString("group"); - m_removeQ = getBoolean("remove-breaks"); - m_page2lineQ = getBoolean("page-to-line-breaks"); - - vector lbs; - vector pbs; - HumRegex hre; - hre.split(lbs, systemMeasures, "[^\\da-z]+"); - hre.split(pbs, pageMeasures, "[^\\da-z]+"); - - for (int i=0; i<(int)lbs.size(); i++) { - if (hre.search(lbs[i], "^(p?)(\\d+)([a-z]?)")) { - int number = hre.getMatchInt(2); - if (!hre.getMatch(1).empty()) { - m_pageMeasures[number] = 1; - int offset = 0; - string letter; - if (!hre.getMatch(3).empty()) { - letter = hre.getMatch(3); - offset = letter.at(0) - 'a'; - } - m_pageOffset[number] = offset; - } else { - m_lineMeasures[number] = 1; - int offset = 0; - if (!hre.getMatch(3).empty()) { - string letter = hre.getMatch(3); - offset = letter.at(0) - 'a'; - } - m_lineOffset[number] = offset; - } - } +void Tool_gasparize::fixTiesForStrand(HTp sstart, HTp send) { + if (!sstart) { + return; } - - for (int i=0; i<(int)pbs.size(); i++) { - if (hre.search(pbs[i], "^(\\d+)([a-z]?)")) { - int number = hre.getMatchInt(1); - m_pageMeasures[number] = 1; - int offset = 0; - if (!hre.getMatch(2).empty()) { - string letter = hre.getMatch(2); - offset = letter.at(0) - 'a'; - } - m_pageOffset[number] = offset; + HTp current = sstart; + HTp last = NULL; + current = current->getNextToken(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + if (last == NULL) { + last = current; + current = current->getNextToken(); + continue; } + if (current->find("yy") != string::npos) { + fixTieToInvisibleRest(last, current); + } else if (((last->find("[") != string::npos) || (last->find("_") != string::npos)) + && ((current->find("]") == string::npos) && (current->find("_") == string::npos))) { + fixHangingTie(last, current); + } + last = current; + current = current->getNextToken(); } } @@ -87375,189 +87992,62 @@ void Tool_humbreak::initialize(void) { ////////////////////////////// // -// Tool_humbreak::markLineBreakMeasures -- +// Tool_gasparize::fixTieToInvisibleRest -- // -void Tool_humbreak::markLineBreakMeasures(HumdrumFile& infile) { - vector pbreak; - vector lbreak; +void Tool_gasparize::fixTieToInvisibleRest(HTp first, HTp second) { + if (second->find("yy") == string::npos) { + return; + } + if ((first->find("[") == string::npos) && (first->find("_") == string::npos)) { + string ftext = *first; + ftext = "[" + ftext; + first->setText(ftext); + } HumRegex hre; - map used; - - for (int i=0; isetText(text); +} - if (!infile[i].isBarline()) { - continue; - } - int barnum = infile[i].getBarNumber(); - if (barnum < 0) { - lbreak.clear(); - pbreak.clear(); - continue; - } - int status = m_lineMeasures[barnum]; - if (status) { - HLp line = &infile[i]; - int offset = m_lineOffset[barnum]; - if (offset && (used[barnum] == 0)) { - used[barnum] = offset; - int ocounter = 0; - lbreak.clear(); - pbreak.clear(); - for (int j=i+1; jsetValue("auto", "barnum", barnum + 1); - } else { - line->setValue("auto", "barnum", barnum + 1); - } - } else { - line->setValue("auto", "barnum", barnum + 1); - } - } +////////////////////////////// +// +// Tool_gasparize::fixHangingTie -- Not dealing with chain of missing ties. +// - status = m_pageMeasures[barnum]; - if (status) { - HLp line = &infile[i]; - int offset = m_pageOffset[barnum]; - if (offset) { - int ocounter = 0; - lbreak.clear(); - pbreak.clear(); - for (int j=i+1; jsetValue("auto", "barnum", barnum + 1); - pbreak.back()->setValue("auto", "page", 1); - } - } else { - line->setValue("auto", "barnum", barnum + 1); - line->setValue("auto", "page", 1); - } - } - } +void Tool_gasparize::fixHangingTie(HTp first, HTp second) { + string text = *second; + text += "]"; + second->setText(text); } ////////////////////////////// // -// Tool_humbreak::addBreaks -- +// Tool_gasparize::addMensurations -- Add mensurations. // -void Tool_humbreak::addBreaks(HumdrumFile& infile) { - markLineBreakMeasures(infile); - +void Tool_gasparize::addMensurations(HumdrumFile& infile) { HumRegex hre; - for (int i=0; i=0; i--) { + if (!infile[i].isInterpretation()) { continue; } - barnum--; - int pageQ = infile[i].getValueInt("auto", "page"); - - if (pageQ && infile[i].isComment()) { + for (int j=0; jisBarline()) { - int measure = infile[i+1].getBarNumber(); - int pbStatus = m_pageMeasures[measure]; - if (pbStatus) { - string query = "\\b" + m_group + "\\b"; - if (!hre.match(token, query)) { - m_humdrum_text << token << ", " << m_group << endl; - } else { - m_humdrum_text << token << endl; - } - } else { - m_humdrum_text << token << endl; - } - m_humdrum_text << infile[i+1] << endl; - i++; - continue; - } - } else if (hre.search(token, "^!!LO:LB:")) { - // Add group to existing LO:LB: - HTp token = infile.token(i, 0); - HTp barToken = infile.token(i+1, 0); - if (barToken->isBarline()) { - int measure = infile[i+1].getBarNumber(); - int lbStatus = m_lineMeasures[measure]; - if (lbStatus) { - string query = "\\b" + m_group + "\\b"; - if (!hre.match(token, query)) { - m_humdrum_text << token << ", " << m_group << endl; - } else { - m_humdrum_text << token << endl; - } - } else { - m_humdrum_text << token << endl; - } - m_humdrum_text << infile[i+1] << endl; - i++; - continue; - } + if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) { + int value = hre.getMatchInt(1); + addMensuration(value, infile, i); } } - - if (pageQ) { - m_humdrum_text << "!!LO:PB:g=" << m_group << endl; - } else { - m_humdrum_text << "!!LO:LB:g=" << m_group << endl; - } - m_humdrum_text << infile[i] << endl; } } @@ -87565,364 +88055,352 @@ void Tool_humbreak::addBreaks(HumdrumFile& infile) { ////////////////////////////// // -// Tool_humbreak::processFile -- +// Tool_gasparize::addMensuration -- // -void Tool_humbreak::processFile(HumdrumFile& infile) { - initialize(); - if (m_removeQ) { - removeBreaks(infile); - } else if (m_page2lineQ) { - convertPageToLine(infile); - } else { - addBreaks(infile); +void Tool_gasparize::addMensuration(int top, HumdrumFile& infile, int index) { + HTp checktoken = infile[index+1].token(0); + if (!checktoken) { + return; + } + if (checktoken->find("met") != string::npos) { + return; + } + int fieldcount = infile[index].getFieldCount(); + string line = "*"; + HTp token = infile[index].token(0); + if (token->isKern()) { + if (top == 2) { + line += "met(C|)"; + } else { + line += "met(O)"; + } + } + for (int i=1; iisKern()) { + if (top == 2) { + line += "met(C|)"; + } else { + line += "met(O)"; + } + } } + infile.insertLine(index+1, line); } - -////////////////////////////// +/////////////////////////////// // -// Tool_humbreak::removeBreaks -- +// Tool_gasparize::createEditText -- Convert markers into *edit interps. // -void Tool_humbreak::removeBreaks(HumdrumFile& infile) { - for (int i=0; icompare(0, 7, "!!LO:LB") == 0) { +void Tool_gasparize::createEditText(HumdrumFile& infile) { + // previous process manipulated the structure so reanalyze here for now: + infile.analyzeBaseFromTokens(); + infile.analyzeStructureNoRhythm(); + + int strands = infile.getStrandCount(); + for (int i=0; icompare(0, 7, "!!LO:PB") == 0) { + if (!sstart->isDataType("**text")) { continue; } - m_humdrum_text << infile[i] << endl; + HTp send = infile.getStrandEnd(i); + bool status = addEditStylingForText(infile, sstart, send); + if (status) { + infile.analyzeBaseFromTokens(); + infile.analyzeStructureNoRhythm(); + } } } - ////////////////////////////// // -// Tool_humbreak::convertPageToLine -- +// Tool_gasparize::addEditStylingForText -- // -void Tool_humbreak::convertPageToLine(HumdrumFile& infile) { +bool Tool_gasparize::addEditStylingForText(HumdrumFile& infile, HTp sstart, HTp send) { + HTp current = send->getPreviousToken(); + bool output = false; + string state = ""; + string laststate = ""; HumRegex hre; - for (int i=0; icompare(0, 7, "!!LO:PB") == 0) { - string text = *infile[i].token(0); - hre.replaceDestructive(text, "!!LO:LB", "!!LO:PB"); - m_humdrum_text << text << endl; + HTp lastdata = NULL; + bool italicQ = false; + while (current && (current != sstart)) { + if (!current->isData()) { + current = current->getPreviousToken(); continue; } - m_humdrum_text << infile[i] << endl; - } + if (current->isNull()) { + current = current->getPreviousToken(); + continue; + } + italicQ = false; + string text = current->getText(); + if (text.find("") != string::npos) { + italicQ = true; + hre.replaceDestructive(text, "", "", "g"); + hre.replaceDestructive(text, "", "", "g"); + current->setText(text); + } else { } + if (laststate == "") { + if (italicQ) { + laststate = "italic"; + } else { + laststate = "regular"; + } + current = current->getPreviousToken(); + continue; + } else { + if (italicQ) { + state = "italic"; + } else { + state = "regular"; + } + } + if (state != laststate) { + if (lastdata && (laststate == "italic")) { + output = true; + if (!insertEditText("*edit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) { + string line = getEditLine("*edit", lastdata->getFieldIndex(), lastdata->getOwner()); + infile.insertLine(lastdata->getLineIndex(), line); + } + } else if (lastdata && (laststate == "regular")) { + output = true; + if (!insertEditText("*Xedit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) { + string line = getEditLine("*Xedit", lastdata->getFieldIndex(), lastdata->getOwner()); + infile.insertLine(lastdata->getLineIndex(), line); + } + } + } + laststate = state; + lastdata = current; + current = current->getPreviousToken(); + } + if (lastdata && italicQ) { + // add *edit before first syllable in **text. + output = true; + if (!insertEditText("*edit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) { + string line = getEditLine("*edit", lastdata->getFieldIndex(), lastdata->getOwner()); + infile.insertLine(lastdata->getLineIndex(), line); + } + } - - -///////////////////////////////// -// -// Tool_humdiff::Tool_humdiff -- Set the recognized options for the tool. -// - -Tool_humdiff::Tool_humdiff(void) { - define("r|reference=i:1", "sequence number of reference score"); - define("report=b", "display report of differences"); - define("time-points|times=b", "display timepoint lists for each file"); - define("note-points|notes=b", "display notepoint lists for each file"); - define("c|color=s:red", "color for difference markers"); + return output; } ////////////////////////////// // -// Tool_humdiff::run -- +// Tool_gasparize::insertEditText -- // -bool Tool_humdiff::run(HumdrumFileSet& infiles) { - int reference = getInteger("reference") - 1; - if (reference < 0) { - cerr << "Error: reference has to be 1 or higher" << endl; - return false; - } - if (reference > infiles.getCount()) { - cerr << "Error: reference number is too large: " << reference << endl; - cerr << "Maximum is " << infiles.getCount() << endl; +bool Tool_gasparize::insertEditText(const string& text, HumdrumFile& infile, int line, int field) { + if (!infile[line].isInterpretation()) { return false; } - - if (infiles.getSize() == 0) { - cerr << "Usage: " << getCommand() << " files" << endl; - return false; - } else if (infiles.getSize() < 2) { - cerr << "Error: requires two or more files" << endl; - cerr << "Usage: " << getCommand() << " files" << endl; - return false; - } else { - HumNum targetdur = infiles[0].getScoreDuration(); - for (int i=1; iisNull()) { + continue; } - - if (!getBoolean("report")) { - infiles[reference].createLinesFromTokens(); - m_humdrum_text << infiles[reference]; - if (m_marked) { - m_humdrum_text << "!!!RDF**kern: @ = marked note"; - if (getBoolean("color")) { - m_humdrum_text << "color=\"" << getString("color") << "\""; - } - m_humdrum_text << endl; - } + if (token->find("edit") != string::npos) { + break; } + return false; } + token = infile.token(line, field); + token->setText(text); return true; } -////////////////////////////// +///////////////////// // -// Tool_humdiff::compareFiles -- +// Tool_gasparize::getEditLine -- // -void Tool_humdiff::compareFiles(HumdrumFile& reference, HumdrumFile& alternate) { - vector> timepoints(2); - extractTimePoints(timepoints.at(0), reference); - extractTimePoints(timepoints.at(1), alternate); - - if (getBoolean("time-points")) { - printTimePoints(timepoints[0]); - printTimePoints(timepoints[1]); +string Tool_gasparize::getEditLine(const string& text, int fieldindex, HLp line) { + string output; + for (int i=0; igetFieldCount()) { + output += "\t"; + } } - - compareTimePoints(timepoints, reference, alternate); -} - - - -////////////////////////////// -// -// Tool_humdiff::printTimePoints -- -// - -void Tool_humdiff::printTimePoints(vector& timepoints) { - for (int i=0; i<(int)timepoints.size(); i++) { - m_free_text << "TIMEPOINT " << i << ":" << endl; - m_free_text << timepoints[i] << endl; + output += text; + if (fieldindex < line->getFieldCount()) { + output += "\t"; + } + for (int i=fieldindex+1; igetFieldCount(); i++) { + output += "*"; + if (i < line->getFieldCount()) { + output += "\t"; + } } + return output; } ////////////////////////////// // -// Tool_humdiff::compareTimePoints -- +// adjustIntrumentNames -- // -void Tool_humdiff::compareTimePoints(vector>& timepoints, - HumdrumFile& reference, HumdrumFile& alternate) { - vector indexes(timepoints.size(), 0); - HumNum minval; - HumNum value; - int found; - - vector infiles(2, NULL); - infiles[0] = &reference; - infiles[1] = &alternate; - - vector increment(timepoints.size(), 0); - - while ((1)) { - if (indexes.at(0) >= (int)timepoints.at(0).size()) { - // at the end of the list of notes for the first file. - // break from the comparison for now and figure out how - // to report differences of added notes in the other file(s) - // later. +void Tool_gasparize::adjustIntrumentNames(HumdrumFile& infile) { + int instrumentLine = -1; + int abbrLine = -1; + for (int i=0; i= (int)timepoints.at(i).size()) { - continue; + for (int j=0; jcompare(0, 3, "*I\"") == 0) { + instrumentLine = i; } - value = timepoints.at(i).at(indexes.at(i)).timestamp; - if (value < minval) { - minval = value; + if (token->compare(0, 3, "*I'") == 0) { + abbrLine = i; } } - found = 0; - fill(increment.begin(), increment.end(), 0); - - for (int i=0; i<(int)timepoints.size(); i++) { - if (indexes.at(i) >= (int)timepoints.at(i).size()) { - // index is too large for file, so skip checking it. - continue; - } - found = 1; - value = timepoints.at(i).at(indexes.at(i)).timestamp; - - if (value == minval) { - timepoints.at(0).at(indexes.at(0)).index.at(i) = timepoints.at(i).at(indexes.at(i)).index.at(0); - increment.at(i)++; - } + } + if (instrumentLine < 0) { + return; + } + for (int i=0; isetText("*I\"Contratenor 1"); + } else if (*token == "*I\"CTI") { + token->setText("*I\"Contratenor 1"); + } else if (*token == "*I\"CTII") { + token->setText("*I\"Contratenor 2"); + } else if (*token == "*I\"CT II") { + token->setText("*I\"Contratenor 2"); + } else if (*token == "*I\"CT") { + token->setText("*I\"Contratenor"); + } else if (*token == "*I\"S") { + token->setText("*I\"Superius"); + } else if (*token == "*I\"A") { + token->setText("*I\"Altus"); + } else if (*token == "*I\"T") { + token->setText("*I\"Tenor"); + } else if (*token == "*I\"B") { + token->setText("*I\"Bassus"); + } else if (*token == "*I\"V") { + token->setText("*I\"Quintus"); + } else if (*token == "*I\"VI") { + token->setText("*I\"Sextus"); } - if (!found) { - break; + } + if (abbrLine >= 0) { + return; + } + string abbr; + HumRegex hre; + for (int i=0; i& notelist) { - m_free_text << "vvvvvvvvvvvvvvvvvvvvvvvvv" << endl; - for (int i=0; i<(int)notelist.size(); i++) { - m_free_text << "NOTE " << i << endl; - m_free_text << notelist.at(i) << endl; +void Tool_gasparize::removeKeyDesignations(HumdrumFile& infile) { + HumRegex hre; + for (int i=infile.getLineCount() - 1; i>=0; i--) { + if (!infile[i].isInterpretation()) { + continue; + } + for (int j=0; jisKern()) { + continue; + } + if (hre.search(token, "^\\*[A-Ga-g][#n-]*:$")) { + // suppress the key desingation + infile.deleteLine(i); + break; + } + } } - m_free_text << "^^^^^^^^^^^^^^^^^^^^^^^^^" << endl; - m_free_text << endl; -} - - - -////////////////////////////// -// -// Tool_humdiff::markNote -- mark the note (since it does not have a match in other edition(s). -// -void Tool_humdiff::markNote(NotePoint& np) { - m_marked = 1; - HTp token = np.token; - if (!token) { - return; - } - if (!token->isChord()) { - string contents = *token; - contents += "@"; - token->setText(contents); - return; - } - vector tokens = token->getSubtokens(); - tokens[np.subindex] += "@"; - string output = tokens[0]; - for (int i=1; i<(int)tokens.size(); i++) { - output += " "; - output += tokens[i]; - } - token->setText(output); } - ////////////////////////////// // -// Tool_humdiff::compareLines -- +// Tool_gasparize::fixBarlines -- Add final double barline and convert +// any intermediate final barlines to double barlines. // -void Tool_humdiff::compareLines(HumNum minval, vector& indexes, - vector>& timepoints, vector infiles) { - - bool reportQ = getBoolean("report"); - - // cerr << "COMPARING LINES ====================================" << endl; - vector> notelist(indexes.size()); +void Tool_gasparize::fixBarlines(HumdrumFile& infile) { + fixFinalBarline(infile); + HumRegex hre; - // Note: timepoints size must be 2 - // and infiles size must be 2 - for (int i=0; i<(int)timepoints.size(); i++) { - if (indexes.at(i) >= (int)timepoints.at(i).size()) { + for (int i=0; ifind("==") == string::npos) { + continue; } - } - } - - if (getBoolean("notes")) { - for (int i=0; i<(int)notelist.size(); i++) { - cerr << "========== NOTES FOR I=" << i << endl; - printNotePoints(notelist.at(i)); - cerr << endl; - } - } - - if (!reportQ) { - return; - } - - // report - for (int i=0; i<(int)notelist.at(0).size(); i++) { - for (int j=1; j<(int)notelist.at(0).at(i).matched.size(); j++) { - if (notelist.at(0).at(i).matched.at(j) < 0) { - cout << "NOTE " << notelist.at(0).at(i).subtoken - << " DOES NOT HAVE EXACT MATCH IN SOURCE " << j << endl; - int humindex = notelist.at(0).at(i).token->getLineIndex(); - cout << "\tREFERENCE MEASURE\t: " << notelist.at(0).at(i).measure << endl; - cout << "\tREFERENCE LINE NO.\t: " << humindex+1 << endl; - cout << "\tREFERENCE LINE TEXT\t: " << (*infiles[0])[humindex] << endl; - - cout << "\tTARGET " << j << " LINE NO. "; - if (j < 10) { - cout << " "; - } - cout << ":\t" << "X" << endl; - - cout << "\tTARGET " << j << " LINE TEXT"; - if (j < 10) { - cout << " "; - } - cout << ":\t" << "X" << endl; - - cout << endl; + if (hre.search(token, "^==(\\d*)")) { + string text = "="; + text += hre.getMatch(1); + text += "||"; + token->setText(text); } } } @@ -87932,195 +88410,132 @@ void Tool_humdiff::compareLines(HumNum minval, vector& indexes, ////////////////////////////// // -// Tool_humdiff::findNoteInList -- +// Tool_gasparize::fixFinalBarline -- // -int Tool_humdiff::findNoteInList(NotePoint& np, vector& nps) { - for (int i=0; i<(int)nps.size(); i++) { - // cerr << "COMPARING " << np.token << " (" << np.b40 << ") TO " << nps.at(i).token << " (" << nps.at(i).b40 << ") " << endl; - if (nps.at(i).processed) { - continue; +void Tool_gasparize::fixFinalBarline(HumdrumFile& infile) { + for (int i=infile.getLineCount() - 1; i>=0; i--) { + if (infile[i].isData()) { + break; } - if (nps.at(i).b40 != np.b40) { + if (!infile[i].isBarline()) { continue; } - if (nps.at(i).duration != np.duration) { - continue; + for (int j=0; jsetText("=="); + } } - return i; } - // cerr << "\tCannot find note " << np.token << " on line " << np.token->getLineIndex() << " in other work" << endl; - return -1; } - ////////////////////////////// // -// Tool_humdiff::getNoteList -- +// Tool_gasparize::createJEditorialAccidentals -- +// convert +// !LO:TX:a:t=( ) +// 4F# // -void Tool_humdiff::getNoteList(vector& notelist, HumdrumFile& infile, int line, int measure, int sourceindex, int tpindex) { - for (int i=0; iisKern()) { +void Tool_gasparize::createJEditorialAccidentals(HumdrumFile& infile) { + int strands = infile.getStrandCount(); + for (int i=0; iisNull()) { + if (!sstart->isKern()) { continue; } - if (token->isRest()) { + HTp send = infile.getStrandEnd(i); + createJEditorialAccidentals(sstart, send); + } +} + +void Tool_gasparize::createJEditorialAccidentals(HTp sstart, HTp send) { + HTp current = sstart->getNextToken(); + HumRegex hre; + while (current && (current != send)) { + if (!current->isCommentLocal()) { + current = current->getNextToken(); continue; } - int scount = token->getSubtokenCount(); - int track = token->getTrack(); - int layer = token->getSubtrack(); - for (int j=0; jgetSubtoken(j); - if (subtok.find("]") != string::npos) { - continue; - } - if (subtok.find("_") != string::npos) { - continue; - } - // found a note to store; - notelist.resize(notelist.size() + 1); - notelist.back().token = token; - notelist.back().subtoken = subtok; - notelist.back().subindex = j; - notelist.back().measurequarter = token->getDurationFromBarline(); - notelist.back().measure = - notelist.back().track = track; - notelist.back().layer = layer; - notelist.back().sourceindex = sourceindex; - notelist.back().tpindex = tpindex; - notelist.back().duration = token->getTiedDuration(); - notelist.back().b40 = Convert::kernToBase40(subtok); + if (hre.search(current, "^!LO:TX:a:t=\\(\\s*\\)$")) { + current->setText("!"); + convertNextNoteToJAccidental(current); } + current = current->getNextToken(); } } - - -////////////////////////////// -// -// Tool_humdiff::extractTimePoints -- Extract a list of the timestamps in a file. -// - -void Tool_humdiff::extractTimePoints(vector& points, HumdrumFile& infile) { - TimePoint tp; - points.clear(); +void Tool_gasparize::convertNextNoteToJAccidental(HTp current) { + current = current->getNextToken(); HumRegex hre; - points.reserve(infile.getLineCount()); - int measure = -1; - for (int i=0; iisData()) { + // Does not handle LO for non-data. + current = current->getNextToken(); continue; } - if (infile[i].getDuration() == 0) { - // ignore grace notes for now - continue; + if (current->isNull()) { + break; } - tp.clear(); - tp.file.push_back(&infile); - tp.index.push_back(i); - tp.timestamp = infile[i].getDurationFromStart(); - tp.measure = measure; - points.push_back(tp); + if (current->isRest()) { + break; + } + string text = *current; + if (hre.search(text, "i")) { + hre.replaceDestructive(text, "j", "i"); + current->setText(text); + break; + } else if (hre.search(text, "[-#n]")) { + hre.replaceDestructive(text, "$1j", "(.*[-#n]+)"); + current->setText(text); + break; + } else { + // Need to add a natural sign as well. + hre.replaceDestructive(text, "$1nj", "(.*[A-Ga-g]+)"); + current->setText(text); + break; + } + break; } + current = current->getNextToken(); } -////////////////////////////// -// -// operator<< == print a TimePoint -// - -ostream& operator<<(ostream& out, TimePoint& tp) { - out << "\ttimestamp:\t" << tp.timestamp.getFloat() << endl; - out << "\tmeasure:\t" << tp.measure << endl; - out << "\tindexes:\t" << endl; - for (int i=0; i<(int)tp.index.size(); i++) { - out << "\t\tindex " << i << " is:\t" << tp.index[i] << "\t" << (*tp.file[i])[tp.index[i]] << endl; - } - return out; -} - -////////////////////////////// +///////////////////////////////// // -// operator<< == print a NotePoint +// Tool_grep::Tool_grep -- Set the recognized options for the tool. // -ostream& operator<<(ostream& out, NotePoint& np) { - if (np.token) { - out << "\ttoken:\t\t" << np.token << endl; - } - out << "\ttoken index:\t" << np.subindex << endl; - if (!np.subtoken.empty()) { - out << "\tsubtoken:\t" << np.subtoken << endl; - } - out << "\tmeasure:\t" << np.measure << endl; - out << "\tmquarter:\t" << np.measurequarter << endl; - out << "\ttrack:\t\t" << np.track << endl; - out << "\tlayer:\t\t" << np.layer << endl; - out << "\tduration:\t" << np.duration << endl; - out << "\tb40:\t\t" << np.b40 << endl; - out << "\tprocessed:\t" << np.processed << endl; - out << "\tsourceindex:\t" << np.sourceindex << endl; - out << "\ttpindex:\t" << np.tpindex << endl; - out << "\tmatched:\t" << endl; - for (int i=0; i<(int)np.matched.size(); i++) { - out << "\t\tindex " << i << " is:\t" << np.matched[i] << endl; - } - return out; +Tool_grep::Tool_grep(void) { + define("v|remove-matching-lines=b", "remove lines that match regex"); + define("e|regex|regular-expression=s", "regular expression to search with"); } - - - ///////////////////////////////// // -// Tool_humsheet::Tool_humsheet -- Set the recognized options for the tool. +// Tool_grep::run -- Do the main work of the tool. // -Tool_humsheet::Tool_humsheet(void) { - define("h|H|html|HTML=b", "output table in HTML wrapper"); - define("i|id|ID=b", "include ID for each cell"); - define("z|zebra=b", "add zebra striping by spine to style"); - define("y|z2|zebra2|zebra-2=b", "zebra striping by data type"); - define("t|tab-index=b", "vertical tab indexing"); - define("X|no-exinterp=b", "do not embed exclusive interp data"); - define("J|no-javascript=b", "do not embed javascript code"); - define("S|no-style=b", "do not embed CSS style element"); +bool Tool_grep::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i\n"; +void Tool_grep::processFile(HumdrumFile& infile) { + HumRegex hre; + bool match; for (int i=0; i"; - printRowContents(infile, i); - m_free_text << "\n"; - } - m_free_text << ""; - if (m_htmlQ) { - if (m_javascriptQ) { - printJavascript(); + match = hre.search(infile[i], m_regex); + if (m_negateQ) { + if (match) { + continue; + } + } else { + if (!match) { + continue; + } } - printHtmlFooter(); + m_humdrum_text << infile[i] << "\n"; } } -////////////////////////////// + +///////////////////////////////// // -// Tool_humsheet::printTitle -- +// Tool_half::Tool_half -- Set the recognized options for the tool. // -void Tool_humsheet::printTitle(HumdrumFile& infile, int line) { - if (!infile[line].isReference()) { - return; - } - string meaning = Convert::getReferenceKeyMeaning(infile[line].token(0)); - if (!meaning.empty()) { - m_free_text << " title=\"" << meaning << "\""; - } +Tool_half::Tool_half(void) { + define("l|lyric-beam-break=b", "Break beams at syllable starts"); } -////////////////////////////// +///////////////////////////////// // -// Tool_humsheet::printRowData -- +// Tool_half::run -- Primary interfaces to the tool. // -void Tool_humsheet::printRowData(HumdrumFile& infile, int line) { - m_free_text << " data-line=\"" << line << "\""; +bool Tool_half::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i\n"; - m_free_text << "\n"; - m_free_text << "\n"; - m_free_text << "UNTITLED\n"; - m_free_text << "\n"; - m_free_text << "\n"; - m_free_text << "\n"; +bool Tool_half::run(HumdrumFile& infile) { + processFile(infile); + + // Re-load the text for each line from their tokens. + infile.createLinesFromTokens(); + + // Need to adjust the line numbers for tokens for later + // processing. + m_humdrum_text << infile; + return true; } -/////////////////////////////// +////////////////////////////// // -// printHtmlFooter -- +// Tool_half::processFile -- // -void Tool_humsheet::printHtmlFooter(void) { - m_free_text << "\n"; - m_free_text << "\n"; +void Tool_half::processFile(HumdrumFile& infile) { + m_lyricBreakQ = getBoolean("lyric-beam-break"); + terminalLongToTerminalBreve(infile); + halfRhythms(infile); + adjustBeams(infile); } -/////////////////////////////// +////////////////////////////// // -// printRowClasses -- +// Tool_half::adjustBeams -- // -void Tool_humsheet::printRowClasses(HumdrumFile& infile, int row) { - string classes; - HLp hl = &infile[row]; - if (hl->hasSpines()) { - classes += "spined "; - } - if (hl->isEmpty()) { - classes += "empty "; - } - if (hl->isData()) { - classes += "data "; - } - if (hl->isInterpretation()) { - classes += "interp "; - HTp token = hl->token(0); - if (token->compare(0, 2, "*>") == 0) { - classes += "label "; - } - } - if (hl->isLocalComment()) { - classes += "lcomment "; - if (isLayout(hl)) { - classes += "layout "; - } - } - HTp token = hl->token(0); - if (token->compare(0, 2, "!!") == 0) { - if ((token->size() == 2) || (token->at(3) != '!')) { - classes += "gcommet "; - } - } - - if (hl->isUniversalReference()) { - if (token->compare(0, 11, "!!!!filter:") == 0) { - classes += "ufilter "; - } else if (token->compare(0, 12, "!!!!Xfilter:") == 0) { - classes += "usedufilter "; - } else { - classes += "ureference "; - if (token->compare(0, 12, "!!!!SEGMENT:") == 0) { - classes += "segment "; - } - } - } else if (hl->isCommentUniversal()) { - classes += "ucomment "; - } else if (hl->isReference()) { - classes += "reference "; - } else if (hl->isGlobalComment()) { - HTp token = hl->token(0); - if (token->compare(0, 10, "!!!filter:") == 0) { - classes += "filter "; - } else if (token->compare(0, 11, "!!!Xfilter:") == 0) { - classes += "usedfilter "; - } else { - classes += "gcomment "; - if (isLayout(hl)) { - classes += "layout "; - } - } - } - - if (hl->isBarline()) { - classes += "barline "; - } - if (hl->isManipulator()) { - HTp token = hl->token(0); - if (token->compare(0, 2, "**") == 0) { - classes += "exinterp "; - } else { - classes += "manip "; - } - } - if (!classes.empty()) { - // remove space. - classes.resize((int)classes.size() - 1); - m_free_text << " class=\"" << classes << "\""; +void Tool_half::adjustBeams(HumdrumFile& infile) { + Tool_autobeam autobeam; + vector argv; + argv.push_back("autobeam"); + if (m_lyricBreakQ) { + argv.push_back("-l"); } + autobeam.process(argv); + autobeam.run(infile); } ////////////////////////////// // -// Tool_humsheet::isLayout -- check to see if any cell -// starts with "!LO:". +// Tool_half::halfRhythms -- // -bool Tool_humsheet::isLayout(HLp line) { - if (line->hasSpines()) { - if (!line->isCommentLocal()) { - return false; - } - for (int i=0; igetFieldCount(); i++) { - HTp token = line->token(i); - if (token->compare(0, 4, "!LO:") == 0) { - return true; +void Tool_half::halfRhythms(HumdrumFile& infile) { + HumRegex hre; + for (int i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + + string text = *token; + // extract duration without dot + HumNum durnodot = Convert::recipToDurationNoDots(text); + durnodot /= 2; + string newrhythm = Convert::durationToRecip(durnodot); + hre.replaceDestructive(text, newrhythm, "\\d+%?\\d*"); + token->setText(text); + } + } else if (infile[i].isInterpretation()) { + // half time signatures + for (int j=0; jsetText(text); + } else { + string text = *token; + string replacement = "/" + to_string(bot1); + replacement += "%" + to_string(bot2); + hre.replaceDestructive(text, replacement, "/\\d+"); + token->setText(text); + } + } else if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) { + int bot = hre.getMatchInt(2); + if (bot == 4) { + bot = 8; + } else if (bot == 2) { + bot = 4; + } else if (bot == 3) { + bot = 6; + } else if (bot == 1) { + bot = 2; + } else if (bot == 0) { + bot = 1; + } else { + cerr << "Warning: ignored time signature: " << token << endl; + } + string text = *token; + string replacement = "/" + to_string(bot); + hre.replaceDestructive(text, replacement, "/\\d+"); + token->setText(text); + } } - } - } else { - HTp token = line->token(0); - if (token->compare(0, 5, "!!LO:") == 0) { - return true; } } - return false; } -/////////////////////////////// +////////////////////////////// // -// Tool_humsheet::printRowContents -- +// Tool_half::terminalLongToTerminalBreve -- // -void Tool_humsheet::printRowContents(HumdrumFile& infile, int row) { - int fieldcount = infile[row].getFieldCount(); - for (int i=0; ifind("terminal long") == string::npos) { + continue; } - m_free_text << ">"; - printToken(token); - m_free_text << ""; + string text = *token; + hre.replaceDestructive(text, "terminal breve", "terminal long", "g"); + token->setText(text); } } -////////////////////////////// + +///////////////////////////////// // -// Tool_humsheet::printCellData -- +// Tool_hands::Tool_hands -- Set the recognized options for the tool. // -void Tool_humsheet::printCellData(HTp token) { - int field = token->getFieldIndex(); - m_free_text << " data-field=\"" << field << "\""; - - - if (token->getOwner()->hasSpines()) { - int spine = token->getTrack() - 1; - m_free_text << " data-spine=\"" << spine << "\""; - - int subspine = token->getSubtrack(); - if (subspine > 0) { - m_free_text << " data-subspine=\"" << subspine << "\""; - } - - string exinterp = token->getDataType().substr(2); - if (m_exinterpQ && !exinterp.empty()) { - m_free_text << " data-x=\"" << exinterp << "\""; - } - } +Tool_hands::Tool_hands(void) { + define("c|color=b", "color right-hand notes red and left-hand notes blue"); + define("lcolor|left-color=s:dodgerblue", "color of left-hand notes"); + define("rcolor|right-color=s:crimson", "color of right-hand notes"); + define("l|left-only=b", "remove right-hand notes"); + define("r|right-only=b", "remove left-hand notes"); + define("m|mark=b", "mark left and right-hand notes"); + define("a|attacks-only=b", "only mark note attacks and not note sustains"); } ////////////////////////////// // -// Tool_humsheet::printToken -- +// Tool_hands::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -void Tool_humsheet::printToken(HTp token) { - for (int i=0; i<(int)token->size(); i++) { - switch (token->at(i)) { - case '>': - m_free_text << ">"; - break; - case '<': - m_free_text << "<"; - break; - default: - m_free_text << token->at(i); - } - } +void Tool_hands::initialize(void) { + m_colorQ = getBoolean("color"); + m_leftColor = getString("left-color"); + m_rightColor = getString("right-color"); + m_leftOnlyQ = getBoolean("left-only"); + m_rightOnlyQ = getBoolean("right-only"); + m_markQ = getBoolean("mark"); + m_attacksOnlyQ = getBoolean("attacks-only"); } -/////////////////////////////// +///////////////////////////////// // -// Tool_humsheet::printId -- +// Tool_hands::run -- Do the main work of the tool. // -void Tool_humsheet::printId(HTp token) { - int line = token->getLineNumber(); - int field = token->getFieldNumber(); - string id = "tok-L"; - id += to_string(line); - id += "F"; - id += to_string(field); - m_free_text << " id=\"" << id << "\""; +bool Tool_hands::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; igetValue("auto", "tabindex"); - if (number.empty()) { - return; + +bool Tool_hands::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } - m_free_text << " tabindex=\"" << number << "\""; + return status; +} + + +bool Tool_hands::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_humsheet::printColspan -- print any necessary colspan values for -// token (to align by primary spines) +// Tool_hands::processFile -- // -void Tool_humsheet::printColSpan(HTp token) { - if (!token->getOwner()->hasSpines()) { - m_free_text << " colspan=\"" << m_max_field << "\""; - return; - } - int track = token->getTrack() - 1; - int scount = m_max_subtrack.at(track); - int subtrack = token->getSubtrack(); - if (subtrack > 1) { - subtrack--; +void Tool_hands::processFile(HumdrumFile& infile) { + if (m_markQ || m_leftOnlyQ || m_rightOnlyQ) { + infile.doHandAnalysis(m_attacksOnlyQ); } - HTp nexttok = token->getNextFieldToken(); - int ntrack = -1; - if (nexttok) { - ntrack = nexttok->getTrack() - 1; + if (m_leftOnlyQ) { + removeNotes(infile, "RH"); + } else if (m_rightOnlyQ) { + removeNotes(infile, "LH"); } - if ((ntrack < 0) || (ntrack != track)) { - // at the end of a primary spine, so do a colspan with the remaining subtracks - if (subtrack < scount-1) { - int colspan = scount - subtrack; - m_free_text << " colspan=\"" << colspan << "\""; - } - } else { - // do nothing + if (m_colorQ) { + colorHands(infile); + } else if (m_markQ) { + markNotes(infile); } + m_humdrum_text << infile; } -/////////////////////////////// +////////////////////////////// // -// printCellClasses -- +// Tool_hands::removeNotes -- +// + +void Tool_hands::removeNotes(HumdrumFile& infile, const string& htype) { + int counter = 0; + int scount = infile.getStrandCount(); + for (int i=0; igetExclusiveInterpretation(); + int hasHandMarkup = xtok->getValueInt("auto", "hand"); + if (!hasHandMarkup) { + continue; + } + HTp send = infile.getStrandEnd(i); + removeNotes(sstart, send, htype); + counter++; + } + + + if (counter) { + infile.createLinesFromTokens(); + } +} + + +void Tool_hands::removeNotes(HTp sstart, HTp send, const string& htype) { + HTp current = sstart; + while (current && (current != send)) { + if (!current->isData() || current->isNull()) { + current = current->getNextToken(); + continue; + } + + HumRegex hre; + string ttype = current->getValue("auto", "hand"); + if (ttype != htype) { + current = current->getNextToken(); + continue; + } + string text = *current; + hre.replaceDestructive(text, "", "[^0-9.%q ]", "g"); + hre.replaceDestructive(text, "ryy ", " ", "g"); + text += "ryy"; + current->setText(text); + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_hands::markNotes -- +// + +void Tool_hands::markNotes(HumdrumFile& infile) { + HumRegex hre; + + int counter = 0; + int scount = infile.getStrandCount(); + for (int i=0; igetExclusiveInterpretation(); + int hasHandMarkup = xtok->getValueInt("auto", "hand"); + if (!hasHandMarkup) { + continue; + } + HTp send = infile.getStrandEnd(i); + markNotes(sstart, send); + counter++; + } + + if (counter) { + infile.appendLine("!!!RDF**kern: " + m_leftMarker + " = marked note, color=\"" + m_leftColor + "\", left-hand note"); + infile.appendLine("!!!RDF**kern: " + m_rightMarker + " = marked note, color=\"" + m_rightColor + "\", right-hand note"); + infile.createLinesFromTokens(); + } +} + + +void Tool_hands::markNotes(HTp sstart, HTp send) { + HTp current = sstart; + while (current && (current != send)) { + if (!current->isData() || current->isNull() || current->isRest()) { + current = current->getNextToken(); + continue; + } + + HumRegex hre; + string text = *current; + string htype = current->getValue("auto", "hand"); + if (htype == "LH") { + hre.replaceDestructive(text, " " + m_leftMarker, " +", "g"); + text = m_leftMarker + text; + } else if (htype == "RH") { + hre.replaceDestructive(text, " " + m_rightMarker, " +", "g"); + text = m_rightMarker + text; + } + current->setText(text); + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_hands::colorHands -- Convert for example *LH into *color:dodgerblue. +// + +void Tool_hands::colorHands(HumdrumFile& infile) { + string left = "*color:" + m_leftColor; + string right = "*color:" + m_rightColor; + for (int i=0; iisKern()) { + continue; + } + if (*token == "*LH") { + token->setText(left); + changed = true; + } + if (*token == "*RH") { + token->setText(right); + changed = true; + } + } + if (changed) { + infile[i].createLineFromTokens(); + } + } +} + + + + +///////////////////////////////// +// +// Tool_homorhythm::Tool_homorhythm -- Set the recognized options for the tool. +// + +Tool_homorhythm::Tool_homorhythm(void) { + define("a|append=b", "append analysis to end of input data"); + define("attacks=b", "append attack counts for each sonority"); + define("p|prepend=b", "prepend analysis to end of input data"); + define("r|raw-sonority=b", "display individual sonority scores only"); + define("raw-score=b", "display accumulated scores"); + define("M|no-marks=b", "do not mark homorhythm section notes"); + define("f|fraction=b", "calculate fraction of music that is homorhythm"); + define("v|voice=b", "display voice information or fraction results"); + define("F|filename=b", "show filename for f option"); + define("n|t|threshold=d:4.0", "threshold score sum required for homorhythm texture detection"); + define("s|score=d:1.0", "score assigned to a sonority with three or more attacks"); + define("m|intermediate-score=d:0.5", "score to give sonority between two adjacent attack sonoroties"); + define("l|letter=b", "display letter scoress before calculations"); +} + + + +///////////////////////////////// +// +// Tool_homorhythm::run -- Do the main work of the tool. +// + +bool Tool_homorhythm::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i m_score) { + m_intermediate_score = m_score; + } + +} + + + +////////////////////////////// +// +// Tool_homorhythm::processFile -- +// + +void Tool_homorhythm::processFile(HumdrumFile& infile) { + vector data; + data.reserve(infile.getLineCount()); + + m_homorhythm.clear(); + m_homorhythm.resize(infile.getLineCount()); + + m_notecount.clear(); + m_notecount.resize(infile.getLineCount()); + fill(m_notecount.begin(), m_notecount.end(), 0); + + m_attacks.clear(); + m_attacks.resize(infile.getLineCount()); + fill(m_attacks.begin(), m_attacks.end(), 0); + + m_notes.clear(); + m_notes.resize(infile.getLineCount()); + + for (int i=0; i score(infile.getLineCount(), 0); + vector raw(infile.getLineCount(), 0); + + double sum = 0.0; + for (int i=0; i<(int)data.size(); i++) { + if (m_homorhythm[data[i]].find("Y") != string::npos) { + if (m_homorhythm[data[i]].find("N") != string::npos) { + // sonority between two homorhythm-like sonorities. + // maybe also differentiate based on metric position. + sum += m_intermediate_score; + raw[data[i]] = m_intermediate_score; + } else { + sum += m_score; + raw[data[i]] = m_score; + } + } else { + sum = 0.0; + } + score[data[i]] = sum; + } + + for (int i=(int)data.size()-2; i>=0; i--) { + if (score[data[i]] == 0) { + continue; + } + if (score[data[i+1]] > score[data[i]]) { + score[data[i]] = score[data[i+1]]; + } + } + + if (getBoolean("raw-score")) { + addAccumulatedScores(infile, score); + } + + if (getBoolean("raw-sonority")) { + addRawAnalysis(infile, raw); + } + if (getBoolean("raw-score")) { + addAccumulatedScores(infile, score); + } + + if (getBoolean("fraction")) { + addFractionAnalysis(infile, score); + } + + if (getBoolean("attacks")) { + addAttacks(infile, m_attacks); + } + + if (!getBoolean("fraction")) { + // Color the notes within homorhythm textures. + // mark homorhythm regions in red, + // non-homorhythm sonorities within these regions in green + // and non-homorhythm regions in black. + if (m_letterQ) { + infile.appendDataSpine(m_homorhythm, "", "**hp"); + } + for (int i=0; i<(int)data.size(); i++) { + if (score[data[i]] >= m_threshold) { + if (m_attacks[data[i]] < (int)m_notes[data[i]].size() - 1) { + m_homorhythm[data[i]] = "dodgerblue"; + } else { + m_homorhythm[data[i]] = "red"; + } + } else { + m_homorhythm[data[i]] = "black"; + } + } + infile.appendDataSpine(m_homorhythm, "", "**color"); + + // problem with **color spine in javascript, so output via humdrum text + m_humdrum_text << infile; + } + +} + + + +////////////////////////////// +// +// Tool_homorhythm::addAccumulatedScores -- +// + +void Tool_homorhythm::addAccumulatedScores(HumdrumFile& infile, vector& score) { + infile.appendDataSpine(score, "", "**score", false); +} + + + +////////////////////////////// +// +// Tool_homorhythm::addRawAnalysis -- +// + +void Tool_homorhythm::addRawAnalysis(HumdrumFile& infile, vector& raw) { + infile.appendDataSpine(raw, "", "**raw", false); +} + + + +////////////////////////////// +// +// Tool_homorhythm::addAttacks -- +// + +void Tool_homorhythm::addAttacks(HumdrumFile& infile, vector& attacks) { + infile.appendDataSpine(attacks, "", "**atks"); +} + + + +////////////////////////////// +// +// Tool_homorhythm::addFractionAnalysis -- +// + +void Tool_homorhythm::addFractionAnalysis(HumdrumFile& infile, vector& score) { + double sum = 0.0; + for (int i=0; i m_threshold) { + sum += infile[i].getDuration().getFloat(); + } + } + double total = infile.getScoreDuration().getFloat(); + int ocount = getOriginalVoiceCount(infile); + double fraction = sum / total; + double percent = int(fraction * 1000.0 + 0.5)/10.0; + if (getBoolean("filename")) { + m_free_text << infile.getFilename() << "\t"; + } + if (getBoolean("voice")) { + m_free_text << ocount; + m_free_text << "\t"; + m_free_text << m_voice_count; + m_free_text << "\t"; + if (ocount == m_voice_count) { + m_free_text << "complete" << "\t"; + } else { + m_free_text << "incomplete" << "\t"; + } + } + if (m_voice_count < 2) { + m_free_text << -1; + } else { + m_free_text << percent; + } + m_free_text << endl; +} + + + +////////////////////////////// +// +// Tool_homorhythm::getOriginalVoiceCount -- +// + +int Tool_homorhythm::getOriginalVoiceCount(HumdrumFile& infile) { + HumRegex hre; + for (int i=0; i spines = infile.getKernSpineStartList(); + return (int)spines.size(); +} + + + +////////////////////////////// +// +// Tool_homorhythm::analyzeLine -- +// + +void Tool_homorhythm::analyzeLine(HumdrumFile& infile, int line) { + m_notes[line].reserve(10); + HPNote note; + if (!infile[line].isData()) { + return; + } + int nullQ = 0; + for (int i=0; iisKern()) { + continue; + } + if (token->isRest()) { + continue; + } + if (token->isNull()) { + nullQ = 1; + token = token->resolveNull(); + if (!token) { + continue; + } + if (token->isRest()) { + continue; + } + } else { + nullQ = 0; + } + int track = token->getTrack(); + vector subtokens = token->getSubtokens(); + for (int j=0; j<(int)subtokens.size(); j++) { + note.track = track; + note.line = token->getLineIndex(); + note.field = token->getFieldIndex(); + note.subfield = j; + note.token = token; + note.text = subtokens[j]; + note.duration = Convert::recipToDuration(note.text); + if (nullQ) { + note.attack = false; + note.nullQ = true; + } else { + note.nullQ = false; + if ((note.text.find("_") != string::npos) || + (note.text.find("]") != string::npos)) { + note.attack = false; + } else { + note.attack = true; + } + } + m_notes[line].push_back(note); + } + } + + // There must be at least three attacks to be considered homorhythm + // maybe adjust to N-1 or three voices, or a similar rule. + vector adurs; + for (int i=0; i<(int)m_notes[line].size(); i++) { + if (m_notes[line][i].attack) { + adurs.push_back(m_notes[line][i].duration); + m_attacks[line]++; + } + } + // if ((int)m_attacks[line] >= (int)m_notes[line].size() - 1) { + if ((int)m_attacks[line] >= 3) { + string value = "Y"; + // value += to_string(m_attacks[line]); + m_homorhythm[line] = value; + } else if ((m_voice_count == 3) && (m_attacks[line] == 2)) { + if ((adurs.size() >= 2) && (adurs[0] == adurs[1])) { + m_homorhythm[line] = "Y"; + } else { + m_homorhythm[line] = "N"; + } + } else { + string value = "N"; + // value += to_string(m_attacks[line]); + m_homorhythm[line] = value; + } + // redundant or three-or-more case: + if (m_notes[line].size() <= 2) { + m_homorhythm[line] = "N"; + } +} + + + + +///////////////////////////////// +// +// Tool_homorhythm2::Tool_homorhythm -- Set the recognized options for the tool. +// + +Tool_homorhythm2::Tool_homorhythm2(void) { + define("t|threshold=d:1.6", "threshold score sum required for homorhythm texture detection"); + define("u|threshold2=d:1.3", "threshold score sum required for semi-homorhythm texture detection"); + define("s|score=b", "show numeric scores"); + define("n|length=i:4", "sonority length to calculate"); + define("f|fraction=b", "report fraction of music that is homorhythm"); +} + + + +///////////////////////////////// +// +// Tool_homorhythm2::run -- Do the main work of the tool. +// + +bool Tool_homorhythm2::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; iisRest()) { + continue; + } + NoteCell* cell2 = grid.cell(k, i+m); + if (cell2->isRest()) { + continue; + } + count++; + if (cell1->isAttack() && cell2->isAttack()) { + score += 1.0; + } + } + } + } + int index = grid.getLineIndex(i); + m_score[index] = score / count; + } + + for (int i=grid.getSliceCount()-1; i>=wsize; i--) { + score = 0; + count = 0; + for (int j=0; jisRest()) { + continue; + } + NoteCell* cell2 = grid.cell(k, i-m); + if (cell2->isRest()) { + continue; + } + count++; + if (cell1->isAttack() && cell2->isAttack()) { + score += 1.0; + } + } + } + } + int index = grid.getLineIndex(i); + m_score[index] += score / count; + } + + + for (int i=0; i<(int)m_score.size(); i++) { + m_score[i] = int(m_score[i] * 100.0 + 0.5) / 100.0; + } + + + vector color(infile.getLineCount());; + for (int i=0; i= m_threshold) { + color[i] = "red"; + } else if (m_score[i] >= m_threshold2) { + color[i] = "orange"; + } else { + color[i] = "black"; + } + } + + if (getBoolean("fraction")) { + HumNum sum = 0; + HumNum total = infile.getScoreDuration(); + for (int i=0; i<(int)m_score.size(); i++) { + if (m_score[i] >= m_threshold2) { + sum += infile[i].getDuration(); + } + } + HumNum fraction = sum / total; + m_free_text << int(fraction.getFloat() * 1000.0 + 0.5) / 10.0 << endl; + } else { + if (getBoolean("score")) { + infile.appendDataSpine(m_score, ".", "**cdata", false); + } + infile.appendDataSpine(color, ".", "**color", true); + infile.createLinesFromTokens(); + + // problem within emscripten-compiled version, so force to output as string: + m_humdrum_text << infile; + } + +} + + + + + + +///////////////////////////////// +// +// Tool_gridtest::Tool_hproof -- Set the recognized options for the tool. +// + +Tool_hproof::Tool_hproof(void) { + // put option definitions here +} + + + +/////////////////////////////// +// +// Tool_hproof::run -- Primary interfaces to the tool. +// + +bool Tool_hproof::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i list; + infile.getSpineStartList(list); + vector hlist; + for (auto it : list) { + if (*it == "**harm") { + hlist.push_back(it); + } + if (*it == "**rhrm") { + hlist.push_back(it); + } + } + if (hlist.empty()) { + cerr << "Warning: No **harm or **rhrm spines in data" << endl; + return; + } + + processHarmSpine(infile, hlist[0]); +} + + + +////////////////////////////// +// +// processHarmSpine -- +// + +void Tool_hproof::processHarmSpine(HumdrumFile& infile, HTp hstart) { + string key = "*C:"; // assume C major if no key designation + HTp token = hstart; + HTp ntoken = token->getNextNNDT(); + while (token) { + markNotesInRange(infile, token, ntoken, key); + if (!ntoken) { + break; + } + if (ntoken && token) { + getNewKey(token, ntoken, key); + } + token = ntoken; + ntoken = ntoken->getNextNNDT(); + } +} + + + +////////////////////////////// +// +// Tool_hproof::getNewKey -- +// + +void Tool_hproof::getNewKey(HTp token, HTp ntoken, string& key) { + token = token->getNextToken(); + while (token && (token != ntoken)) { + if (token->isKeyDesignation()) { + key = *token; + } + token = token->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_hproof::markNotesInRange -- +// + +void Tool_hproof::markNotesInRange(HumdrumFile& infile, HTp ctoken, HTp ntoken, const string& key) { + if (!ctoken) { + return; + } + int startline = ctoken->getLineIndex(); + int stopline = infile.getLineCount(); + if (ntoken) { + stopline = ntoken->getLineIndex(); + } + vector cts; + cts = Convert::harmToBase40(ctoken, key); + for (int i=startline; iisKern()) { + continue; + } + HTp tok = infile.token(i, j); + if (tok->isNull()) { + continue; + } + if (tok->isRest()) { + continue; + } + markHarmonicTones(tok, cts); + } + } + +// cerr << "TOK\t" << ctoken << "\tLINES\t" << startline << "\t" << stopline << "\t"; +// for (int i=0; i& cts) { + int count = tok->getSubtokenCount(); + vector notes = cts; + string output; + for (int i=0; igetSubtoken(i); + int pitch = Convert::kernToBase40(subtok); + if (i > 0) { + output += " "; + } + bool found = false; + for (int j=0; j<(int)cts.size(); j++) { + if (pitch % 40 == cts[j] % 40) { + output += subtok; + output += "Z"; + found = true; + break; + } + } + if (!found) { + output += subtok; + output += "N"; + } + } + tok->setText(output); +} + + + + + +///////////////////////////////// +// +// Tool_humbreak::Tool_humbreak -- Set the recognized options for the tool. +// + +Tool_humbreak::Tool_humbreak(void) { + define("m|measures=s", "measures numbers to place linebreaks before"); + define("p|page-breaks=s", "measure numbers to place page breaks before"); + define("g|group=s:original", "line/page break group"); + define("r|remove|remove-breaks=b", "remove line/page breaks"); + define("l|page-to-line-breaks=b", "convert page breaks to line breaks"); +} + + + +///////////////////////////////// +// +// Tool_humbreak::run -- Do the main work of the tool. +// + +bool Tool_humbreak::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i lbs; + vector pbs; + HumRegex hre; + hre.split(lbs, systemMeasures, "[^\\da-z]+"); + hre.split(pbs, pageMeasures, "[^\\da-z]+"); + + for (int i=0; i<(int)lbs.size(); i++) { + if (hre.search(lbs[i], "^(p?)(\\d+)([a-z]?)")) { + int number = hre.getMatchInt(2); + if (!hre.getMatch(1).empty()) { + m_pageMeasures[number] = 1; + int offset = 0; + string letter; + if (!hre.getMatch(3).empty()) { + letter = hre.getMatch(3); + offset = letter.at(0) - 'a'; + } + m_pageOffset[number] = offset; + } else { + m_lineMeasures[number] = 1; + int offset = 0; + if (!hre.getMatch(3).empty()) { + string letter = hre.getMatch(3); + offset = letter.at(0) - 'a'; + } + m_lineOffset[number] = offset; + } + } + } + + for (int i=0; i<(int)pbs.size(); i++) { + if (hre.search(pbs[i], "^(\\d+)([a-z]?)")) { + int number = hre.getMatchInt(1); + m_pageMeasures[number] = 1; + int offset = 0; + if (!hre.getMatch(2).empty()) { + string letter = hre.getMatch(2); + offset = letter.at(0) - 'a'; + } + m_pageOffset[number] = offset; + } + } +} + + + +////////////////////////////// +// +// Tool_humbreak::markLineBreakMeasures -- +// + +void Tool_humbreak::markLineBreakMeasures(HumdrumFile& infile) { + vector pbreak; + vector lbreak; + HumRegex hre; + map used; + + for (int i=0; isetValue("auto", "barnum", barnum + 1); + } else { + line->setValue("auto", "barnum", barnum + 1); + } + } else { + line->setValue("auto", "barnum", barnum + 1); + } + } + + status = m_pageMeasures[barnum]; + if (status) { + HLp line = &infile[i]; + int offset = m_pageOffset[barnum]; + if (offset) { + int ocounter = 0; + lbreak.clear(); + pbreak.clear(); + for (int j=i+1; jsetValue("auto", "barnum", barnum + 1); + pbreak.back()->setValue("auto", "page", 1); + } + } else { + line->setValue("auto", "barnum", barnum + 1); + line->setValue("auto", "page", 1); + } + } + } +} + + + +////////////////////////////// +// +// Tool_humbreak::addBreaks -- +// + +void Tool_humbreak::addBreaks(HumdrumFile& infile) { + markLineBreakMeasures(infile); + + HumRegex hre; + for (int i=0; iisBarline()) { + int measure = infile[i+1].getBarNumber(); + int pbStatus = m_pageMeasures[measure]; + if (pbStatus) { + string query = "\\b" + m_group + "\\b"; + if (!hre.match(token, query)) { + m_humdrum_text << token << ", " << m_group << endl; + } else { + m_humdrum_text << token << endl; + } + } else { + m_humdrum_text << token << endl; + } + m_humdrum_text << infile[i+1] << endl; + i++; + continue; + } + } else if (hre.search(token, "^!!LO:LB:")) { + // Add group to existing LO:LB: + HTp token = infile.token(i, 0); + HTp barToken = infile.token(i+1, 0); + if (barToken->isBarline()) { + int measure = infile[i+1].getBarNumber(); + int lbStatus = m_lineMeasures[measure]; + if (lbStatus) { + string query = "\\b" + m_group + "\\b"; + if (!hre.match(token, query)) { + m_humdrum_text << token << ", " << m_group << endl; + } else { + m_humdrum_text << token << endl; + } + } else { + m_humdrum_text << token << endl; + } + m_humdrum_text << infile[i+1] << endl; + i++; + continue; + } + } + } + + if (pageQ) { + m_humdrum_text << "!!LO:PB:g=" << m_group << endl; + } else { + m_humdrum_text << "!!LO:LB:g=" << m_group << endl; + } + m_humdrum_text << infile[i] << endl; + } +} + + + +////////////////////////////// +// +// Tool_humbreak::processFile -- +// + +void Tool_humbreak::processFile(HumdrumFile& infile) { + initialize(); + if (m_removeQ) { + removeBreaks(infile); + } else if (m_page2lineQ) { + convertPageToLine(infile); + } else { + addBreaks(infile); + } +} + + + +////////////////////////////// +// +// Tool_humbreak::removeBreaks -- +// + +void Tool_humbreak::removeBreaks(HumdrumFile& infile) { + for (int i=0; icompare(0, 7, "!!LO:LB") == 0) { + continue; + } + if (infile[i].token(0)->compare(0, 7, "!!LO:PB") == 0) { + continue; + } + m_humdrum_text << infile[i] << endl; + } +} + + + +////////////////////////////// +// +// Tool_humbreak::convertPageToLine -- +// + +void Tool_humbreak::convertPageToLine(HumdrumFile& infile) { + HumRegex hre; + for (int i=0; icompare(0, 7, "!!LO:PB") == 0) { + string text = *infile[i].token(0); + hre.replaceDestructive(text, "!!LO:LB", "!!LO:PB"); + m_humdrum_text << text << endl; + continue; + } + m_humdrum_text << infile[i] << endl; + } +} + + + + +///////////////////////////////// +// +// Tool_humdiff::Tool_humdiff -- Set the recognized options for the tool. +// + +Tool_humdiff::Tool_humdiff(void) { + define("r|reference=i:1", "sequence number of reference score"); + define("report=b", "display report of differences"); + define("time-points|times=b", "display timepoint lists for each file"); + define("note-points|notes=b", "display notepoint lists for each file"); + define("c|color=s:red", "color for difference markers"); +} + + + +////////////////////////////// +// +// Tool_humdiff::run -- +// + +bool Tool_humdiff::run(HumdrumFileSet& infiles) { + int reference = getInteger("reference") - 1; + if (reference < 0) { + cerr << "Error: reference has to be 1 or higher" << endl; + return false; + } + if (reference > infiles.getCount()) { + cerr << "Error: reference number is too large: " << reference << endl; + cerr << "Maximum is " << infiles.getCount() << endl; + return false; + } + + if (infiles.getSize() == 0) { + cerr << "Usage: " << getCommand() << " files" << endl; + return false; + } else if (infiles.getSize() < 2) { + cerr << "Error: requires two or more files" << endl; + cerr << "Usage: " << getCommand() << " files" << endl; + return false; + } else { + HumNum targetdur = infiles[0].getScoreDuration(); + for (int i=1; i> timepoints(2); + extractTimePoints(timepoints.at(0), reference); + extractTimePoints(timepoints.at(1), alternate); + + if (getBoolean("time-points")) { + printTimePoints(timepoints[0]); + printTimePoints(timepoints[1]); + } + + compareTimePoints(timepoints, reference, alternate); +} + + + +////////////////////////////// +// +// Tool_humdiff::printTimePoints -- +// + +void Tool_humdiff::printTimePoints(vector& timepoints) { + for (int i=0; i<(int)timepoints.size(); i++) { + m_free_text << "TIMEPOINT " << i << ":" << endl; + m_free_text << timepoints[i] << endl; + } +} + + + +////////////////////////////// +// +// Tool_humdiff::compareTimePoints -- +// + +void Tool_humdiff::compareTimePoints(vector>& timepoints, + HumdrumFile& reference, HumdrumFile& alternate) { + vector indexes(timepoints.size(), 0); + HumNum minval; + HumNum value; + int found; + + vector infiles(2, NULL); + infiles[0] = &reference; + infiles[1] = &alternate; + + vector increment(timepoints.size(), 0); + + while ((1)) { + if (indexes.at(0) >= (int)timepoints.at(0).size()) { + // at the end of the list of notes for the first file. + // break from the comparison for now and figure out how + // to report differences of added notes in the other file(s) + // later. + break; + } + timepoints.at(0).at(indexes.at(0)).index.resize(timepoints.size()); + for (int i=1; i<(int)timepoints.size(); i++) { + timepoints.at(0).at(indexes.at(0)).index.at(i) = -1; + } + minval = timepoints.at(0).at(indexes.at(0)).timestamp; + for (int i=1; i<(int)timepoints.size(); i++) { + if (indexes.at(i) >= (int)timepoints.at(i).size()) { + continue; + } + value = timepoints.at(i).at(indexes.at(i)).timestamp; + if (value < minval) { + minval = value; + } + } + found = 0; + fill(increment.begin(), increment.end(), 0); + + for (int i=0; i<(int)timepoints.size(); i++) { + if (indexes.at(i) >= (int)timepoints.at(i).size()) { + // index is too large for file, so skip checking it. + continue; + } + found = 1; + value = timepoints.at(i).at(indexes.at(i)).timestamp; + + if (value == minval) { + timepoints.at(0).at(indexes.at(0)).index.at(i) = timepoints.at(i).at(indexes.at(i)).index.at(0); + increment.at(i)++; + } + } + if (!found) { + break; + } else { + compareLines(minval, indexes, timepoints, infiles); + } + for (int i=0; i<(int)increment.size(); i++) { + indexes.at(i) += increment.at(i); + } + } +} + + + +////////////////////////////// +// +// Tool_humdiff::printNotePoints -- +// + +void Tool_humdiff::printNotePoints(vector& notelist) { + m_free_text << "vvvvvvvvvvvvvvvvvvvvvvvvv" << endl; + for (int i=0; i<(int)notelist.size(); i++) { + m_free_text << "NOTE " << i << endl; + m_free_text << notelist.at(i) << endl; + } + m_free_text << "^^^^^^^^^^^^^^^^^^^^^^^^^" << endl; + m_free_text << endl; +} + + + +////////////////////////////// +// +// Tool_humdiff::markNote -- mark the note (since it does not have a match in other edition(s). +// + +void Tool_humdiff::markNote(NotePoint& np) { + m_marked = 1; + HTp token = np.token; + if (!token) { + return; + } + if (!token->isChord()) { + string contents = *token; + contents += "@"; + token->setText(contents); + return; + } + vector tokens = token->getSubtokens(); + tokens[np.subindex] += "@"; + string output = tokens[0]; + for (int i=1; i<(int)tokens.size(); i++) { + output += " "; + output += tokens[i]; + } + token->setText(output); +} + + + +////////////////////////////// +// +// Tool_humdiff::compareLines -- +// + +void Tool_humdiff::compareLines(HumNum minval, vector& indexes, + vector>& timepoints, vector infiles) { + + bool reportQ = getBoolean("report"); + + // cerr << "COMPARING LINES ====================================" << endl; + vector> notelist(indexes.size()); + + // Note: timepoints size must be 2 + // and infiles size must be 2 + for (int i=0; i<(int)timepoints.size(); i++) { + if (indexes.at(i) >= (int)timepoints.at(i).size()) { + continue; + } + if (timepoints.at(i).at(indexes.at(i)).timestamp != minval) { + // not at the same time + continue; + } + + getNoteList(notelist.at(i), *infiles[i], + timepoints.at(i).at(indexes.at(i)).index[0], + timepoints.at(i).at(indexes.at(i)).measure, i, indexes.at(i)); + + + } + for (int i=0; i<(int)notelist.at(0).size(); i++) { + notelist.at(0).at(i).matched.resize(notelist.size()); + fill(notelist.at(0).at(i).matched.begin(), notelist.at(0).at(i).matched.end(), -1); + notelist.at(0).at(i).matched.at(0) = i; + for (int j=1; j<(int)notelist.size(); j++) { + int status = findNoteInList(notelist.at(0).at(i), notelist.at(j)); + notelist.at(0).at(i).matched.at(j) = status; + if ((status < 0) && !reportQ) { + markNote(notelist.at(0).at(i)); + } + } + } + + if (getBoolean("notes")) { + for (int i=0; i<(int)notelist.size(); i++) { + cerr << "========== NOTES FOR I=" << i << endl; + printNotePoints(notelist.at(i)); + cerr << endl; + } + } + + if (!reportQ) { + return; + } + + // report + for (int i=0; i<(int)notelist.at(0).size(); i++) { + for (int j=1; j<(int)notelist.at(0).at(i).matched.size(); j++) { + if (notelist.at(0).at(i).matched.at(j) < 0) { + cout << "NOTE " << notelist.at(0).at(i).subtoken + << " DOES NOT HAVE EXACT MATCH IN SOURCE " << j << endl; + int humindex = notelist.at(0).at(i).token->getLineIndex(); + cout << "\tREFERENCE MEASURE\t: " << notelist.at(0).at(i).measure << endl; + cout << "\tREFERENCE LINE NO.\t: " << humindex+1 << endl; + cout << "\tREFERENCE LINE TEXT\t: " << (*infiles[0])[humindex] << endl; + + cout << "\tTARGET " << j << " LINE NO. "; + if (j < 10) { + cout << " "; + } + cout << ":\t" << "X" << endl; + + cout << "\tTARGET " << j << " LINE TEXT"; + if (j < 10) { + cout << " "; + } + cout << ":\t" << "X" << endl; + + cout << endl; + } + } + } +} + + + +////////////////////////////// +// +// Tool_humdiff::findNoteInList -- +// + +int Tool_humdiff::findNoteInList(NotePoint& np, vector& nps) { + for (int i=0; i<(int)nps.size(); i++) { + // cerr << "COMPARING " << np.token << " (" << np.b40 << ") TO " << nps.at(i).token << " (" << nps.at(i).b40 << ") " << endl; + if (nps.at(i).processed) { + continue; + } + if (nps.at(i).b40 != np.b40) { + continue; + } + if (nps.at(i).duration != np.duration) { + continue; + } + return i; + } + // cerr << "\tCannot find note " << np.token << " on line " << np.token->getLineIndex() << " in other work" << endl; + return -1; +} + + + + +////////////////////////////// +// +// Tool_humdiff::getNoteList -- +// + +void Tool_humdiff::getNoteList(vector& notelist, HumdrumFile& infile, int line, int measure, int sourceindex, int tpindex) { + for (int i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + if (token->isRest()) { + continue; + } + int scount = token->getSubtokenCount(); + int track = token->getTrack(); + int layer = token->getSubtrack(); + for (int j=0; jgetSubtoken(j); + if (subtok.find("]") != string::npos) { + continue; + } + if (subtok.find("_") != string::npos) { + continue; + } + // found a note to store; + notelist.resize(notelist.size() + 1); + notelist.back().token = token; + notelist.back().subtoken = subtok; + notelist.back().subindex = j; + notelist.back().measurequarter = token->getDurationFromBarline(); + notelist.back().measure = + notelist.back().track = track; + notelist.back().layer = layer; + notelist.back().sourceindex = sourceindex; + notelist.back().tpindex = tpindex; + notelist.back().duration = token->getTiedDuration(); + notelist.back().b40 = Convert::kernToBase40(subtok); + } + } +} + + + +////////////////////////////// +// +// Tool_humdiff::extractTimePoints -- Extract a list of the timestamps in a file. +// + +void Tool_humdiff::extractTimePoints(vector& points, HumdrumFile& infile) { + TimePoint tp; + points.clear(); + HumRegex hre; + points.reserve(infile.getLineCount()); + int measure = -1; + for (int i=0; i\n"; + for (int i=0; i"; + printRowContents(infile, i); + m_free_text << "\n"; + } + m_free_text << ""; + if (m_htmlQ) { + if (m_javascriptQ) { + printJavascript(); + } + printHtmlFooter(); + } +} + + + +////////////////////////////// +// +// Tool_humsheet::printTitle -- +// + +void Tool_humsheet::printTitle(HumdrumFile& infile, int line) { + if (!infile[line].isReference()) { + return; + } + string meaning = Convert::getReferenceKeyMeaning(infile[line].token(0)); + if (!meaning.empty()) { + m_free_text << " title=\"" << meaning << "\""; + } +} + + + +////////////////////////////// +// +// Tool_humsheet::printRowData -- +// + +void Tool_humsheet::printRowData(HumdrumFile& infile, int line) { + m_free_text << " data-line=\"" << line << "\""; +} + + + +/////////////////////////////// +// +// printHtmlHeader -- +// + +void Tool_humsheet::printHtmlHeader(void) { + m_free_text << "\n"; + m_free_text << "\n"; + m_free_text << "\n"; + m_free_text << "UNTITLED\n"; + m_free_text << "\n"; + m_free_text << "\n"; + m_free_text << "\n"; +} + + + +/////////////////////////////// +// +// printHtmlFooter -- +// + +void Tool_humsheet::printHtmlFooter(void) { + m_free_text << "\n"; + m_free_text << "\n"; +} + + + +/////////////////////////////// +// +// printRowClasses -- +// + +void Tool_humsheet::printRowClasses(HumdrumFile& infile, int row) { + string classes; + HLp hl = &infile[row]; + if (hl->hasSpines()) { + classes += "spined "; + } + if (hl->isEmpty()) { + classes += "empty "; + } + if (hl->isData()) { + classes += "data "; + } + if (hl->isInterpretation()) { + classes += "interp "; + HTp token = hl->token(0); + if (token->compare(0, 2, "*>") == 0) { + classes += "label "; + } + } + if (hl->isLocalComment()) { + classes += "lcomment "; + if (isLayout(hl)) { + classes += "layout "; + } + } + HTp token = hl->token(0); + if (token->compare(0, 2, "!!") == 0) { + if ((token->size() == 2) || (token->at(3) != '!')) { + classes += "gcommet "; + } + } + + if (hl->isUniversalReference()) { + if (token->compare(0, 11, "!!!!filter:") == 0) { + classes += "ufilter "; + } else if (token->compare(0, 12, "!!!!Xfilter:") == 0) { + classes += "usedufilter "; + } else { + classes += "ureference "; + if (token->compare(0, 12, "!!!!SEGMENT:") == 0) { + classes += "segment "; + } + } + } else if (hl->isCommentUniversal()) { + classes += "ucomment "; + } else if (hl->isReference()) { + classes += "reference "; + } else if (hl->isGlobalComment()) { + HTp token = hl->token(0); + if (token->compare(0, 10, "!!!filter:") == 0) { + classes += "filter "; + } else if (token->compare(0, 11, "!!!Xfilter:") == 0) { + classes += "usedfilter "; + } else { + classes += "gcomment "; + if (isLayout(hl)) { + classes += "layout "; + } + } + } + + if (hl->isBarline()) { + classes += "barline "; + } + if (hl->isManipulator()) { + HTp token = hl->token(0); + if (token->compare(0, 2, "**") == 0) { + classes += "exinterp "; + } else { + classes += "manip "; + } + } + if (!classes.empty()) { + // remove space. + classes.resize((int)classes.size() - 1); + m_free_text << " class=\"" << classes << "\""; + } +} + + + +////////////////////////////// +// +// Tool_humsheet::isLayout -- check to see if any cell +// starts with "!LO:". +// + +bool Tool_humsheet::isLayout(HLp line) { + if (line->hasSpines()) { + if (!line->isCommentLocal()) { + return false; + } + for (int i=0; igetFieldCount(); i++) { + HTp token = line->token(i); + if (token->compare(0, 4, "!LO:") == 0) { + return true; + } + } + } else { + HTp token = line->token(0); + if (token->compare(0, 5, "!!LO:") == 0) { + return true; + } + } + return false; +} + + + +/////////////////////////////// +// +// Tool_humsheet::printRowContents -- +// + +void Tool_humsheet::printRowContents(HumdrumFile& infile, int row) { + int fieldcount = infile[row].getFieldCount(); + for (int i=0; i"; + printToken(token); + m_free_text << ""; + } +} + + + +////////////////////////////// +// +// Tool_humsheet::printCellData -- +// + +void Tool_humsheet::printCellData(HTp token) { + int field = token->getFieldIndex(); + m_free_text << " data-field=\"" << field << "\""; + + + if (token->getOwner()->hasSpines()) { + int spine = token->getTrack() - 1; + m_free_text << " data-spine=\"" << spine << "\""; + + int subspine = token->getSubtrack(); + if (subspine > 0) { + m_free_text << " data-subspine=\"" << subspine << "\""; + } + + string exinterp = token->getDataType().substr(2); + if (m_exinterpQ && !exinterp.empty()) { + m_free_text << " data-x=\"" << exinterp << "\""; + } + } +} + + + +////////////////////////////// +// +// Tool_humsheet::printToken -- +// + +void Tool_humsheet::printToken(HTp token) { + for (int i=0; i<(int)token->size(); i++) { + switch (token->at(i)) { + case '>': + m_free_text << ">"; + break; + case '<': + m_free_text << "<"; + break; + default: + m_free_text << token->at(i); + } + } +} + + + +/////////////////////////////// +// +// Tool_humsheet::printId -- +// + +void Tool_humsheet::printId(HTp token) { + int line = token->getLineNumber(); + int field = token->getFieldNumber(); + string id = "tok-L"; + id += to_string(line); + id += "F"; + id += to_string(field); + m_free_text << " id=\"" << id << "\""; +} + + + +/////////////////////////////// +// +// Tool_humsheet::printTabIndex -- +// + +void Tool_humsheet::printTabIndex(HTp token) { + string number = token->getValue("auto", "tabindex"); + if (number.empty()) { + return; + } + m_free_text << " tabindex=\"" << number << "\""; +} + + + +////////////////////////////// +// +// Tool_humsheet::printColspan -- print any necessary colspan values for +// token (to align by primary spines) +// + +void Tool_humsheet::printColSpan(HTp token) { + if (!token->getOwner()->hasSpines()) { + m_free_text << " colspan=\"" << m_max_field << "\""; + return; + } + int track = token->getTrack() - 1; + int scount = m_max_subtrack.at(track); + int subtrack = token->getSubtrack(); + if (subtrack > 1) { + subtrack--; + } + HTp nexttok = token->getNextFieldToken(); + int ntrack = -1; + if (nexttok) { + ntrack = nexttok->getTrack() - 1; + } + if ((ntrack < 0) || (ntrack != track)) { + // at the end of a primary spine, so do a colspan with the remaining subtracks + if (subtrack < scount-1) { + int colspan = scount - subtrack; + m_free_text << " colspan=\"" << colspan << "\""; + } + } else { + // do nothing + } +} + + + +/////////////////////////////// +// +// printCellClasses -- // void Tool_humsheet::printCellClasses(HTp token) { @@ -95404,843 +98036,1597 @@ void Tool_mei2hum::processNodeStartLinks(string& output, xml_node node, // duration of the note/rest/chord is calculated. // -void Tool_mei2hum::processNodeStartLinks2(xml_node node, - vector& nodelist) { - for (int i=0; i<(int)nodelist.size(); i++) { - string nodename = nodelist[i].name(); - if (nodename == "tupletSpan") { - parseTupletSpanStart(node, nodelist[i]); +void Tool_mei2hum::processNodeStartLinks2(xml_node node, + vector& nodelist) { + for (int i=0; i<(int)nodelist.size(); i++) { + string nodename = nodelist[i].name(); + if (nodename == "tupletSpan") { + parseTupletSpanStart(node, nodelist[i]); + } + } +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseTupletSpanStart -- +// Such as: +// +// + +void Tool_mei2hum::parseTupletSpanStart(xml_node node, + xml_node tupletSpan) { + NODE_VERIFY(tupletSpan, ) + + if (strcmp(tupletSpan.attribute("endid").value(), "") == 0) { + cerr << "Warning: requires endid attribute (at least "; + cerr << "for this parser)" << endl; + return; + } + + if (strcmp(tupletSpan.attribute("startid").value(), "") == 0) { + cerr << "Warning: requires startid attribute (at least "; + cerr << "for this parser)" << endl; + return; + } + + string num = tupletSpan.attribute("num").value(); + string numbase = tupletSpan.attribute("numbase").value(); + + HumNum newfactor = 1; + + if (numbase == "") { + cerr << "Warning: tuplet@numbase is empty" << endl; + } else { + newfactor = stoi(numbase); + } + + if (num == "") { + cerr << "Warning: tuplet@num is empty" << endl; + } else { + newfactor /= stoi(num); + } + + m_tupletfactor *= newfactor; + +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseTupletSpanStop -- +// Such as: +// +// + +void Tool_mei2hum::parseTupletSpanStop(string& output, xml_node node, + xml_node tupletSpan) { + NODE_VERIFY(tupletSpan, ) + + if (strcmp(tupletSpan.attribute("endid").value(), "") == 0) { + return; + } + if (strcmp(tupletSpan.attribute("startid").value(), "") == 0) { + return; + } + + string num = tupletSpan.attribute("num").value(); + string numbase = tupletSpan.attribute("numbase").value(); + + HumNum newfactor = 1; + + if (numbase == "") { + cerr << "Warning: tuplet@numbase is empty" << endl; + } else { + newfactor = stoi(numbase); + } + + if (num == "") { + cerr << "Warning: tuplet@num is empty" << endl; + } else { + newfactor /= stoi(num); + } + + // undo the tuplet factor: + m_tupletfactor /= newfactor; + +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseArpeg -- Only handles single chord arpeggiation for now +// (ignores @endid). +// + +void Tool_mei2hum::parseArpeg(string& output, xml_node node, xml_node arpeg) { + NODE_VERIFY(arpeg, ) + + if (strcmp(arpeg.attribute("endid").value(), "") != 0) { + cerr << "Warning: multi-note arpeggios are not yet handled in the converter." << endl; + } + + string nodename = node.name(); + if (nodename == "note") { + output += ':'; + } else if (nodename == "chord") { + string temp = output; + output.clear(); + for (int i=0; i<(int)temp.size(); i++) { + if (temp[i] == ' ') { + output += ": "; + } else { + output += temp[i]; + } + } + output += ':'; + } else { + cerr << DKHTP << "an arpeggio attached to a " + << nodename << " element" << endl; + return; + } + +} + + + +////////////////////////////// +// +// Tool_mei2hum::processNodeStopLinks -- +// + +void Tool_mei2hum::processNodeStopLinks(string& output, xml_node node, + vector& nodelist) { + for (int i=0; i<(int)nodelist.size(); i++) { + string nodename = nodelist[i].name(); + if (nodename == "slur") { + parseSlurStop(output, node, nodelist[i]); + } else if (nodename == "tie") { + parseTieStop(output, node, nodelist[i]); + } else if (nodename == "tupletSpan") { + parseTupletSpanStop(output, node, nodelist[i]); + } else { + cerr << DKHTP << nodename + << " element in processNodeStopLinks()" << endl; + } + } +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseSlurStart -- +// + +void Tool_mei2hum::parseSlurStart(string& output, xml_node node, xml_node slur) { + NODE_VERIFY(slur, ) + string nodename = node.name(); + if (nodename == "note") { + output = "(" + setPlacement(slur.attribute("curvedir").value()) + output; + } else if (nodename == "chord") { + output = "(" + setPlacement(slur.attribute("curvedir").value()) + output; + } else { + cerr << DKHTP << "a slur start attached to a " + << nodename << " element" << endl; + return; + } + +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseSlurStop -- +// + +void Tool_mei2hum::parseSlurStop(string& output, xml_node node, xml_node slur) { + NODE_VERIFY(slur, ) + string nodename = node.name(); + if (nodename == "note") { + output += ")"; + } else if (nodename == "chord") { + output += ")"; + } else { + cerr << DKHTP << "a tie end attached to a " + << nodename << " element" << endl; + return; + } +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseTieStart -- Need to deal with chords later. +// + +void Tool_mei2hum::parseTieStart(string& output, xml_node node, xml_node tie) { + NODE_VERIFY(tie, ) + + string id = node.attribute("xml:id").value(); + if (!id.empty()) { + auto found = m_stoplinks.find(id); + if (found != m_stoplinks.end()) { + for (auto item : (*found).second) { + if (strcmp(tie.attribute("startid").value(), item.attribute("endid").value()) == 0) { + // deal with tie middles in parseTieStop(). + return; + } + } + } + } + + string nodename = node.name(); + if (nodename == "note") { + output = "[" + output; + } else { + cerr << DKHTP << "a tie start attached to a " + << nodename << " element" << endl; + return; + } +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseTrill -- +// + +void Tool_mei2hum::parseTrill(string& output, xml_node node, xml_node trill) { + NODE_VERIFY(trill, ) + + auto loc = output.find(";"); + if (loc != string::npos) { + output.insert(loc, "T"); + return; + } + + loc = output.find(")"); + if (loc != string::npos) { + output.insert(loc, "T"); + return; + } + + output += "T"; + + // Deal with endid attribute on trills later. +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseTieStop -- Need to deal with chords later. +// + +void Tool_mei2hum::parseTieStop(string& output, xml_node node, xml_node tie) { + NODE_VERIFY(tie, ) + + string id = node.attribute("xml:id").value(); + if (!id.empty()) { + auto found = m_startlinks.find(id); + if (found != m_startlinks.end()) { + for (auto item : (*found).second) { + if (strcmp(tie.attribute("endid").value(), item.attribute("startid").value()) == 0) { + output += "_"; + return; + } + } + } + } + + string nodename = node.name(); + if (nodename == "note") { + output += "]"; + } else { + cerr << DKHTP << "a tie end attached to a " + << nodename << " element" << endl; + return; + } +} + + + +////////////////////////////// +// +// Tool_mei2hum::parseFermata -- deal with a fermata attached to something. +/// output is a Humdrum token string (maybe have it as a HumdrumToken object). +// + +void Tool_mei2hum::parseFermata(string& output, xml_node node, xml_node fermata) { + NODE_VERIFY(fermata, ) + + string nodename = node.name(); + if (nodename == "note") { + output += ';'; + } else if (nodename == "chord") { + output += ';'; + } else if (nodename == "rest") { + output += ';'; + } else { + cerr << DKHTP << "a fermata attached to a " + << nodename << " element" << endl; + return; + } + +} + + + +////////////////////////////// +// +// Tool_mei2hum::getHumdrumRecip -- +// + +string Tool_mei2hum::getHumdrumRecip(HumNum duration, int dotcount) { + string output; + + if (dotcount > 0) { + // remove dots from duration + int top = (1 << (dotcount+1)) - 1; + int bot = 1 << dotcount; + HumNum dotfactor(bot, top); + duration *= dotfactor; + } + + if (duration.getNumerator() == 1) { + output = to_string(duration.getDenominator()); + } else if ((duration.getNumerator() == 2) && (duration.getDenominator() == 1)) { + // breve symbol: + output = "0"; + } else if ((duration.getNumerator() == 4) && (duration.getDenominator() == 1)) { + // long symbol: + output = "00"; + } else if ((duration.getNumerator() == 8) && (duration.getDenominator() == 1)) { + // maxima symbol: + output = "000"; + } else { + output = to_string(duration.getDenominator()); + output += "%"; + output += to_string(duration.getNumerator()); + } + + for (int i=0; i& children) { + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename != "accid") { + continue; + } + string func = children[i].attribute("func").value(); + if (func == "caution") { + // cautionary accidental handled elsewhere + return ""; + } else if (func == "edit") { + // editorial accidental handled elsewhere + return ""; + } + string accid = children[i].attribute("accid").value(); + return accid; + } + return ""; +} + + + +////////////////////////////// +// +// Tool_mei2hum::getChildAccidGes -- Return the accid@accid.ges value +// of any element in the input list, but not if the accidental is +// part of an cautionary or editorial accidental. +// + +string Tool_mei2hum::getChildAccidGes(vector& children) { + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename != "accid") { + continue; + } + string func = children[i].attribute("func").value(); + if (func == "caution") { + // cautionary accidental handled elsewhere + return ""; + } else if (func == "edit") { + // editorial accidental handled elsewhere + return ""; + } + string accidges = children[i].attribute("accid.ges").value(); + return accidges; + } + return ""; +} + + + +////////////////////////////// +// +// Tool_mei2hum::getHumdrumPitch -- +// + +string Tool_mei2hum::getHumdrumPitch(xml_node note, vector& children) { + string pname = note.attribute("pname").value(); + string accidvis = note.attribute("accid").value(); + string accidges = note.attribute("accid.ges").value(); + + string accidvischild = getChildAccidVis(children); + string accidgeschild = getChildAccidGes(children); + + int octnum = 4; + string oct = note.attribute("oct").value(); + if (oct == "") { + cerr << "Empty octave" << endl; + } else if (isdigit(oct[0])) { + octnum = stoi(oct); + } else { + cerr << "Unknown octave value: " << oct << endl; + } + + if (pname == "") { + cerr << "Empty pname" << endl; + return "x"; + } + + string output; + if (octnum < 4) { + char val = toupper(pname[0]); + int count = 4 - octnum; + for (int i=0; i +// Tool_mei2hum::getDuration -- Get duration from note or chord. If chord does not +// have @dur then use @dur of first note in children elements. // -void Tool_mei2hum::parseTupletSpanStart(xml_node node, - xml_node tupletSpan) { - NODE_VERIFY(tupletSpan, ) - - if (strcmp(tupletSpan.attribute("endid").value(), "") == 0) { - cerr << "Warning: requires endid attribute (at least "; - cerr << "for this parser)" << endl; - return; +HumNum Tool_mei2hum::getDuration(xml_node element) { + xml_attribute dur_attr = element.attribute("dur"); + string name = element.name(); + if ((!dur_attr) && (name == "note")) { + // real notes must have durations, but this one + // does not, so assign zero duration + return 0; + } + if ((!dur_attr) && (name == "chord")) { + // if there is no dur attribute on a chord, then look for it + // on the first note subelement of the chord. + auto newelement = element.select_node(".//note").node(); + if (newelement) { + element = newelement; + dur_attr = element.attribute("dur"); + name = element.name(); + } else { + return 0; + } } - if (strcmp(tupletSpan.attribute("startid").value(), "") == 0) { - cerr << "Warning: requires startid attribute (at least "; - cerr << "for this parser)" << endl; - return; + string dur = dur_attr.value(); + if (dur == "") { + return 0; } - string num = tupletSpan.attribute("num").value(); - string numbase = tupletSpan.attribute("numbase").value(); + HumNum output; + if (dur == "breve") { + output = 2; + } else if (dur == "long") { + output = 4; + } else if (dur == "maxima") { + output = 8; + } else if (isdigit(dur[0])) { + output = 1; + output /= stoi(dur); + } else { + cerr << "Unknown " << element.name() << "@dur: " << dur << endl; + return 0; + } - HumNum newfactor = 1; + if (output == 0) { + cerr << "Error: zero duration for note" << endl; + } - if (numbase == "") { - cerr << "Warning: tuplet@numbase is empty" << endl; + int dotcount; + string dots = element.attribute("dots").value(); + if (dots == "") { + dotcount = 0; + } else if (isdigit(dots[0])) { + dotcount = stoi(dots); } else { - newfactor = stoi(numbase); + cerr << "Unknown " << element.name() << "@dotcount: " << dur << endl; + return 0; } - if (num == "") { - cerr << "Warning: tuplet@num is empty" << endl; - } else { - newfactor /= stoi(num); + if (dotcount > 0) { + int top = (1 << (dotcount+1)) - 1; + int bot = 1 << dotcount; + HumNum dotfactor(top, bot); + output *= dotfactor; } - m_tupletfactor *= newfactor; + // add a correction for the tuplet factor which is currently active. + if (m_tupletfactor != 1) { + output *= m_tupletfactor; + } + return output; } ////////////////////////////// // -// Tool_mei2hum::parseTupletSpanStop -- -// Such as: -// +// Tool_mei2hum::getDuration_mensural -- Get duration from note or chord. If chord does not +// have @dur then use @dur of first note in children elements. +// +// @dur: https://music-encoding.org/guidelines/v4/data-types/data.duration.mensural.html +// X = maxima +// L = longa +// S = brevis +// s = semibrevis +// M = minima +// m = semiminima +// U = fusa +// u = semifusa +// @dur.quality: +// i = imperfecta :: remove augmentation dot +// p = perfecta :: add augmentation dot +// altera = altera :: duration is double the rhythmic value of notes // -void Tool_mei2hum::parseTupletSpanStop(string& output, xml_node node, - xml_node tupletSpan) { - NODE_VERIFY(tupletSpan, ) +HumNum Tool_mei2hum::getDuration_mensural(xml_node element, int& dotcount) { + dotcount = 0; - if (strcmp(tupletSpan.attribute("endid").value(), "") == 0) { - return; + xml_attribute dur_qual = element.attribute("dur.quality"); + xml_attribute dur_attr = element.attribute("dur"); + string name = element.name(); + + if ((!dur_attr) && (name == "note")) { + // real notes must have durations, but this one + // does not, so assign zero duration + return 0; } - if (strcmp(tupletSpan.attribute("startid").value(), "") == 0) { - return; + if ((!dur_attr) && (name == "chord")) { + // if there is no dur attribute on a chord, then look for it + // on the first note subelement of the chord. + auto newelement = element.select_node(".//note").node(); + if (newelement) { + element = newelement; + dur_attr = element.attribute("dur"); + name = element.name(); + dur_qual = element.attribute("dur.quality"); + } else { + return 0; + } } - string num = tupletSpan.attribute("num").value(); - string numbase = tupletSpan.attribute("numbase").value(); - - HumNum newfactor = 1; - - if (numbase == "") { - cerr << "Warning: tuplet@numbase is empty" << endl; - } else { - newfactor = stoi(numbase); + string dur = dur_attr.value(); + if (dur == "") { + return 0; } + string durquality = dur_qual.value(); - if (num == "") { - cerr << "Warning: tuplet@num is empty" << endl; + char rhythm = '\0'; + if (dur == "maxima") { + rhythm = 'X'; + } else if (dur == "longa") { + rhythm = 'L'; + } else if (dur == "brevis") { + rhythm = 'S'; + } else if (dur == "semibrevis") { + rhythm = 's'; + } else if (dur == "minima") { + rhythm = 'M'; + } else if (dur == "semiminima") { + rhythm = 'm'; + } else if (dur == "fusa") { + rhythm = 'U'; + } else if (dur == "semifusa") { + rhythm = 'u'; } else { - newfactor /= stoi(num); + cerr << "Error: unknown rhythm" << element.name() << "@dur: " << dur << endl; + return 0; } - // undo the tuplet factor: - m_tupletfactor /= newfactor; + mei_staffDef& ss = m_scoreDef.staves.at(m_currentStaff - 1); + int maximodus = ss.maximodus == 3 ? 3 : 2; + int modus = ss.modus == 3 ? 3 : 2; + int tempus = ss.tempus == 3 ? 3 : 2; + int prolatio = ss.prolatio == 3 ? 3 : 2; + + bool altera = false; + bool perfecta = false; + bool imperfecta = false; + + if (durquality == "imperfecta") { + imperfecta = true; + } else if (durquality == "perfecta") { + perfecta = true; + } else if (durquality == "altera") { + altera = true; + } + HumNum output = Convert::mensToDuration(rhythm, altera, perfecta, imperfecta, maximodus, modus, tempus, prolatio); + return output; } + ////////////////////////////// // -// Tool_mei2hum::parseArpeg -- Only handles single chord arpeggiation for now -// (ignores @endid). +// Tool_mei2hum::parseVerse -- // -void Tool_mei2hum::parseArpeg(string& output, xml_node node, xml_node arpeg) { - NODE_VERIFY(arpeg, ) +void Tool_mei2hum::parseVerse(xml_node verse, GridStaff* staff) { + NODE_VERIFY(verse, ) + MAKE_CHILD_LIST(children, verse); - if (strcmp(arpeg.attribute("endid").value(), "") != 0) { - cerr << "Warning: multi-note arpeggios are not yet handled in the converter." << endl; + string n = verse.attribute("n").value(); + int nnum = 1; + if (n.empty()) { + cerr << "Warning: no layer number on layer element" << endl; + } else { + nnum = stoi(n); + } + if (nnum < 1) { + cerr << "Warning: invalid layer number: " << nnum << endl; + cerr << "Setting it to 1." << endl; + nnum = 1; } - string nodename = node.name(); - if (nodename == "note") { - output += ':'; - } else if (nodename == "chord") { - string temp = output; - output.clear(); - for (int i=0; i<(int)temp.size(); i++) { - if (temp[i] == ' ') { - output += ": "; - } else { - output += temp[i]; + string versetext; + int sylcount = 0; + + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename == "syl") { + if (sylcount > 0) { + versetext += " "; } + sylcount++; + versetext += parseSyl(children[i]); + } else { + cerr << DKHTP << verse.name() << "/" << nodename << CURRLOC << endl; } - output += ':'; - } else { - cerr << DKHTP << "an arpeggio attached to a " - << nodename << " element" << endl; + } + + if (versetext == "") { + // nothing to store return; } + staff->setVerse(nnum-1, versetext); + reportVerseNumber(nnum, m_currentStaff-1); + + return; } ////////////////////////////// // -// Tool_mei2hum::processNodeStopLinks -- +// Tool_mei2hum::parseBareSyl -- Only one syl allows as a bar child of note element. +// This function is used to process syl elements that are not wrapped in a verse element. // -void Tool_mei2hum::processNodeStopLinks(string& output, xml_node node, - vector& nodelist) { - for (int i=0; i<(int)nodelist.size(); i++) { - string nodename = nodelist[i].name(); - if (nodename == "slur") { - parseSlurStop(output, node, nodelist[i]); - } else if (nodename == "tie") { - parseTieStop(output, node, nodelist[i]); - } else if (nodename == "tupletSpan") { - parseTupletSpanStop(output, node, nodelist[i]); - } else { - cerr << DKHTP << nodename - << " element in processNodeStopLinks()" << endl; - } - } -} +void Tool_mei2hum::parseBareSyl(xml_node syl, GridStaff* staff) { + NODE_VERIFY(syl, ) + int nnum = 1; + xml_attribute n_attr = syl.attribute("n"); + if (n_attr) { + nnum = n_attr.as_int(); + } + if (nnum < 1) { + cerr << "Warning: invalid layer number: " << nnum << endl; + cerr << "Setting it to 1." << endl; + nnum = 1; + } -////////////////////////////// -// -// Tool_mei2hum::parseSlurStart -- -// + string versetext = parseSyl(syl); -void Tool_mei2hum::parseSlurStart(string& output, xml_node node, xml_node slur) { - NODE_VERIFY(slur, ) - string nodename = node.name(); - if (nodename == "note") { - output = "(" + setPlacement(slur.attribute("curvedir").value()) + output; - } else if (nodename == "chord") { - output = "(" + setPlacement(slur.attribute("curvedir").value()) + output; - } else { - cerr << DKHTP << "a slur start attached to a " - << nodename << " element" << endl; + if (versetext == "") { + // nothing to store return; } + staff->setVerse(nnum-1, versetext); + reportVerseNumber(nnum, m_currentStaff-1); + + return; } ////////////////////////////// // -// Tool_mei2hum::parseSlurStop -- +// Tool_mei2hum::reportVerseNumber -- // -void Tool_mei2hum::parseSlurStop(string& output, xml_node node, xml_node slur) { - NODE_VERIFY(slur, ) - string nodename = node.name(); - if (nodename == "note") { - output += ")"; - } else if (nodename == "chord") { - output += ")"; - } else { - cerr << DKHTP << "a tie end attached to a " - << nodename << " element" << endl; +void Tool_mei2hum::reportVerseNumber(int pmax, int staffindex) { + if (staffindex < 0) { + return; + } + if (staffindex >= (int)m_maxverse.size()) { return; } + if (m_maxverse.at(staffindex) < pmax) { + m_maxverse[staffindex] = pmax; + } } ////////////////////////////// // -// Tool_mei2hum::parseTieStart -- Need to deal with chords later. +// Tool_mei2hum::parseSyl -- // -void Tool_mei2hum::parseTieStart(string& output, xml_node node, xml_node tie) { - NODE_VERIFY(tie, ) +string Tool_mei2hum::parseSyl(xml_node syl) { + NODE_VERIFY(syl, "") + MAKE_CHILD_LIST(children, syl); - string id = node.attribute("xml:id").value(); - if (!id.empty()) { - auto found = m_stoplinks.find(id); - if (found != m_stoplinks.end()) { - for (auto item : (*found).second) { - if (strcmp(tie.attribute("startid").value(), item.attribute("endid").value()) == 0) { - // deal with tie middles in parseTieStop(). - return; - } - } + string text = syl.child_value(); + for (int i=0; i<(int)text.size(); i++) { + if (text[i] == '_') { + text[i] = ' '; } } - string nodename = node.name(); - if (nodename == "note") { - output = "[" + output; - } else { - cerr << DKHTP << "a tie start attached to a " - << nodename << " element" << endl; - return; + string wordpos = syl.attribute("wordpos").value(); + if (wordpos == "i") { + text = text + "-"; + } else if (wordpos == "m") { + text = "-" + text + "-"; + } else if (wordpos == "t") { + text = "-" + text; } + + return text; } ////////////////////////////// // -// Tool_mei2hum::parseTrill -- +// Tool_mei2hum::parseClef -- +// // -void Tool_mei2hum::parseTrill(string& output, xml_node node, xml_node trill) { - NODE_VERIFY(trill, ) +void Tool_mei2hum::parseClef(xml_node clef, HumNum starttime) { + NODE_VERIFY(clef, ) - auto loc = output.find(";"); - if (loc != string::npos) { - output.insert(loc, "T"); - return; - } + string shape = clef.attribute("shape").value(); + string line = clef.attribute("line").value(); + string clefdis = clef.attribute("clef.dis").value(); + string clefdisplace = clef.attribute("clef.dis.place").value(); - loc = output.find(")"); - if (loc != string::npos) { - output.insert(loc, "T"); - return; - } + string tok = makeHumdrumClef(shape, line, clefdis, clefdisplace); - output += "T"; + m_outdata.back()->addClefToken(tok, starttime QUARTER_CONVERT, + m_currentStaff-1, 0, 0, m_staffcount); - // Deal with endid attribute on trills later. } ////////////////////////////// // -// Tool_mei2hum::parseTieStop -- Need to deal with chords later. +// Tool_mei2hum::makeHumdrumClef -- +// +// Example: +// // -void Tool_mei2hum::parseTieStop(string& output, xml_node node, xml_node tie) { - NODE_VERIFY(tie, ) - - string id = node.attribute("xml:id").value(); - if (!id.empty()) { - auto found = m_startlinks.find(id); - if (found != m_startlinks.end()) { - for (auto item : (*found).second) { - if (strcmp(tie.attribute("endid").value(), item.attribute("startid").value()) == 0) { - output += "_"; - return; - } - } +string Tool_mei2hum::makeHumdrumClef(const string& shape, + const string& line, const string& clefdis, const string& clefdisplace) { + string output = "*clef" + shape; + if (!clefdis.empty()) { + int number = stoi(clefdis); + int count = 0; + if (number == 8) { + count = 1; + } else if (number == 15) { + count = 2; + } + if (clefdisplace != "above") { + count = -count; + } + switch (count) { + case 1: output += "^"; break; + case 2: output += "^^"; break; + case -1: output += "v"; break; + case -2: output += "vv"; break; } } - - string nodename = node.name(); - if (nodename == "note") { - output += "]"; - } else { - cerr << DKHTP << "a tie end attached to a " - << nodename << " element" << endl; - return; - } + output += line; + return output; } ////////////////////////////// // -// Tool_mei2hum::parseFermata -- deal with a fermata attached to something. -/// output is a Humdrum token string (maybe have it as a HumdrumToken object). +// Tool_mei2hum::parseChord -- // -void Tool_mei2hum::parseFermata(string& output, xml_node node, xml_node fermata) { - NODE_VERIFY(fermata, ) - - string nodename = node.name(); - if (nodename == "note") { - output += ';'; - } else if (nodename == "chord") { - output += ';'; - } else if (nodename == "rest") { - output += ';'; - } else { - cerr << DKHTP << "a fermata attached to a " - << nodename << " element" << endl; - return; - } - -} - - - -////////////////////////////// -// -// Tool_mei2hum::getHumdrumRecip -- -// +HumNum Tool_mei2hum::parseChord(xml_node chord, HumNum starttime, int gracenumber) { + NODE_VERIFY(chord, starttime) + MAKE_CHILD_LIST(children, chord); -string Tool_mei2hum::getHumdrumRecip(HumNum duration, int dotcount) { - string output; + processPreliminaryLinkedNodes(chord); - if (dotcount > 0) { - // remove dots from duration - int top = (1 << (dotcount+1)) - 1; - int bot = 1 << dotcount; - HumNum dotfactor(bot, top); - duration *= dotfactor; - } + HumNum duration = getDuration(chord); - if (duration.getNumerator() == 1) { - output = to_string(duration.getDenominator()); - } else if ((duration.getNumerator() == 2) && (duration.getDenominator() == 1)) { - // breve symbol: - output = "0"; - } else if ((duration.getNumerator() == 4) && (duration.getDenominator() == 1)) { - // long symbol: - output = "00"; - } else if ((duration.getNumerator() == 8) && (duration.getDenominator() == 1)) { - // maxima symbol: - output = "000"; - } else { - output = to_string(duration.getDenominator()); - output += "%"; - output += to_string(duration.getNumerator()); + string tok; + int counter = 0; + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename == "note") { + counter++; + if (counter > 1) { + tok += " "; + } + parseNote(children[i], chord, tok, starttime, gracenumber); + } else if (nodename == "artic") { + // This is handled within parseNote(); + } else { + cerr << DKHTP << chord.name() << "/" << nodename << CURRLOC << endl; + } } - for (int i=0; iaddDataToken(tok, starttime QUARTER_CONVERT, m_currentStaff-1, + 0, m_currentLayer-1, m_staffcount); + + return starttime + duration; } ////////////////////////////// // -// Tool_mei2hum::getChildAccidVis -- Return accid@accid from any element -// in the list, if it is not editorial or cautionary. +// Tool_mei2hum::getChildrenVector -- Return a list of all children elements +// of a given element. Pugixml does not allow random access, but storing +// them in a vector allows that possibility. // -string Tool_mei2hum::getChildAccidVis(vector& children) { - for (int i=0; i<(int)children.size(); i++) { - string nodename = children[i].name(); - if (nodename != "accid") { - continue; - } - string func = children[i].attribute("func").value(); - if (func == "caution") { - // cautionary accidental handled elsewhere - return ""; - } else if (func == "edit") { - // editorial accidental handled elsewhere - return ""; - } - string accid = children[i].attribute("accid").value(); - return accid; +void Tool_mei2hum::getChildrenVector(vector& children, + xml_node parent) { + children.clear(); + for (xml_node child : parent.children()) { + children.push_back(child); } - return ""; } ////////////////////////////// // -// Tool_mei2hum::getChildAccidGes -- Return the accid@accid.ges value -// of any element in the input list, but not if the accidental is -// part of an cautionary or editorial accidental. +// Tool_mei2hum::initialize -- Setup for the tool, mostly parsing command-line +// (input) options. // -string Tool_mei2hum::getChildAccidGes(vector& children) { - for (int i=0; i<(int)children.size(); i++) { - string nodename = children[i].name(); - if (nodename != "accid") { - continue; - } - string func = children[i].attribute("func").value(); - if (func == "caution") { - // cautionary accidental handled elsewhere - return ""; - } else if (func == "edit") { - // editorial accidental handled elsewhere - return ""; - } - string accidges = children[i].attribute("accid.ges").value(); - return accidges; - } - return ""; +void Tool_mei2hum::initialize(void) { + m_recipQ = getBoolean("recip"); + m_stemsQ = getBoolean("stems"); + m_xmlidQ = getBoolean("xmlids"); + m_xmlidQ = 1; // for testing + m_appLabel = getString("app-label"); + m_placeQ = !getBoolean("no-place"); } ////////////////////////////// // -// Tool_mei2hum::getHumdrumPitch -- +// Tool_mei2hum::buildIdLinkMap -- Build table of startid and endid links between elements. +// +// Reference: https://pugixml.org/docs/samples/traverse_walker.cpp // -string Tool_mei2hum::getHumdrumPitch(xml_node note, vector& children) { - string pname = note.attribute("pname").value(); - string accidvis = note.attribute("accid").value(); - string accidges = note.attribute("accid.ges").value(); - - string accidvischild = getChildAccidVis(children); - string accidgeschild = getChildAccidGes(children); +void Tool_mei2hum::buildIdLinkMap(xml_document& doc) { + class linkmap_walker : public pugi::xml_tree_walker { + public: + virtual bool for_each(pugi::xml_node& node) { + xml_attribute startid = node.attribute("startid"); + xml_attribute endid = node.attribute("endid"); + if (startid) { - int octnum = 4; - string oct = note.attribute("oct").value(); - if (oct == "") { - cerr << "Empty octave" << endl; - } else if (isdigit(oct[0])) { - octnum = stoi(oct); - } else { - cerr << "Unknown octave value: " << oct << endl; - } + string value = startid.value(); + if (!value.empty()) { + if (value[0] == '#') { + value = value.substr(1, string::npos); + } + } + if (!value.empty()) { + (*startlinks)[value].push_back(node); + } - if (pname == "") { - cerr << "Empty pname" << endl; - return "x"; - } + } + if (endid) { - string output; - if (octnum < 4) { - char val = toupper(pname[0]); - int count = 4 - octnum; - for (int i=0; i>* startlinks = NULL; + map>* stoplinks = NULL; + }; - return output; + m_startlinks.clear(); + m_stoplinks.clear(); + linkmap_walker walker; + walker.startlinks = &m_startlinks; + walker.stoplinks = &m_stoplinks; + doc.traverse(walker); } ////////////////////////////// // -// Tool_mei2hum::getDuration -- Get duration from note or chord. If chord does not -// have @dur then use @dur of first note in children elements. +// Tool_mei2hum::parseDir -- Meter cannot change in middle of measure. +// Need to implement @startid version. +// +// Example: +// con espressione +// +// or with normal font specified: +// +// test +// +// +// bold font: +// +// comment +// // -HumNum Tool_mei2hum::getDuration(xml_node element) { - xml_attribute dur_attr = element.attribute("dur"); - string name = element.name(); - if ((!dur_attr) && (name == "note")) { - // real notes must have durations, but this one - // does not, so assign zero duration - return 0; +void Tool_mei2hum::parseDir(xml_node dir, HumNum starttime) { + NODE_VERIFY(dir, ) + MAKE_CHILD_LIST(children, dir); + + string font = "i"; // italic by default in verovio + + string placement = ""; // a = above, b = below + + string place = dir.attribute("place").value(); + if (place == "above") { + placement = "a:"; } - if ((!dur_attr) && (name == "chord")) { - // if there is no dur attribute on a chord, then look for it - // on the first note subelement of the chord. - auto newelement = element.select_node(".//note").node(); - if (newelement) { - element = newelement; - dur_attr = element.attribute("dur"); - name = element.name(); - } else { - return 0; + // Below is the default in Humdrum layout commands. + + string text; + + if (!children.empty()) { // also includes the above text node, but only looking at . + int count = 0; + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename == "rend") { + if (count) { + text += " "; + } + count++; + text += children[i].child_value(); + if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) { + font = ""; // normal is default in Humdrum layout + } + if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) { + font += "B"; // normal is default in Humdrum layout + } + } else if (nodename == "") { + // text node + if (count) { + text += " "; + } + count++; + text += children[i].value(); + } else { + cerr << DKHTP << dir.name() << "/" << nodename << CURRLOC << endl; + } } } - string dur = dur_attr.value(); - if (dur == "") { - return 0; + if (text.empty()) { + return; } - HumNum output; - if (dur == "breve") { - output = 2; - } else if (dur == "long") { - output = 4; - } else if (dur == "maxima") { - output = 8; - } else if (isdigit(dur[0])) { - output = 1; - output /= stoi(dur); - } else { - cerr << "Unknown " << element.name() << "@dur: " << dur << endl; - return 0; + string message = "!LO:TX:"; + message += placement; + if (!font.empty()) { + message += font + ":"; } + message += "t=" + cleanDirText(text); - if (output == 0) { - cerr << "Error: zero duration for note" << endl; + string ts = dir.attribute("tstamp").value(); + if (ts.empty()) { + cerr << "Error: no timestamp on dir element and can't currently processes with @startid." << endl; + return; } - int dotcount; - string dots = element.attribute("dots").value(); - if (dots == "") { - dotcount = 0; - } else if (isdigit(dots[0])) { - dotcount = stoi(dots); - } else { - cerr << "Unknown " << element.name() << "@dotcount: " << dur << endl; - return 0; + xml_attribute atstaffnum = dir.attribute("staff"); + if (!atstaffnum) { + cerr << "Error: staff number required on dir element in measure " + << m_currentMeasure << " (ignoring text: " << cleanWhiteSpace(text) << ")" << endl; + return; } - - if (dotcount > 0) { - int top = (1 << (dotcount+1)) - 1; - int bot = 1 << dotcount; - HumNum dotfactor(top, bot); - output *= dotfactor; + int staffnum = dir.attribute("staff").as_int(); + if (staffnum <= 0) { + cerr << "Error: staff number on dir element in measure should be positive.\n"; + cerr << "Instead the staff number is: " << m_currentMeasure << " (ignoring text: " << cleanWhiteSpace(text) << ")" << endl; + return; } - // add a correction for the tuplet factor which is currently active. - if (m_tupletfactor != 1) { - output *= m_tupletfactor; - } + double meterunit = m_currentMeterUnit[staffnum - 1]; + double tsd = (stof(ts)-1) * 4.0 / meterunit; - return output; + GridMeasure* gm = m_outdata.back(); + double tsm = gm->getTimestamp().getFloat(); + bool foundslice = false; + GridSlice* gs; + for (auto gsit = gm->begin(); gsit != gm->end(); gsit++) { + gs = *gsit; + if (!gs->isDataSlice()) { + continue; + } + double gsts = gs->getTimestamp().getFloat(); + double difference = (gsts-tsm) - tsd; + if (!(fabs(difference) < 0.0001)) { + continue; + } + // GridVoice* voice = gs->at(staffnum-1)->at(0)->at(0); + // HTp token = voice->getToken(); + // if (token != NULL) { + // token->setValue("LO", "TX", "t", text); + // } else { + // cerr << "Strange null-token error while inserting dir element." << endl; + // } + foundslice = true; + + // Found data line which should prefixed with a layout line + // should be done with HumHash post-processing, but do it manually for now. + + auto previousit = gsit; + previousit--; + if (previousit == gm->end()) { + previousit = gsit; + } + auto previous = *previousit; + if (previous->isLayoutSlice()) { + GridVoice* voice = previous->at(staffnum-1)->at(0)->at(0); + HTp tok = voice->getToken(); + if (tok == NULL) { + HTp newtok = new HumdrumToken(message); + voice->setToken(newtok); + tok = voice->getToken(); + break; + } else if (tok->isNull()) { + tok->setText(message); + break; + } + } + + // Insert a layout slice in front of current data slice. + GridSlice* ngs = new GridSlice(gm, gs->getTimestamp(), SliceType::Layouts, m_maxStaffInFile); + int parti = staffnum - 1; + int staffi = 0; + int voicei = 0; + ngs->addToken(message, parti, staffi, voicei); + gm->insert(gsit, ngs); + + break; + } + if (!foundslice) { + cerr << "Warning: dir elements not occuring at note/rest times are not yet supported" << endl; + } } ////////////////////////////// // -// Tool_mei2hum::getDuration_mensural -- Get duration from note or chord. If chord does not -// have @dur then use @dur of first note in children elements. -// -// @dur: https://music-encoding.org/guidelines/v4/data-types/data.duration.mensural.html -// X = maxima -// L = longa -// S = brevis -// s = semibrevis -// M = minima -// m = semiminima -// U = fusa -// u = semifusa -// @dur.quality: -// i = imperfecta :: remove augmentation dot -// p = perfecta :: add augmentation dot -// altera = altera :: duration is double the rhythmic value of notes +// Tool_mei2hum::cleanWhiteSpace -- Convert newlines to "\n", and trim spaces. +// Also remove more than one space in a row. // -HumNum Tool_mei2hum::getDuration_mensural(xml_node element, int& dotcount) { - dotcount = 0; - - xml_attribute dur_qual = element.attribute("dur.quality"); - xml_attribute dur_attr = element.attribute("dur"); - string name = element.name(); - - if ((!dur_attr) && (name == "note")) { - // real notes must have durations, but this one - // does not, so assign zero duration - return 0; - } - if ((!dur_attr) && (name == "chord")) { - // if there is no dur attribute on a chord, then look for it - // on the first note subelement of the chord. - auto newelement = element.select_node(".//note").node(); - if (newelement) { - element = newelement; - dur_attr = element.attribute("dur"); - name = element.name(); - dur_qual = element.attribute("dur.quality"); +string Tool_mei2hum::cleanWhiteSpace(const string& input) { + string output; + output.reserve(input.size() + 8); + bool foundstart = false; + for (int i=0; i<(int)input.size(); i++) { + if ((!foundstart) && std::isspace(input[i])) { + continue; + } + foundstart = true; + if (input[i] == '\t') { + if ((!output.empty()) && (output.back() != ' ')) { + output += ' '; + } + } else if (input[i] == '\n') { + if ((!output.empty()) && (output.back() != ' ')) { + output += ' '; + } + } else if (input[i] == ' ') { + if ((!output.empty()) && (output.back() != ' ')) { + output += ' '; + } } else { - return 0; + output += input[i]; } } - - string dur = dur_attr.value(); - if (dur == "") { - return 0; + while ((!output.empty()) && (output.back() == ' ')) { + output.pop_back(); } - string durquality = dur_qual.value(); - char rhythm = '\0'; - if (dur == "maxima") { - rhythm = 'X'; - } else if (dur == "longa") { - rhythm = 'L'; - } else if (dur == "brevis") { - rhythm = 'S'; - } else if (dur == "semibrevis") { - rhythm = 's'; - } else if (dur == "minima") { - rhythm = 'M'; - } else if (dur == "semiminima") { - rhythm = 'm'; - } else if (dur == "fusa") { - rhythm = 'U'; - } else if (dur == "semifusa") { - rhythm = 'u'; - } else { - cerr << "Error: unknown rhythm" << element.name() << "@dur: " << dur << endl; - return 0; - } + return output; +} - mei_staffDef& ss = m_scoreDef.staves.at(m_currentStaff - 1); - int maximodus = ss.maximodus == 3 ? 3 : 2; - int modus = ss.modus == 3 ? 3 : 2; - int tempus = ss.tempus == 3 ? 3 : 2; - int prolatio = ss.prolatio == 3 ? 3 : 2; - bool altera = false; - bool perfecta = false; - bool imperfecta = false; - if (durquality == "imperfecta") { - imperfecta = true; - } else if (durquality == "perfecta") { - perfecta = true; - } else if (durquality == "altera") { - altera = true; +////////////////////////////// +// +// Tool_mei2hum::cleanDirText -- convert ":" to ":". +// Remove tabs and newlines, and trim spaces. Maybe allow +// newlines using "\n" and allow font changes in the future. +// Remove redundant whitespace. Do accents later perhaps or +// monitor for UTF-8. +// + +string Tool_mei2hum::cleanDirText(const string& input) { + string output; + output.reserve(input.size() + 8); + bool foundstart = false; + for (int i=0; i<(int)input.size(); i++) { + if ((!foundstart) && std::isspace(input[i])) { + continue; + } + foundstart = true; + if (input[i] == ':') { + output += ":"; + } else if (input[i] == '\t') { + if ((!output.empty()) && (output.back() != ' ')) { + output += ' '; + } + } else if (input[i] == '\n') { + if ((!output.empty()) && (output.back() != ' ')) { + output += ' '; + } + } else if (input[i] == ' ') { + if ((!output.empty()) && (output.back() != ' ')) { + output += ' '; + } + } else { + output += input[i]; + } + } + while ((!output.empty()) && (output.back() == ' ')) { + output.pop_back(); } - HumNum output = Convert::mensToDuration(rhythm, altera, perfecta, imperfecta, maximodus, modus, tempus, prolatio); return output; } - ////////////////////////////// // -// Tool_mei2hum::parseVerse -- +// Tool_mei2hum::cleanVerseText -- +// Remove tabs and newlines, and trim spaces. +// Do accents later perhaps or monitor for UTF-8. // -void Tool_mei2hum::parseVerse(xml_node verse, GridStaff* staff) { - NODE_VERIFY(verse, ) - MAKE_CHILD_LIST(children, verse); - - string n = verse.attribute("n").value(); - int nnum = 1; - if (n.empty()) { - cerr << "Warning: no layer number on layer element" << endl; - } else { - nnum = stoi(n); +string Tool_mei2hum::cleanVerseText(const string& input) { + string output; + output.reserve(input.size() + 8); + bool foundstart = false; + for (int i=0; i<(int)input.size(); i++) { + if ((!foundstart) && std::isspace(input[i])) { + continue; + } + foundstart = true; + if (input[i] == '\t') { + output += ' '; + } else if (input[i] == '\n') { + output += ' '; + } else { + output += input[i]; + } } - if (nnum < 1) { - cerr << "Warning: invalid layer number: " << nnum << endl; - cerr << "Setting it to 1." << endl; - nnum = 1; + while ((!output.empty()) && (output.back() == ' ')) { + output.pop_back(); } - string versetext; - int sylcount = 0; + return output; +} - for (int i=0; i<(int)children.size(); i++) { - string nodename = children[i].name(); - if (nodename == "syl") { - if (sylcount > 0) { - versetext += " "; + + +////////////////////////////// +// +// Tool_mei2hum::cleanReferenceRecordText -- convert ":" to ":". +// Remove tabs and newlines, and trim spaces. Maybe allow +// newlines using "\n" and allow font changes in the future. +// Do accents later perhaps or monitor for UTF-8. +// + +string Tool_mei2hum::cleanReferenceRecordText(const string& input) { + string output; + output.reserve(input.size() + 8); + bool foundstart = false; + char lastchar = '\0'; + for (int i=0; i<(int)input.size(); i++) { + if ((!foundstart) && std::isspace(input[i])) { + continue; + } + foundstart = true; + if (input[i] == '\n') { + if (lastchar != ' ') { + output += ' '; } - sylcount++; - versetext += parseSyl(children[i]); + lastchar = ' '; + } else if (input[i] == '\t') { + if (lastchar != ' ') { + output += ' '; + } + lastchar = ' '; } else { - cerr << DKHTP << verse.name() << "/" << nodename << CURRLOC << endl; + output += input[i]; + lastchar = input[i]; } } - - if (versetext == "") { - // nothing to store - return; + while ((!output.empty()) && (output.back() == ' ')) { + output.pop_back(); } - staff->setVerse(nnum-1, versetext); - reportVerseNumber(nnum, m_currentStaff-1); - - return; + return output; } ////////////////////////////// // -// Tool_mei2hum::parseBareSyl -- Only one syl allows as a bar child of note element. -// This function is used to process syl elements that are not wrapped in a verse element. +// Tool_mei2hum::parseTempo -- +// +// Example: +// +// 1 - Allegro con spirito = 132 +// +// +// +// Ways of indicating tempo: // +// tempo@midi.bpm == tempo per quarter note (Same as Humdrum *MM value) +// +// tempo@midi.mspb == microseconds per quarter note ( bpm = mspb * 60 / 1000000) +// +// tempo@mm == tempo per beat (bpm = mm / unit(dots)) +// tempo@mm.unit == beat unit for tempo@mm +// tempo@mm.dots == dots for tempo@unit +// +// Free-form text: +// +//  == quarter note +// +// #define SMUFL_QUARTER_NOTE "\ue1d5" -void Tool_mei2hum::parseBareSyl(xml_node syl, GridStaff* staff) { - NODE_VERIFY(syl, ) +void Tool_mei2hum::parseTempo(xml_node tempo, HumNum starttime) { + NODE_VERIFY(tempo, ) - int nnum = 1; - xml_attribute n_attr = syl.attribute("n"); - if (n_attr) { - nnum = n_attr.as_int(); - } + bool found = false; + double value = 0.0; - if (nnum < 1) { - cerr << "Warning: invalid layer number: " << nnum << endl; - cerr << "Setting it to 1." << endl; - nnum = 1; + xml_attribute bpm = tempo.attribute("bpm"); + if (bpm) { + value = bpm.as_double(); + if (value > 0.0) { + found = true; + } } - string versetext = parseSyl(syl); - - if (versetext == "") { - // nothing to store - return; + if (!found) { + xml_attribute mspb = tempo.attribute("mspb"); + value = mspb.as_double() * 60.0 / 1000000.0; + if (value > 0.0) { + found = true; + } } - staff->setVerse(nnum-1, versetext); - reportVerseNumber(nnum, m_currentStaff-1); + if (!found) { + xml_attribute mm = tempo.attribute("mm"); + xml_attribute mmunit = tempo.attribute("mm.unit"); + xml_attribute mmdots = tempo.attribute("mm.dots"); + value = mm.as_double(); + string recip = mmunit.value(); + int dcount = mmdots.as_int(); + for (int i=0; i 0.0) { + found = true; + } + } - return; -} + if (!found) { + // search for free-form tempo marking. Something like: + // + // 1 - Allegro con spirito = 132 + // + // + // UTF-8 version in string "\ue1d5"; + string text; + MAKE_CHILD_LIST(children, tempo); + for (int i=0; i<(int)children.size(); i++) { + if (children[i].type() == pugi::node_pcdata) { + text += children[i].value(); + } else { + text += children[i].child_value(); + } + text += " "; + } + HumRegex hre; + // #define SMUFL_QUARTER_NOTE "\ue1d5" + // if (hre.search(text, SMUFL_QUARTER_NOTE "\\s*=\\s*(\\d+\\.?\\d*)")) { + if (hre.search(text, "\\s*=\\s*(\\d+\\.?\\d*)")) { + // assuming quarter note for now. + value = hre.getMatchDouble(1); + found = true; + } + // further rhythmic values for tempo should go here. + } -////////////////////////////// -// -// Tool_mei2hum::reportVerseNumber -- -// + // also deal with tempo designiations such as "Allegro"... -void Tool_mei2hum::reportVerseNumber(int pmax, int staffindex) { - if (staffindex < 0) { + if (!found) { + // no tempo to set return; } - if (staffindex >= (int)m_maxverse.size()) { - return; + + // insert tempo + GridMeasure* gm = m_outdata.back(); + GridSlice* gs = new GridSlice(gm, starttime, SliceType::Tempos, m_maxStaffInFile); + stringstream stok; + stok << "*MM" << value; + string token = stok.str(); + + for (int i=0; iat(i)->at(0)->at(0)->setToken(token); } - if (m_maxverse.at(staffindex) < pmax) { - m_maxverse[staffindex] = pmax; + + // insert after time signature at same timestamp if possible + bool inserted = false; + for (auto it = gm->begin(); it != gm->end(); it++) { + if ((*it)->getTimestamp() > starttime) { + gm->insert(it, gs); + inserted = true; + break; + } else if ((*it)->isTimeSigSlice()) { + it++; + gm->insert(it, gs); + inserted = true; + break; + } else if (((*it)->getTimestamp() == starttime) && ((*it)->isNoteSlice() + || (*it)->isGraceSlice())) { + gm->insert(it, gs); + inserted = true; + break; + } + } + + if (!inserted) { + gm->push_back(gs); } + } ////////////////////////////// // -// Tool_mei2hum::parseSyl -- +// Tool_mei2hum::parseHarm -- Not yet ready to convert data. +// There will be different types of harm (such as figured bass), which +// will need to be subcategorized into different datatypes, such as +// *fb for figured bass. Also free-text can be present in +// data, so the current datatype for that is **cdata (meaning chord-like +// data that will be mapped back into which converting back to +// MEI data. +// +// Example: +// C major // -string Tool_mei2hum::parseSyl(xml_node syl) { - NODE_VERIFY(syl, "") - MAKE_CHILD_LIST(children, syl); +void Tool_mei2hum::parseHarm(xml_node harm, HumNum starttime) { + NODE_VERIFY(harm, ) + MAKE_CHILD_LIST(children, harm); - string text = syl.child_value(); - for (int i=0; i<(int)text.size(); i++) { - if (text[i] == '_') { - text[i] = ' '; + string text = harm.child_value(); + + if (text.empty()) { // looking at sub-elements + int count = 0; + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename == "rend") { + if (count) { + text += " "; + } + count++; + text += children[i].child_value(); + //if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) { + // font = ""; // normal is default in Humdrum layout + //} + //if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) { + // font += "B"; // normal is default in Humdrum layout + //} + } else if (nodename == "") { + // text node + if (count) { + text += " "; + } + count++; + text += children[i].value(); + } else { + cerr << DKHTP << harm.name() << "/" << nodename << CURRLOC << endl; + } } } - string wordpos = syl.attribute("wordpos").value(); - if (wordpos == "i") { - text = text + "-"; - } else if (wordpos == "m") { - text = "-" + text + "-"; - } else if (wordpos == "t") { - text = "-" + text; + if (text.empty()) { + return; } - return text; -} - - + // cerr << "FOUND HARM DATA " << text << endl; -////////////////////////////// -// -// Tool_mei2hum::parseClef -- -// -// +/* -void Tool_mei2hum::parseClef(xml_node clef, HumNum starttime) { - NODE_VERIFY(clef, ) + string startid = harm.attribute("startid").value(); - string shape = clef.attribute("shape").value(); - string line = clef.attribute("line").value(); - string clefdis = clef.attribute("clef.dis").value(); - string clefdisplace = clef.attribute("clef.dis.place").value(); + int staffnum = harm.attribute("staff").as_int(); + if (staffnum == 0) { + cerr << "Error: staff number required on harm element" << endl; + return; + } + double meterunit = m_currentMeterUnit[staffnum - 1]; - string tok = makeHumdrumClef(shape, line, clefdis, clefdisplace); + if (!startid.empty()) { + // Harmony is (or at least should) be attached directly + // do a note, so it is handled elsewhere. + cerr << "Warning DYNAMIC " << text << " is not yet processed." << endl; + return; + } - m_outdata.back()->addClefToken(tok, starttime QUARTER_CONVERT, - m_currentStaff-1, 0, 0, m_staffcount); + string ts = harm.attribute("tstamp").value(); + if (ts.empty()) { + cerr << "Error: no timestamp on harm element" << endl; + return; + } + double tsd = (stof(ts)-1) * 4.0 / meterunit; + double tolerance = 0.001; + GridMeasure* gm = m_outdata.back(); + double tsm = gm->getTimestamp().getFloat(); + bool foundslice = false; + GridSlice *nextgs = NULL; + for (auto gs : *gm) { + if (!gs->isDataSlice()) { + continue; + } + double gsts = gs->getTimestamp().getFloat(); + double difference = (gsts-tsm) - tsd; + if (difference < tolerance) { + // did not find data line at exact timestamp, so move + // the harm to the next event. Need to think about adding + // a new timeslice for the harm when it is not attached to + // a note. + nextgs = gs; + break; + } + if (!(fabs(difference) < tolerance)) { + continue; + } + GridPart* part = gs->at(staffnum-1); + part->setHarmony(text); + m_outdata.setHarmonyPresent(staffnum-1); + foundslice = true; + break; + } + if (!foundslice) { + if (nextgs == NULL) { + cerr << "Warning: harmony not attched to system events " + << "are not yet supported in measure " << m_currentMeasure << endl; + } else { + GridPart* part = nextgs->at(staffnum-1); + part->setHarmony(text); + m_outdata.setHarmonyPresent(staffnum-1); + // Give a time offset for displaying the harmmony here. + } + } +*/ } @@ -96248,446 +99634,623 @@ void Tool_mei2hum::parseClef(xml_node clef, HumNum starttime) { ////////////////////////////// // -// Tool_mei2hum::makeHumdrumClef -- +// Tool_mei2hum::parseDynam -- // // Example: -// +// p // -string Tool_mei2hum::makeHumdrumClef(const string& shape, - const string& line, const string& clefdis, const string& clefdisplace) { - string output = "*clef" + shape; - if (!clefdis.empty()) { - int number = stoi(clefdis); +void Tool_mei2hum::parseDynam(xml_node dynam, HumNum starttime) { + NODE_VERIFY(dynam, ) + MAKE_CHILD_LIST(children, dynam); + + string text = dynam.child_value(); + + if (text.empty()) { // looking at sub-elements int count = 0; - if (number == 8) { - count = 1; - } else if (number == 15) { - count = 2; + for (int i=0; i<(int)children.size(); i++) { + string nodename = children[i].name(); + if (nodename == "rend") { + if (count) { + text += " "; + } + count++; + text += children[i].child_value(); + //if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) { + // font = ""; // normal is default in Humdrum layout + //} + //if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) { + // font += "B"; // normal is default in Humdrum layout + //} + } else if (nodename == "") { + // text node + if (count) { + text += " "; + } + count++; + text += children[i].value(); + } else { + cerr << DKHTP << dynam.name() << "/" << nodename << CURRLOC << endl; + } } - if (clefdisplace != "above") { - count = -count; + } + + if (text.empty()) { + return; + } + + string startid = dynam.attribute("startid").value(); + + int staffnum = dynam.attribute("staff").as_int(); + if (staffnum == 0) { + cerr << "Error: staff number required on dynam element" << endl; + return; + } + double meterunit = m_currentMeterUnit[staffnum - 1]; + + if (!startid.empty()) { + // Dynamic is (or at least should) be attached directly + // do a note, so it is handled elsewhere. + cerr << "Warning DYNAMIC " << text << " is not yet processed." << endl; + return; + } + + string ts = dynam.attribute("tstamp").value(); + if (ts.empty()) { + cerr << "Error: no timestamp on dynam element" << endl; + return; + } + double tsd = (stof(ts)-1) * 4.0 / meterunit; + double tolerance = 0.001; + GridMeasure* gm = m_outdata.back(); + double tsm = gm->getTimestamp().getFloat(); + bool foundslice = false; + GridSlice *nextgs = NULL; + for (auto gs : *gm) { + if (!gs->isDataSlice()) { + continue; } - switch (count) { - case 1: output += "^"; break; - case 2: output += "^^"; break; - case -1: output += "v"; break; - case -2: output += "vv"; break; + double gsts = gs->getTimestamp().getFloat(); + double difference = (gsts-tsm) - tsd; + if (difference < tolerance) { + // did not find data line at exact timestamp, so move + // the dynamic to the next event. Maybe think about adding + // a new timeslice for the dynamic. + nextgs = gs; + break; + } + if (!(fabs(difference) < tolerance)) { + continue; } + GridPart* part = gs->at(staffnum-1); + part->setDynamics(text); + m_outdata.setDynamicsPresent(staffnum-1); + foundslice = true; + break; } - output += line; - return output; + if (!foundslice) { + if (nextgs == NULL) { + cerr << "Warning: dynamics not attched to system events " + << "are not yet supported in measure " << m_currentMeasure << endl; + } else { + GridPart* part = nextgs->at(staffnum-1); + part->setDynamics(text); + m_outdata.setDynamicsPresent(staffnum-1); + // Give a time offset for displaying the dynamic here. + } + } +} + + + + + +///////////////////////////////// +// +// Tool_gridtest::Tool_melisma -- Set the recognized options for the tool. +// + +Tool_melisma::Tool_melisma(void) { + define("m|min=i:2", "minimum length to identify as a melisma"); + define("r|replace=b", "replace lyrics with note counts"); + define("a|average|avg=b", "calculate note-to-syllable ratio"); + define("w|words=b", "list words that contain a melisma"); + define("p|part=b", "also calculate note-to-syllable ratios by part"); } -////////////////////////////// +/////////////////////////////// // -// Tool_mei2hum::parseChord -- +// Tool_melisma::run -- Primary interfaces to the tool. // -HumNum Tool_mei2hum::parseChord(xml_node chord, HumNum starttime, int gracenumber) { - NODE_VERIFY(chord, starttime) - MAKE_CHILD_LIST(children, chord); +bool Tool_melisma::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i 1) { - tok += " "; - } - parseNote(children[i], chord, tok, starttime, gracenumber); - } else if (nodename == "artic") { - // This is handled within parseNote(); - } else { - cerr << DKHTP << chord.name() << "/" << nodename << CURRLOC << endl; - } - } - m_fermata = false; - processLinkedNodes(tok, chord); - if (!m_fermata) { - processFermataAttribute(tok, chord); - } +bool Tool_melisma::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + return status; +} - m_outdata.back()->addDataToken(tok, starttime QUARTER_CONVERT, m_currentStaff-1, - 0, m_currentLayer-1, m_staffcount); - return starttime + duration; +bool Tool_melisma::run(HumdrumFile& infile) { + initialize(infile); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_mei2hum::getChildrenVector -- Return a list of all children elements -// of a given element. Pugixml does not allow random access, but storing -// them in a vector allows that possibility. +// Tool_melisma::initialize -- // -void Tool_mei2hum::getChildrenVector(vector& children, - xml_node parent) { - children.clear(); - for (xml_node child : parent.children()) { - children.push_back(child); - } +void Tool_melisma::initialize(HumdrumFile& infile) { + // do nothing for now } ////////////////////////////// // -// Tool_mei2hum::initialize -- Setup for the tool, mostly parsing command-line -// (input) options. +// Tool_melisma::processFile -- // -void Tool_mei2hum::initialize(void) { - m_recipQ = getBoolean("recip"); - m_stemsQ = getBoolean("stems"); - m_xmlidQ = getBoolean("xmlids"); - m_xmlidQ = 1; // for testing - m_appLabel = getString("app-label"); - m_placeQ = !getBoolean("no-place"); +void Tool_melisma::processFile(HumdrumFile& infile) { + vector> notecount; + getNoteCounts(infile, notecount); + vector wordinfo; + wordinfo.reserve(1000); + map wordlist; + initializePartInfo(infile); + + if (getBoolean("replace")) { + replaceLyrics(infile, notecount); + } else if (getBoolean("words")) { + markMelismas(infile, notecount); + extractWordlist(wordinfo, wordlist, infile, notecount); + printWordlist(infile, wordinfo, wordlist); + } else { + markMelismas(infile, notecount); + } + } ////////////////////////////// // -// Tool_mei2hum::buildIdLinkMap -- Build table of startid and endid links between elements. -// -// Reference: https://pugixml.org/docs/samples/traverse_walker.cpp +// Tool_melisma::initializePartInfo -- // -void Tool_mei2hum::buildIdLinkMap(xml_document& doc) { - class linkmap_walker : public pugi::xml_tree_walker { - public: - virtual bool for_each(pugi::xml_node& node) { - xml_attribute startid = node.attribute("startid"); - xml_attribute endid = node.attribute("endid"); - if (startid) { +void Tool_melisma::initializePartInfo(HumdrumFile& infile) { + m_names.clear(); + m_abbreviations.clear(); + m_partnums.clear(); - string value = startid.value(); - if (!value.empty()) { - if (value[0] == '#') { - value = value.substr(1, string::npos); - } - } - if (!value.empty()) { - (*startlinks)[value].push_back(node); - } + m_names.resize(infile.getTrackCount() + 1); + m_abbreviations.resize(infile.getTrackCount() + 1); + m_partnums.resize(infile.getTrackCount() + 1); + fill(m_partnums.begin(), m_partnums.end(), -1); + vector starts; + infile.getSpineStartList(starts); + int ktrack = 0; + int track = 0; + int part = 0; + for (int i=0; i<(int)starts.size(); i++) { + track = starts[i]->getTrack(); + if (starts[i]->isKern()) { + ktrack = track; + part++; + m_partnums[ktrack] = part; + HTp current = starts[i]; + while (current) { + if (current->isData()) { + break; } - if (endid) { - - string value = endid.value(); - if (!value.empty()) { - if (value[0] == '#') { - value = value.substr(1, string::npos); - } - } - if (!value.empty()) { - (*stoplinks)[value].push_back(node); - } - + if (current->compare(0, 3, "*I\"") == 0) { + m_names[ktrack] = current->substr(3); + } else if (current->compare(0, 3, "*I\'") == 0) { + m_abbreviations[ktrack] = current->substr(3); } - return true; // continue traversal + current = current->getNextToken(); } + } else if (ktrack) { + m_names[track] = m_names[ktrack]; + m_abbreviations[track] = m_abbreviations[ktrack]; + m_partnums[track] = m_partnums[ktrack]; + } + } - map>* startlinks = NULL; - map>* stoplinks = NULL; - }; - - m_startlinks.clear(); - m_stoplinks.clear(); - linkmap_walker walker; - walker.startlinks = &m_startlinks; - walker.stoplinks = &m_stoplinks; - doc.traverse(walker); } ////////////////////////////// // -// Tool_mei2hum::parseDir -- Meter cannot change in middle of measure. -// Need to implement @startid version. -// -// Example: -// con espressione -// -// or with normal font specified: -// -// test -// -// -// bold font: -// -// comment -// +// printWordlist -- // -void Tool_mei2hum::parseDir(xml_node dir, HumNum starttime) { - NODE_VERIFY(dir, ) - MAKE_CHILD_LIST(children, dir); - - string font = "i"; // italic by default in verovio +void Tool_melisma::printWordlist(HumdrumFile& infile, vector& wordinfo, + map words) { - string placement = ""; // a = above, b = below + // for (auto& item : words) { + // m_free_text << item.first; + // if (item.second > 1) { + // m_free_text << " (" << item.second << ")"; + // } + // m_free_text << endl; + // } - string place = dir.attribute("place").value(); - if (place == "above") { - placement = "a:"; - } - // Below is the default in Humdrum layout commands. + vector ncounts; + vector mcounts; + getMelismaNoteCounts(ncounts, mcounts, infile); - string text; + // m_free_text << "===========================" << endl; - if (!children.empty()) { // also includes the above text node, but only looking at . - int count = 0; - for (int i=0; i<(int)children.size(); i++) { - string nodename = children[i].name(); - if (nodename == "rend") { - if (count) { - text += " "; - } - count++; - text += children[i].child_value(); - if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) { - font = ""; // normal is default in Humdrum layout - } - if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) { - font += "B"; // normal is default in Humdrum layout - } - } else if (nodename == "") { - // text node - if (count) { - text += " "; - } - count++; - text += children[i].value(); - } else { - cerr << DKHTP << dir.name() << "/" << nodename << CURRLOC << endl; - } - } - } + std::vector kspines = infile.getKernSpineStartList(); - if (text.empty()) { - return; - } + m_free_text << "@@BEGIN:\tMELISMAS\n"; - string message = "!LO:TX:"; - message += placement; - if (!font.empty()) { - message += font + ":"; + string filename = infile.getFilename(); + auto pos = filename.rfind("/"); + if (pos != string::npos) { + filename = filename.substr(pos+1); } - message += "t=" + cleanDirText(text); + m_free_text << "@FILENAME:\t" << filename << endl; + m_free_text << "@PARTCOUNT:\t" << kspines.size() << endl; + m_free_text << "@WORDCOUNT:\t" << wordinfo.size() << endl; + m_free_text << "@SCOREDURATION:\t" << getScoreDuration(infile) << endl; + m_free_text << "@NOTES:\t\t" << ncounts[0] << endl; + m_free_text << "@MELISMANOTES:\t" << mcounts[0] << endl; - string ts = dir.attribute("tstamp").value(); - if (ts.empty()) { - cerr << "Error: no timestamp on dir element and can't currently processes with @startid." << endl; - return; + m_free_text << "@MELISMASCORE:\t" << int((double)mcounts[0] / (double)ncounts[0] * 1000.0 + 0.5)/10.0 << "%" << endl; + for (int i=1; i<(int)m_partnums.size(); i++) { + if (m_partnums[i] == 0) { + continue; + } + if (m_partnums[i] == m_partnums[i-1]) { + continue; + } + m_free_text << "@PARTSCORE-" << m_partnums[i] << ":\t" << int((double)mcounts[i] / (double)ncounts[i] * 1000.0 + 0.5)/10.0 << "%" << endl; } - xml_attribute atstaffnum = dir.attribute("staff"); - if (!atstaffnum) { - cerr << "Error: staff number required on dir element in measure " - << m_currentMeasure << " (ignoring text: " << cleanWhiteSpace(text) << ")" << endl; - return; - } - int staffnum = dir.attribute("staff").as_int(); - if (staffnum <= 0) { - cerr << "Error: staff number on dir element in measure should be positive.\n"; - cerr << "Instead the staff number is: " << m_currentMeasure << " (ignoring text: " << cleanWhiteSpace(text) << ")" << endl; - return; + for (int i=1; i<(int)m_partnums.size(); i++) { + if (m_partnums[i] == 0) { + continue; + } + if (m_partnums[i] == m_partnums[i-1]) { + continue; + } + m_free_text << "@PARTNAME-" << m_partnums[i] << ":\t" << m_names[i] << endl; } - double meterunit = m_currentMeterUnit[staffnum - 1]; - double tsd = (stof(ts)-1) * 4.0 / meterunit; - - GridMeasure* gm = m_outdata.back(); - double tsm = gm->getTimestamp().getFloat(); - bool foundslice = false; - GridSlice* gs; - for (auto gsit = gm->begin(); gsit != gm->end(); gsit++) { - gs = *gsit; - if (!gs->isDataSlice()) { + for (int i=1; i<(int)m_partnums.size(); i++) { + if (m_partnums[i] == 0) { continue; } - double gsts = gs->getTimestamp().getFloat(); - double difference = (gsts-tsm) - tsd; - if (!(fabs(difference) < 0.0001)) { + if (m_partnums[i] == m_partnums[i-1]) { continue; } - // GridVoice* voice = gs->at(staffnum-1)->at(0)->at(0); - // HTp token = voice->getToken(); - // if (token != NULL) { - // token->setValue("LO", "TX", "t", text); - // } else { - // cerr << "Strange null-token error while inserting dir element." << endl; - // } - foundslice = true; + m_free_text << "@PARTABBR-" << m_partnums[i] << ":\t" << m_abbreviations[i] << endl; + } - // Found data line which should prefixed with a layout line - // should be done with HumHash post-processing, but do it manually for now. + m_free_text << endl; - auto previousit = gsit; - previousit--; - if (previousit == gm->end()) { - previousit = gsit; + for (int i=0; i<(int)wordinfo.size(); i++) { + m_free_text << "@@BEGIN:\tWORD\n"; + m_free_text << "@PARTNUM:\t" << wordinfo[i].partnum << endl; + // m_free_text << "@NAME:\t\t" << wordinfo[i].name << endl; + // m_free_text << "@ABBR:\t\t" << wordinfo[i].abbreviation << endl; + m_free_text << "@WORD:\t\t" << wordinfo[i].word << endl; + m_free_text << "@STARTTIME:\t" << wordinfo[i].starttime.getFloat() << endl; + m_free_text << "@ENDTIME:\t" << wordinfo[i].endtime.getFloat() << endl; + m_free_text << "@STARTBAR:\t" << wordinfo[i].bar << endl; + + m_free_text << "@SYLLABLES:\t"; + for (int j=0; j<(int)wordinfo[i].syllables.size(); j++) { + m_free_text << wordinfo[i].syllables[j]; + if (j < (int)wordinfo[i].syllables.size() - 1) { + m_free_text << " "; + } } - auto previous = *previousit; - if (previous->isLayoutSlice()) { - GridVoice* voice = previous->at(staffnum-1)->at(0)->at(0); - HTp tok = voice->getToken(); - if (tok == NULL) { - HTp newtok = new HumdrumToken(message); - voice->setToken(newtok); - tok = voice->getToken(); - break; - } else if (tok->isNull()) { - tok->setText(message); - break; + m_free_text << endl; + + m_free_text << "@NOTECOUNTS:\t"; + for (int j=0; j<(int)wordinfo[i].notecounts.size(); j++) { + m_free_text << wordinfo[i].notecounts[j]; + if (j < (int)wordinfo[i].notecounts.size() - 1) { + m_free_text << " "; } } + m_free_text << endl; - // Insert a layout slice in front of current data slice. - GridSlice* ngs = new GridSlice(gm, gs->getTimestamp(), SliceType::Layouts, m_maxStaffInFile); - int parti = staffnum - 1; - int staffi = 0; - int voicei = 0; - ngs->addToken(message, parti, staffi, voicei); - gm->insert(gsit, ngs); + m_free_text << "@BARLINES:\t"; + for (int j=0; j<(int)wordinfo[i].bars.size(); j++) { + m_free_text << wordinfo[i].bars[j]; + if (j < (int)wordinfo[i].bars.size() - 1) { + m_free_text << " "; + } + } + m_free_text << endl; - break; - } - if (!foundslice) { - cerr << "Warning: dir elements not occuring at note/rest times are not yet supported" << endl; + m_free_text << "@STARTTIMES:\t"; + for (int j=0; j<(int)wordinfo[i].starttimes.size(); j++) { + m_free_text << wordinfo[i].starttimes[j].getFloat(); + if (j < (int)wordinfo[i].starttimes.size() - 1) { + m_free_text << " "; + } + } + m_free_text << endl; + + m_free_text << "@ENDTIMES:\t"; + for (int j=0; j<(int)wordinfo[i].endtimes.size(); j++) { + m_free_text << wordinfo[i].endtimes[j].getFloat(); + if (j < (int)wordinfo[i].endtimes.size() - 1) { + m_free_text << " "; + } + } + m_free_text << endl; + + m_free_text << "@@END:\tWORD\n"; + m_free_text << endl; } + + m_free_text << "@@END:\tMELISMAS\n"; + m_free_text << endl; } ////////////////////////////// // -// Tool_mei2hum::cleanWhiteSpace -- Convert newlines to "\n", and trim spaces. -// Also remove more than one space in a row. +// Tool_melisma::getScoreDuration -- // -string Tool_mei2hum::cleanWhiteSpace(const string& input) { - string output; - output.reserve(input.size() + 8); - bool foundstart = false; - for (int i=0; i<(int)input.size(); i++) { - if ((!foundstart) && std::isspace(input[i])) { +double Tool_melisma::getScoreDuration(HumdrumFile& infile) { + double output = 0.0; + for (int i=infile.getLineCount() - 1; i>=0; i--) { + if (!infile[i].isData()) { continue; } - foundstart = true; - if (input[i] == '\t') { - if ((!output.empty()) && (output.back() != ' ')) { - output += ' '; + output = (infile[i].getDurationFromStart() + infile[i].getDuration()).getFloat(); + break; + } + return output; +} + + + +////////////////////////////// +// +// Tool_melisma::getMelismaNoteCounts -- +// + +void Tool_melisma::getMelismaNoteCounts(vector& ncounts, vector& mcounts, HumdrumFile& infile) { + ncounts.resize(infile.getTrackCount() + 1); + mcounts.resize(infile.getTrackCount() + 1); + fill(ncounts.begin(), ncounts.end(), 0); + fill(mcounts.begin(), mcounts.end(), 0); + vector starts = infile.getKernSpineStartList(); + for (int i=0; i<(int)starts.size(); i++) { + HTp current = starts[i]; + int track = current->getTrack(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - } else if (input[i] == '\n') { - if ((!output.empty()) && (output.back() != ' ')) { - output += ' '; + if (current->isNull()) { + current = current->getNextToken(); + continue; } - } else if (input[i] == ' ') { - if ((!output.empty()) && (output.back() != ' ')) { - output += ' '; + if (current->isRest()) { + current = current->getNextToken(); + continue; } - } else { - output += input[i]; + if (!current->isNoteAttack()) { + current = current->getNextToken(); + continue; + } + ncounts[track]++; + if (current->find("@") != string::npos) { + mcounts[track]++; + } + current = current->getNextToken(); + } + } + + for (int i=1; i<(int)mcounts.size(); i++) { + mcounts[0] += mcounts[i]; + ncounts[0] += ncounts[i]; + } +} + + + +////////////////////////////// +// +// Tool_melisma::extractWordlist -- +// + +void Tool_melisma::extractWordlist(vector& wordinfo, map& wordlist, + HumdrumFile& infile, vector>& notecount) { + int mincount = getInteger("min"); + if (mincount < 2) { + mincount = 2; + } + string word; + WordInfo winfo; + for (int i=0; i<(int)notecount.size(); i++) { + for (int j=0; j<(int)notecount[i].size(); j++) { + if (notecount[i][j] < mincount) { + continue; + } + HTp token = infile.token(i, j); + word = extractWord(winfo, token, notecount); + wordlist[word]++; + int track = token->getTrack(); + winfo.name = m_names[track]; + winfo.abbreviation = m_abbreviations[track]; + winfo.partnum = m_partnums[track]; + wordinfo.push_back(winfo); } } - while ((!output.empty()) && (output.back() == ' ')) { - output.pop_back(); - } - - return output; } ////////////////////////////// // -// Tool_mei2hum::cleanDirText -- convert ":" to ":". -// Remove tabs and newlines, and trim spaces. Maybe allow -// newlines using "\n" and allow font changes in the future. -// Remove redundant whitespace. Do accents later perhaps or -// monitor for UTF-8. +// Tool_melisma::extractWord -- // -string Tool_mei2hum::cleanDirText(const string& input) { - string output; - output.reserve(input.size() + 8); - bool foundstart = false; - for (int i=0; i<(int)input.size(); i++) { - if ((!foundstart) && std::isspace(input[i])) { +string Tool_melisma::extractWord(WordInfo& winfo, HTp token, vector>& counts) { + winfo.clear(); + string output = *token; + string syllable; + HTp current = token; + while (current) { + if (!current->isData()) { + current = current->getPreviousToken(); continue; } - foundstart = true; - if (input[i] == ':') { - output += ":"; - } else if (input[i] == '\t') { - if ((!output.empty()) && (output.back() != ' ')) { - output += ' '; - } - } else if (input[i] == '\n') { - if ((!output.empty()) && (output.back() != ' ')) { - output += ' '; - } - } else if (input[i] == ' ') { - if ((!output.empty()) && (output.back() != ' ')) { - output += ' '; + if (current->isNull()) { + current = current->getPreviousToken(); + continue; + } + syllable = *current; + auto pos = syllable.rfind(" "); + if (pos != string::npos) { + syllable = syllable.substr(pos + 1); + } + if (syllable.size() > 0) { + if (syllable.at(0) == '-') { + current = current->getPreviousToken(); + continue; + } else { + // found start of word + break; } } else { - output += input[i]; + // some strange problem + break; } } - while ((!output.empty()) && (output.back() == ' ')) { - output.pop_back(); + if (!current) { + // strange problem (no start of word) + return ""; + } + if (syllable.size() == 0) { + return ""; } - return output; -} + winfo.starttime = current->getDurationFromStart(); + int line = current->getLineIndex(); + int field = current->getFieldIndex(); + winfo.endtime = m_endtimes[line][field]; + winfo.bar = m_measures[line]; + transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower); + if (syllable.back() == '-') { + syllable.resize(syllable.size() - 1); + winfo.syllables.push_back(syllable); + winfo.starttimes.push_back(current->getDurationFromStart()); + winfo.endtimes.push_back(m_endtimes[line][field]); + winfo.notecounts.push_back(counts[line][field]); + winfo.bars.push_back(m_measures[line]); + } else { + // single-syllable word + winfo.endtime = getEndtime(current); + transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower); + winfo.word = syllable; + winfo.syllables.push_back(syllable); + winfo.starttimes.push_back(current->getDurationFromStart()); + winfo.endtimes.push_back(m_endtimes[line][field]); + winfo.notecounts.push_back(counts[line][field]); + winfo.bars.push_back(m_measures[line]); + return syllable; + } + output = syllable; + HumRegex hre; + current = current->getNextToken(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + syllable = *current; -////////////////////////////// -// -// Tool_mei2hum::cleanVerseText -- -// Remove tabs and newlines, and trim spaces. -// Do accents later perhaps or monitor for UTF-8. -// + auto pos = syllable.find(" "); + if (pos != string::npos) { + syllable = syllable.substr(0, pos); + } -string Tool_mei2hum::cleanVerseText(const string& input) { - string output; - output.reserve(input.size() + 8); - bool foundstart = false; - for (int i=0; i<(int)input.size(); i++) { - if ((!foundstart) && std::isspace(input[i])) { - continue; + // if there is an elision of words and the second word is more + // than one syllable, then end the word at the apostrophe. + pos = syllable.find("'"); + if (pos != string::npos) { + if (syllable.back() == '-') { + syllable = syllable.substr(0, pos+1); + } } - foundstart = true; - if (input[i] == '\t') { - output += ' '; - } else if (input[i] == '\n') { - output += ' '; + + if (syllable.size() == 0) { + // strange problem + return ""; + } + if (syllable.at(0) != '-') { + // word was not terminated properly? + cerr << "Syllable error at syllable : " << syllable; + cerr << ", line: " << current->getLineNumber(); + cerr << ", field: " << current->getFieldNumber(); + cerr << endl; } else { - output += input[i]; + syllable = syllable.substr(1); + } + transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower); + winfo.endtime = getEndtime(current); + hre.replaceDestructive(syllable, "", "[<>.:?!;,\"]", "g"); + winfo.syllables.push_back(syllable); + winfo.starttimes.push_back(current->getDurationFromStart()); + int cline = current->getLineIndex(); + int cfield = current->getFieldIndex(); + winfo.endtimes.push_back(m_endtimes[cline][cfield]); + winfo.notecounts.push_back(counts[cline][cfield]); + winfo.bars.push_back(m_measures[cline]); + output += syllable; + if (output.back() == '-') { + output.resize(output.size() - 1); + current = current->getNextToken(); + winfo.syllables.back().resize((int)winfo.syllables.back().size() - 1); + continue; + } else { + // last syllable in word + break; } - } - while ((!output.empty()) && (output.back() == ' ')) { - output.pop_back(); } + winfo.word = output; return output; } @@ -96695,418 +100258,248 @@ string Tool_mei2hum::cleanVerseText(const string& input) { ////////////////////////////// // -// Tool_mei2hum::cleanReferenceRecordText -- convert ":" to ":". -// Remove tabs and newlines, and trim spaces. Maybe allow -// newlines using "\n" and allow font changes in the future. -// Do accents later perhaps or monitor for UTF-8. +// Tool_melisma::getEndtime -- // -string Tool_mei2hum::cleanReferenceRecordText(const string& input) { - string output; - output.reserve(input.size() + 8); - bool foundstart = false; - char lastchar = '\0'; - for (int i=0; i<(int)input.size(); i++) { - if ((!foundstart) && std::isspace(input[i])) { - continue; - } - foundstart = true; - if (input[i] == '\n') { - if (lastchar != ' ') { - output += ' '; - } - lastchar = ' '; - } else if (input[i] == '\t') { - if (lastchar != ' ') { - output += ' '; +HumNum Tool_melisma::getEndtime(HTp text) { + int line = text->getLineIndex(); + int field = text->getFieldIndex(); + return m_endtimes[line][field]; +} + + + +///////////////////////////// +// +// Tool_melisma::markMelismas -- +// + +void Tool_melisma::markMelismas(HumdrumFile& infile, vector>& counts) { + int mincount = getInteger("min"); + if (mincount < 2) { + mincount = 2; + } + for (int i=0; i<(int)counts.size(); i++) { + for (int j=0; j<(int)counts[i].size(); j++) { + if (counts[i][j] >= mincount) { + HTp token = infile.token(i, j); + markMelismaNotes(token, counts[i][j]); } - lastchar = ' '; - } else { - output += input[i]; - lastchar = input[i]; } } - while ((!output.empty()) && (output.back() == ' ')) { - output.pop_back(); - } - - return output; + infile.appendLine("!!!RDF**kern: @ = marked note (melisma)"); + infile.createLinesFromTokens(); } ////////////////////////////// // -// Tool_mei2hum::parseTempo -- -// -// Example: -// -// 1 - Allegro con spirito = 132 -// -// -// -// Ways of indicating tempo: -// -// tempo@midi.bpm == tempo per quarter note (Same as Humdrum *MM value) -// -// tempo@midi.mspb == microseconds per quarter note ( bpm = mspb * 60 / 1000000) -// -// tempo@mm == tempo per beat (bpm = mm / unit(dots)) -// tempo@mm.unit == beat unit for tempo@mm -// tempo@mm.dots == dots for tempo@unit -// -// Free-form text: -// -//  == quarter note +// Tool_melisma::markMelismaNotes -- // -// #define SMUFL_QUARTER_NOTE "\ue1d5" - -void Tool_mei2hum::parseTempo(xml_node tempo, HumNum starttime) { - NODE_VERIFY(tempo, ) - bool found = false; - double value = 0.0; +void Tool_melisma::markMelismaNotes(HTp text, int count) { + int counter = 0; - xml_attribute bpm = tempo.attribute("bpm"); - if (bpm) { - value = bpm.as_double(); - if (value > 0.0) { - found = true; + HTp current = text->getPreviousFieldToken(); + while (current) { + if (current->isKern()) { + break; } + current = current->getPreviousFieldToken(); } - - if (!found) { - xml_attribute mspb = tempo.attribute("mspb"); - value = mspb.as_double() * 60.0 / 1000000.0; - if (value > 0.0) { - found = true; - } + if (!current) { + return; } - - if (!found) { - xml_attribute mm = tempo.attribute("mm"); - xml_attribute mmunit = tempo.attribute("mm.unit"); - xml_attribute mmdots = tempo.attribute("mm.dots"); - value = mm.as_double(); - string recip = mmunit.value(); - int dcount = mmdots.as_int(); - for (int i=0; iisData()) { + current = current->getNextToken(); + continue; } - HumNum duration = Convert::recipToDuration(recip); - value *= duration.getFloat(); - if (value > 0.0) { - found = true; + if (current->isNull()) { + current = current->getNextToken(); + continue; } - } - - if (!found) { - // search for free-form tempo marking. Something like: - // - // 1 - Allegro con spirito = 132 - // - // - // UTF-8 version in string "\ue1d5"; - string text; - - MAKE_CHILD_LIST(children, tempo); - for (int i=0; i<(int)children.size(); i++) { - if (children[i].type() == pugi::node_pcdata) { - text += children[i].value(); - } else { - text += children[i].child_value(); - } - text += " "; - + if (current->isRest()) { + current = current->getNextToken(); + continue; } - HumRegex hre; - // #define SMUFL_QUARTER_NOTE "\ue1d5" - // if (hre.search(text, SMUFL_QUARTER_NOTE "\\s*=\\s*(\\d+\\.?\\d*)")) { - if (hre.search(text, "\\s*=\\s*(\\d+\\.?\\d*)")) { - // assuming quarter note for now. - value = hre.getMatchDouble(1); - found = true; + if (current->isNoteAttack()) { + counter++; } - // further rhythmic values for tempo should go here. - } - - // also deal with tempo designiations such as "Allegro"... - - if (!found) { - // no tempo to set - return; - } - - // insert tempo - GridMeasure* gm = m_outdata.back(); - GridSlice* gs = new GridSlice(gm, starttime, SliceType::Tempos, m_maxStaffInFile); - stringstream stok; - stok << "*MM" << value; - string token = stok.str(); - - for (int i=0; iat(i)->at(0)->at(0)->setToken(token); - } - - // insert after time signature at same timestamp if possible - bool inserted = false; - for (auto it = gm->begin(); it != gm->end(); it++) { - if ((*it)->getTimestamp() > starttime) { - gm->insert(it, gs); - inserted = true; - break; - } else if ((*it)->isTimeSigSlice()) { - it++; - gm->insert(it, gs); - inserted = true; - break; - } else if (((*it)->getTimestamp() == starttime) && ((*it)->isNoteSlice() - || (*it)->isGraceSlice())) { - gm->insert(it, gs); - inserted = true; + string text = *current; + text += "@"; + current->setText(text); + if (counter >= count) { break; } + current = current->getNextToken(); } - - if (!inserted) { - gm->push_back(gs); - } - } ////////////////////////////// // -// Tool_mei2hum::parseHarm -- Not yet ready to convert data. -// There will be different types of harm (such as figured bass), which -// will need to be subcategorized into different datatypes, such as -// *fb for figured bass. Also free-text can be present in -// data, so the current datatype for that is **cdata (meaning chord-like -// data that will be mapped back into which converting back to -// MEI data. -// -// Example: -// C major +// Tool_melisma::replaceLyrics -- // -void Tool_mei2hum::parseHarm(xml_node harm, HumNum starttime) { - NODE_VERIFY(harm, ) - MAKE_CHILD_LIST(children, harm); - - string text = harm.child_value(); - - if (text.empty()) { // looking at sub-elements - int count = 0; - for (int i=0; i<(int)children.size(); i++) { - string nodename = children[i].name(); - if (nodename == "rend") { - if (count) { - text += " "; - } - count++; - text += children[i].child_value(); - //if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) { - // font = ""; // normal is default in Humdrum layout - //} - //if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) { - // font += "B"; // normal is default in Humdrum layout - //} - } else if (nodename == "") { - // text node - if (count) { - text += " "; - } - count++; - text += children[i].value(); - } else { - cerr << DKHTP << harm.name() << "/" << nodename << CURRLOC << endl; +void Tool_melisma::replaceLyrics(HumdrumFile& infile, vector>& counts) { + for (int i=0; i<(int)counts.size(); i++) { + for (int j=0; j<(int)counts[i].size(); j++) { + if (counts[i][j] == -1) { + continue; } + string text = to_string(counts[i][j]); + HTp token = infile.token(i, j); + token->setText(text); } } + infile.createLinesFromTokens(); +} - if (text.empty()) { - return; - } - - // cerr << "FOUND HARM DATA " << text << endl; -/* - string startid = harm.attribute("startid").value(); +////////////////////////////// +// +// Tool_melisma::getNoteCounts -- +// - int staffnum = harm.attribute("staff").as_int(); - if (staffnum == 0) { - cerr << "Error: staff number required on harm element" << endl; - return; +void Tool_melisma::getNoteCounts(HumdrumFile& infile, vector>& counts) { + infile.initializeArray(counts, -1); + initBarlines(infile); + HumNum negativeOne = -1; + infile.initializeArray(m_endtimes, negativeOne); + vector lyrics; + infile.getSpineStartList(lyrics, "**text"); + for (int i=0; i<(int)lyrics.size(); i++) { + getNoteCountsForLyric(counts, lyrics[i]); } - double meterunit = m_currentMeterUnit[staffnum - 1]; +} - if (!startid.empty()) { - // Harmony is (or at least should) be attached directly - // do a note, so it is handled elsewhere. - cerr << "Warning DYNAMIC " << text << " is not yet processed." << endl; - return; - } - string ts = harm.attribute("tstamp").value(); - if (ts.empty()) { - cerr << "Error: no timestamp on harm element" << endl; - return; - } - double tsd = (stof(ts)-1) * 4.0 / meterunit; - double tolerance = 0.001; - GridMeasure* gm = m_outdata.back(); - double tsm = gm->getTimestamp().getFloat(); - bool foundslice = false; - GridSlice *nextgs = NULL; - for (auto gs : *gm) { - if (!gs->isDataSlice()) { - continue; - } - double gsts = gs->getTimestamp().getFloat(); - double difference = (gsts-tsm) - tsd; - if (difference < tolerance) { - // did not find data line at exact timestamp, so move - // the harm to the next event. Need to think about adding - // a new timeslice for the harm when it is not attached to - // a note. - nextgs = gs; - break; - } - if (!(fabs(difference) < tolerance)) { - continue; - } - GridPart* part = gs->at(staffnum-1); - part->setHarmony(text); - m_outdata.setHarmonyPresent(staffnum-1); - foundslice = true; - break; - } - if (!foundslice) { - if (nextgs == NULL) { - cerr << "Warning: harmony not attched to system events " - << "are not yet supported in measure " << m_currentMeasure << endl; - } else { - GridPart* part = nextgs->at(staffnum-1); - part->setHarmony(text); - m_outdata.setHarmonyPresent(staffnum-1); - // Give a time offset for displaying the harmmony here. + +////////////////////////////// +// +// Tool_melisma::initBarlines -- +// + +void Tool_melisma::initBarlines(HumdrumFile& infile) { + m_measures.resize(infile.getLineCount()); + fill(m_measures.begin(), m_measures.end(), 0); + HumRegex hre; + for (int i=1; ip +// Tool_melisma::getNoteCountsForLyric -- // -void Tool_mei2hum::parseDynam(xml_node dynam, HumNum starttime) { - NODE_VERIFY(dynam, ) - MAKE_CHILD_LIST(children, dynam); - - string text = dynam.child_value(); - - if (text.empty()) { // looking at sub-elements - int count = 0; - for (int i=0; i<(int)children.size(); i++) { - string nodename = children[i].name(); - if (nodename == "rend") { - if (count) { - text += " "; - } - count++; - text += children[i].child_value(); - //if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) { - // font = ""; // normal is default in Humdrum layout - //} - //if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) { - // font += "B"; // normal is default in Humdrum layout - //} - } else if (nodename == "") { - // text node - if (count) { - text += " "; - } - count++; - text += children[i].value(); - } else { - cerr << DKHTP << dynam.name() << "/" << nodename << CURRLOC << endl; - } +void Tool_melisma::getNoteCountsForLyric(vector>& counts, HTp lyricStart) { + HTp current = lyricStart; + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if (current->isNull()) { + current = current->getNextToken(); + continue; } + int line = current->getLineIndex(); + int field = current->getFieldIndex(); + counts[line][field] = getCountForSyllable(current); + current = current->getNextToken(); } +} - if (text.empty()) { - return; - } - string startid = dynam.attribute("startid").value(); - int staffnum = dynam.attribute("staff").as_int(); - if (staffnum == 0) { - cerr << "Error: staff number required on dynam element" << endl; - return; - } - double meterunit = m_currentMeterUnit[staffnum - 1]; +////////////////////////////// +// +// Tool_melisma::getCountForSyllable -- +// - if (!startid.empty()) { - // Dynamic is (or at least should) be attached directly - // do a note, so it is handled elsewhere. - cerr << "Warning DYNAMIC " << text << " is not yet processed." << endl; - return; +int Tool_melisma::getCountForSyllable(HTp token) { + if (token->back() == '&') { + return 1; + } + HTp nexttok = token->getNextToken(); + int eline = token->getLineIndex(); + int efield = token->getFieldIndex(); + m_endtimes[eline][efield] = token->getDurationFromStart() + token->getDuration(); + while (nexttok) { + if (!nexttok->isData()) { + nexttok = nexttok->getNextToken(); + continue; + } + if (nexttok->isNull()) { + nexttok = nexttok->getNextToken(); + continue; + } + // found non-null data token + break; } - string ts = dynam.attribute("tstamp").value(); - if (ts.empty()) { - cerr << "Error: no timestamp on dynam element" << endl; - return; + HumdrumFile& infile = *token->getOwner()->getOwner(); + int endline = infile.getLineCount() - 1; + if (nexttok) { + endline = nexttok->getLineIndex(); } - double tsd = (stof(ts)-1) * 4.0 / meterunit; - double tolerance = 0.001; - GridMeasure* gm = m_outdata.back(); - double tsm = gm->getTimestamp().getFloat(); - bool foundslice = false; - GridSlice *nextgs = NULL; - for (auto gs : *gm) { - if (!gs->isDataSlice()) { + int output = 0; + HTp current = token->getPreviousFieldToken(); + while (current) { + if (current->isKern()) { + break; + } + current = current->getPreviousFieldToken(); + } + if (!current) { + return 0; + } + while (current) { + if (!current->isData()) { + current = current->getNextToken(); continue; } - double gsts = gs->getTimestamp().getFloat(); - double difference = (gsts-tsm) - tsd; - if (difference < tolerance) { - // did not find data line at exact timestamp, so move - // the dynamic to the next event. Maybe think about adding - // a new timeslice for the dynamic. - nextgs = gs; - break; + if (current->isNull()) { + current = current->getNextToken(); + continue; } - if (!(fabs(difference) < tolerance)) { + if (current->isRest()) { + current = current->getNextToken(); continue; } - GridPart* part = gs->at(staffnum-1); - part->setDynamics(text); - m_outdata.setDynamicsPresent(staffnum-1); - foundslice = true; - break; - } - if (!foundslice) { - if (nextgs == NULL) { - cerr << "Warning: dynamics not attched to system events " - << "are not yet supported in measure " << m_currentMeasure << endl; + if (!current->isNoteAttack()) { + // ignore tied notes + m_endtimes[eline][efield] = current->getDurationFromStart() + current->getDuration(); + current = current->getNextToken(); + continue; + } + int line = current->getLineIndex(); + if (line < endline) { + m_endtimes[eline][efield] = current->getDurationFromStart() + current->getDuration(); + output++; } else { - GridPart* part = nextgs->at(staffnum-1); - part->setDynamics(text); - m_outdata.setDynamicsPresent(staffnum-1); - // Give a time offset for displaying the dynamic here. + break; } + current = current->getNextToken(); } + + return output; } @@ -97115,25 +100508,21 @@ void Tool_mei2hum::parseDynam(xml_node dynam, HumNum starttime) { ///////////////////////////////// // -// Tool_gridtest::Tool_melisma -- Set the recognized options for the tool. +// Tool_mens2kern::Tool_mens2kern -- Set the recognized options for the tool. // -Tool_melisma::Tool_melisma(void) { - define("m|min=i:2", "minimum length to identify as a melisma"); - define("r|replace=b", "replace lyrics with note counts"); - define("a|average|avg=b", "calculate note-to-syllable ratio"); - define("w|words=b", "list words that contain a melisma"); - define("p|part=b", "also calculate note-to-syllable ratios by part"); +Tool_mens2kern::Tool_mens2kern(void) { + define("debug=b", "print debugging statements"); } -/////////////////////////////// +///////////////////////////////// // -// Tool_melisma::run -- Primary interfaces to the tool. +// Tool_mens2kern::run -- Do the main work of the tool. // -bool Tool_melisma::run(HumdrumFileSet& infiles) { +bool Tool_mens2kern::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i> notecount; - getNoteCounts(infile, notecount); - vector wordinfo; - wordinfo.reserve(1000); - map wordlist; - initializePartInfo(infile); - - if (getBoolean("replace")) { - replaceLyrics(infile, notecount); - } else if (getBoolean("words")) { - markMelismas(infile, notecount); - extractWordlist(wordinfo, wordlist, infile, notecount); - printWordlist(infile, wordinfo, wordlist); - } else { - markMelismas(infile, notecount); +void Tool_mens2kern::processFile(HumdrumFile& infile) { + vector melody; + int scount = infile.getStrandCount(); + for (int i=0; iisDataType("**mens")) { + continue; + } + HTp sstop = infile.getStrandEnd(i); + HTp current = sstart; + while (current && (current != sstop)) { + if (current->isNull()) { + // ignore null data tokens + current = current->getNextToken(); + continue; + } + melody.push_back(current); + current = current->getNextToken(); + } + processMelody(melody); + melody.clear(); } + infile.createLinesFromTokens(); } ////////////////////////////// // -// Tool_melisma::initializePartInfo -- +// Tool_mens2kern::processMelody -- // -void Tool_melisma::initializePartInfo(HumdrumFile& infile) { - m_names.clear(); - m_abbreviations.clear(); - m_partnums.clear(); +void Tool_mens2kern::processMelody(vector& melody) { + int maximodus = 0; + int modus = 0; + int tempus = 0; + int prolatio = 0; + int semibrevis_def = 0; + int brevis_def = 0; + int longa_def = 0; + int maxima_def = 0; + string regexopts; + HumRegex hre; + string rhythm; + bool imperfecta; + bool perfecta; + bool altera; - m_names.resize(infile.getTrackCount() + 1); - m_abbreviations.resize(infile.getTrackCount() + 1); - m_partnums.resize(infile.getTrackCount() + 1); - fill(m_partnums.begin(), m_partnums.end(), -1); + for (int i=0; i<(int)melody.size(); i++) { + if (*melody[i] == "**mens") { + // convert spine to **kern data: + melody[i]->setText("**kern"); + } - vector starts; - infile.getSpineStartList(starts); - int ktrack = 0; - int track = 0; - int part = 0; - for (int i=0; i<(int)starts.size(); i++) { - track = starts[i]->getTrack(); - if (starts[i]->isKern()) { - ktrack = track; - part++; - m_partnums[ktrack] = part; - HTp current = starts[i]; - while (current) { - if (current->isData()) { - break; - } - if (current->compare(0, 3, "*I\"") == 0) { - m_names[ktrack] = current->substr(3); - } else if (current->compare(0, 3, "*I\'") == 0) { - m_abbreviations[ktrack] = current->substr(3); - } - current = current->getNextToken(); + if (melody[i]->isMensuration()) { + getMensuralInfo(melody[i], maximodus, modus, tempus, prolatio); + + // Default value of notes from maxima to semibrevis in minims: + semibrevis_def = prolatio; + brevis_def = tempus * semibrevis_def; + longa_def = modus * brevis_def; + maxima_def = maximodus * longa_def; + if (m_debugQ) { + cerr << "LEVELS X_def = " << maxima_def + << " | L_def = " << longa_def + << " | S_def = " << brevis_def + << " | s_def = " << semibrevis_def << endl; } - } else if (ktrack) { - m_names[track] = m_names[ktrack]; - m_abbreviations[track] = m_abbreviations[ktrack]; - m_partnums[track] = m_partnums[ktrack]; } + + if (!melody[i]->isData()) { + continue; + } + string text = melody[i]->getText(); + imperfecta = hre.search(text, "i") ? true : false; + perfecta = hre.search(text, "p") ? true : false; + altera = hre.search(text, "\\+") ? true : false; + if (hre.search(text, "([XLSsMmUu])")) { + rhythm = hre.getMatch(1); + } else { + cerr << "Error: token " << melody[i] << " has no rhythm" << endl; + cerr << " ON LINE: " << melody[i]->getLineNumber() << endl; + continue; + } + + string kernRhythm = mens2kernRhythm(rhythm, altera, perfecta, imperfecta, maxima_def, longa_def, brevis_def, semibrevis_def); + + hre.replaceDestructive(text, kernRhythm, rhythm); + // Remove any dot of division/augmentation + hre.replaceDestructive(text, "", ":"); + // remove perfection/imperfection/alteration markers + hre.replaceDestructive(text, "", "[pi\\+]"); + if (text.empty()) { + text = "."; + } + melody[i]->setText(text); + } +} + + +////////////////////////////// +// +// Tool_mens2kern::getMensuralInfo -- +// + +void Tool_mens2kern::getMensuralInfo(HTp token, int& maximodus, int& modus, + int& tempus, int& prolatio) { + HumRegex hre; + if (!hre.search(token, "^\\*met\\(.*?\\)_(\\d+)")) { + // need to interpret symbols without underscores. + if (token->getText() == "*met(C)") { + maximodus = 2; + modus = 2; + tempus = 2; + prolatio = 2; + } else if (token->getText() == "*met(O)") { + maximodus = 2; + modus = 2; + tempus = 3; + prolatio = 2; + } else if (token->getText() == "*met(C.)") { + maximodus = 2; + modus = 2; + tempus = 2; + prolatio = 3; + } else if (token->getText() == "*met(O.)") { + maximodus = 2; + modus = 2; + tempus = 3; + prolatio = 3; + } else if (token->getText() == "*met(C|)") { + maximodus = 2; + modus = 2; + tempus = 2; + prolatio = 2; + } else if (token->getText() == "*met(O|)") { + maximodus = 2; + modus = 2; + tempus = 3; + prolatio = 2; + } else if (token->getText() == "*met(C.|)") { + maximodus = 2; + modus = 2; + tempus = 2; + prolatio = 3; + } else if (token->getText() == "*met(O.|)") { + maximodus = 2; + modus = 2; + tempus = 3; + prolatio = 3; + } else if (token->getText() == "*met(C2)") { + maximodus = 2; + modus = 2; + tempus = 2; + prolatio = 2; + } else if (token->getText() == "*met(C3)") { + maximodus = 2; + modus = 2; + tempus = 3; + prolatio = 2; + } else if (token->getText() == "*met(O2)") { + maximodus = 2; + modus = 3; + tempus = 2; + prolatio = 2; + } else if (token->getText() == "*met(O3)") { + maximodus = 3; + modus = 3; + tempus = 3; + prolatio = 2; + } else if (token->getText() == "*met(C3/2)") { + maximodus = 2; + modus = 2; + tempus = 2; + prolatio = 3; + } else if (token->getText() == "*met(C|3/2)") { + maximodus = 2; + modus = 2; + tempus = 3; + prolatio = 2; + } + } else { + string levels = hre.getMatch(1); + if (levels.size() >= 1) { + maximodus = levels[0] - '0'; + } + if (levels.size() >= 2) { + modus = levels[1] - '0'; + } + if (levels.size() >= 3) { + tempus = levels[2] - '0'; + } + if (levels.size() >= 4) { + prolatio = levels[3] - '0'; + } + } + + if (m_debugQ) { + cerr << "MENSURAL INFO: maximodus = " << maximodus + << " | modus = " << modus + << " | tempus = " << tempus + << " | prolatio = " << prolatio << endl; + } +} + + + +////////////////////////////// +// +// Tool_mens2kern::mens2kernRhythm -- +// + +string Tool_mens2kern::mens2kernRhythm(const string& rhythm, bool altera, bool perfecta, bool imperfecta, int maxima_def, int longa_def, int brevis_def, int semibrevis_def) { + double val_note; + double minima_def = 1; + double semiminima_def = 0.5; + double fusa_def = 0.25; + double semifusa_def = 0.125; + + if (rhythm == "X") { + if (perfecta) { val_note = 3 * longa_def; } + else if (imperfecta) { val_note = 2 * longa_def; } + else { val_note = maxima_def; } + } + else if (rhythm == "L") { + if (perfecta) { val_note = 3 * brevis_def; } + else if (imperfecta) { val_note = 2 * brevis_def; } + else if (altera) { val_note = 2 * longa_def; } + else { val_note = longa_def; } + } + else if (rhythm == "S") { + if (perfecta) { val_note = 3 * semibrevis_def; } + else if (imperfecta) { val_note = 2 * semibrevis_def; } + else if (altera) { val_note = 2 * brevis_def; } + else { val_note = brevis_def; } + } + else if (rhythm == "s") { + if (perfecta) { val_note = 3 * minima_def; } + else if (imperfecta) { val_note = 2 * minima_def; } + else if (altera) { val_note = 2 * semibrevis_def; } + else { val_note = semibrevis_def; } + } + else if (rhythm == "M") { + if (perfecta) { val_note = 1.5 * minima_def; } + else if (altera) { val_note = 2 * minima_def; } + else { val_note = minima_def; } + } + else if (rhythm == "m") { + if (perfecta) { val_note = 1.5 * semiminima_def; } + else { val_note = semiminima_def; } + } + else if (rhythm == "U") { + if (perfecta) { val_note = 1.5 * fusa_def; } + else { val_note = fusa_def; } + } + else if (rhythm == "u") { + if (perfecta) { val_note = 1.5 * semifusa_def; } + else { val_note = semifusa_def; } + } + else { cerr << "UNKNOWN RHYTHM: " << rhythm << endl; return ""; } + + switch ((int)(val_note * 10000)) { + case 1250: return "16"; break; // sixteenth note + case 1875: return "16."; break; // dotted sixteenth note + case 2500: return "8"; break; // eighth note + case 3750: return "8."; break; // dotted eighth note + case 5000: return "4"; break; // quarter note + case 7500: return "4."; break; // dotted quarter note + case 10000: return "2"; break; // half note + case 15000: return "2."; break; // dotted half note + case 20000: return "1"; break; // whole note + case 30000: return "1."; break; // dotted whole note + case 40000: return "0"; break; // breve note + case 60000: return "0."; break; // dotted breve note + case 90000: return "2%9"; break; // or ["0.", "1."]; + case 80000: return "00"; break; // long note + case 120000: return "00."; break; // dotted long note + case 180000: return "1%9"; break; // or ["00.", "0."]; + case 270000: return "2%27"; break; // or ["0.", "1.", "0.", "1.", "0.", "1."]; + case 160000: return "000"; break; // maxima note + case 240000: return "000."; break; // dotted maxima note + case 360000: return "1%18"; break; // or ["000.", "00."]; + case 540000: return "1%27"; break; // or ["00.", "0.", "00.", "0.", "00.", "0."]; + case 810000: return "2%81"; break; // or ["00.", "0.", "00.", "0.", "00.", "0.", "0.", "1.", "0.", "1.", "0.", "1."]; + default: + cerr << "Error: unknown val_note: " << val_note << endl; } + return ""; } -////////////////////////////// + +///////////////////////////////// // -// printWordlist -- +// Tool_meter::Tool_meter -- Set the recognized options for the tool. // -void Tool_melisma::printWordlist(HumdrumFile& infile, vector& wordinfo, - map words) { - - // for (auto& item : words) { - // m_free_text << item.first; - // if (item.second > 1) { - // m_free_text << " (" << item.second << ")"; - // } - // m_free_text << endl; - // } - - vector ncounts; - vector mcounts; - getMelismaNoteCounts(ncounts, mcounts, infile); +Tool_meter::Tool_meter(void) { + define("c|comma=b", "display decimal points as commas"); + define("d|denominator=b", "display denominator spine"); + define("e|eighth=b", "metric positions in eighth notes rather than beats"); + define("f|float=b", "floating-point beat values instead of rational numbers"); + define("h|half=b", "metric positions in half notes rather than beats"); + define("j|join=b", "join time signature information and metric positions into a single token"); + define("n|numerator=b", "display numerator spine"); + define("q|quarter=b", "metric positions in quarter notes rather than beats"); + define("r|rest=b", "add meteric positions of rests"); + define("s|sixteenth=b", "metric positions in sixteenth notes rather than beats"); + define("t|time-signature|tsig|m|meter=b", "display active time signature for each note"); + define("w|whole=b", "metric positions in whole notes rather than beats"); + define("z|zero=b", "start of measure is beat 0 rather than beat 1"); - // m_free_text << "===========================" << endl; + define("B|no-beat=b", "Do not display metric positions (beats)"); + define("D|digits=i:0", "number of digits after decimal point"); + define("L|no-label=b", "do not add labels to analysis spines"); +} - std::vector kspines = infile.getKernSpineStartList(); - m_free_text << "@@BEGIN:\tMELISMAS\n"; - string filename = infile.getFilename(); - auto pos = filename.rfind("/"); - if (pos != string::npos) { - filename = filename.substr(pos+1); - } - m_free_text << "@FILENAME:\t" << filename << endl; - m_free_text << "@PARTCOUNT:\t" << kspines.size() << endl; - m_free_text << "@WORDCOUNT:\t" << wordinfo.size() << endl; - m_free_text << "@SCOREDURATION:\t" << getScoreDuration(infile) << endl; - m_free_text << "@NOTES:\t\t" << ncounts[0] << endl; - m_free_text << "@MELISMANOTES:\t" << mcounts[0] << endl; +///////////////////////////////// +// +// Tool_meter::run -- Do the main work of the tool. +// - m_free_text << "@MELISMASCORE:\t" << int((double)mcounts[0] / (double)ncounts[0] * 1000.0 + 0.5)/10.0 << "%" << endl; - for (int i=1; i<(int)m_partnums.size(); i++) { - if (m_partnums[i] == 0) { - continue; - } - if (m_partnums[i] == m_partnums[i-1]) { - continue; - } - m_free_text << "@PARTSCORE-" << m_partnums[i] << ":\t" << int((double)mcounts[i] / (double)ncounts[i] * 1000.0 + 0.5)/10.0 << "%" << endl; +bool Tool_meter::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i=0; i--) { - if (!infile[i].isData()) { - continue; - } - output = (infile[i].getDurationFromStart() + infile[i].getDuration()).getFloat(); - break; - } - return output; -} +void Tool_meter::initialize(void) { + m_commaQ = getBoolean("comma"); + m_denominatorQ = getBoolean("denominator"); + m_digits = getInteger("digits"); + m_floatQ = getBoolean("float"); + m_halfQ = getBoolean("half"); + m_joinQ = getBoolean("join"); + m_nobeatQ = getBoolean("no-beat"); + m_nolabelQ = getBoolean("no-label"); + m_numeratorQ = getBoolean("numerator"); + m_quarterQ = getBoolean("quarter"); + m_halfQ = getBoolean("half"); + m_eighthQ = getBoolean("eighth"); + m_sixteenthQ = getBoolean("sixteenth"); + m_restQ = getBoolean("rest"); + m_tsigQ = getBoolean("meter"); + m_wholeQ = getBoolean("whole"); + m_zeroQ = getBoolean("zero"); + if (m_digits < 0) { + m_digits = 0; + } + if (m_digits > 15) { + m_digits = 15; + } -////////////////////////////// -// -// Tool_melisma::getMelismaNoteCounts -- -// + if (m_joinQ && !(m_tsigQ || m_numeratorQ || m_denominatorQ)) { + m_tsigQ = true; + } + if (m_joinQ) { + m_nobeatQ = false; + } + if (m_joinQ && m_numeratorQ && m_denominatorQ) { + m_tsigQ = true; + } -void Tool_melisma::getMelismaNoteCounts(vector& ncounts, vector& mcounts, HumdrumFile& infile) { - ncounts.resize(infile.getTrackCount() + 1); - mcounts.resize(infile.getTrackCount() + 1); - fill(ncounts.begin(), ncounts.end(), 0); - fill(mcounts.begin(), mcounts.end(), 0); - vector starts = infile.getKernSpineStartList(); - for (int i=0; i<(int)starts.size(); i++) { - HTp current = starts[i]; - int track = current->getTrack(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->isRest()) { - current = current->getNextToken(); - continue; - } - if (!current->isNoteAttack()) { - current = current->getNextToken(); - continue; - } - ncounts[track]++; - if (current->find("@") != string::npos) { - mcounts[track]++; - } - current = current->getNextToken(); - } + if (m_tsigQ) { + m_numeratorQ = true; + m_denominatorQ = true; } - for (int i=1; i<(int)mcounts.size(); i++) { - mcounts[0] += mcounts[i]; - ncounts[0] += ncounts[i]; + // Only one fix-width metric position allowed, prioritize + // largest given duration: + if (m_wholeQ) { + m_halfQ = false; + m_quarterQ = false; + m_eighthQ = false; + m_sixteenthQ = false; + } else if (m_halfQ) { + m_wholeQ = false; + m_quarterQ = false; + m_eighthQ = false; + m_sixteenthQ = false; + } else if (m_quarterQ) { + m_wholeQ = false; + m_halfQ = false; + m_eighthQ = false; + m_sixteenthQ = false; + } else if (m_eighthQ) { + m_wholeQ = false; + m_halfQ = false; + m_quarterQ = false; + m_sixteenthQ = false; + } else if (m_sixteenthQ) { + m_wholeQ = false; + m_halfQ = false; + m_quarterQ = false; + m_eighthQ = false; } + } ////////////////////////////// // -// Tool_melisma::extractWordlist -- +// Tool_meter::processFile -- // -void Tool_melisma::extractWordlist(vector& wordinfo, map& wordlist, - HumdrumFile& infile, vector>& notecount) { - int mincount = getInteger("min"); - if (mincount < 2) { - mincount = 2; - } - string word; - WordInfo winfo; - for (int i=0; i<(int)notecount.size(); i++) { - for (int j=0; j<(int)notecount[i].size(); j++) { - if (notecount[i][j] < mincount) { - continue; - } - HTp token = infile.token(i, j); - word = extractWord(winfo, token, notecount); - wordlist[word]++; - int track = token->getTrack(); - winfo.name = m_names[track]; - winfo.abbreviation = m_abbreviations[track]; - winfo.partnum = m_partnums[track]; - wordinfo.push_back(winfo); - } - } +void Tool_meter::processFile(HumdrumFile& infile) { + analyzePickupMeasures(infile); + getMeterData(infile); + printMeterData(infile); } - ////////////////////////////// // -// Tool_melisma::extractWord -- +// Tool_meter::analyzePickupMeasures -- // -string Tool_melisma::extractWord(WordInfo& winfo, HTp token, vector>& counts) { - winfo.clear(); - string output = *token; - string syllable; - HTp current = token; +void Tool_meter::analyzePickupMeasures(HumdrumFile& infile) { + vector sstarts; + infile.getKernSpineStartList(sstarts); + for (int i=0; i<(int)sstarts.size(); i++) { + analyzePickupMeasures(sstarts[i]); + } +} + + +void Tool_meter::analyzePickupMeasures(HTp sstart) { + // First dimension are visible barlines. + // Second dimension are time signature(s) within the barlines. + vector> barandtime; + barandtime.reserve(1000); + barandtime.resize(1); + barandtime[0].push_back(sstart); + HTp current = sstart->getNextToken(); while (current) { - if (!current->isData()) { - current = current->getPreviousToken(); - continue; - } - if (current->isNull()) { - current = current->getPreviousToken(); - continue; - } - syllable = *current; - auto pos = syllable.rfind(" "); - if (pos != string::npos) { - syllable = syllable.substr(pos + 1); - } - if (syllable.size() > 0) { - if (syllable.at(0) == '-') { - current = current->getPreviousToken(); + if (current->isTimeSignature()) { + barandtime.back().push_back(current); + } else if (current->isBarline()) { + if (current->find("-") != std::string::npos) { + current = current->getNextToken(); continue; - } else { - // found start of word - break; } - } else { - // some strange problem + barandtime.resize(barandtime.size() + 1); + barandtime.back().push_back(current); + } else if (*current == "*-") { + barandtime.resize(barandtime.size() + 1); + barandtime.back().push_back(current); break; } + current = current->getNextToken(); } - if (!current) { - // strange problem (no start of word) - return ""; - } - if (syllable.size() == 0) { - return ""; - } - - winfo.starttime = current->getDurationFromStart(); - int line = current->getLineIndex(); - int field = current->getFieldIndex(); - winfo.endtime = m_endtimes[line][field]; - winfo.bar = m_measures[line]; - transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower); - if (syllable.back() == '-') { - syllable.resize(syllable.size() - 1); - winfo.syllables.push_back(syllable); - winfo.starttimes.push_back(current->getDurationFromStart()); - winfo.endtimes.push_back(m_endtimes[line][field]); - winfo.notecounts.push_back(counts[line][field]); - winfo.bars.push_back(m_measures[line]); - } else { - // single-syllable word - winfo.endtime = getEndtime(current); - transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower); - winfo.word = syllable; - winfo.syllables.push_back(syllable); - winfo.starttimes.push_back(current->getDurationFromStart()); - winfo.endtimes.push_back(m_endtimes[line][field]); - winfo.notecounts.push_back(counts[line][field]); - winfo.bars.push_back(m_measures[line]); - return syllable; + // Extract the actual duration of measures: + vector bardur(barandtime.size(), 0); + for (int i=0; i<(int)barandtime.size() - 1; i++) { + HumNum starttime = barandtime[i][0]->getDurationFromStart(); + HumNum endtime = barandtime.at(i+1)[0]->getDurationFromStart(); + HumNum duration = endtime - starttime; + bardur.at(i) = duration; } - output = syllable; - HumRegex hre; - current = current->getNextToken(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; + // Extract the expected duration of measures: + vector tsigdur(barandtime.size(), 0); + int firstmeasure = -1; + HumNum active = 0; + for (int i=0; i<(int)barandtime.size() - 1; i++) { + if (firstmeasure < 0) { + if (bardur.at(i) > 0) { + firstmeasure = i; + } } - if (current->isNull()) { - current = current->getNextToken(); + if (barandtime[i].size() < 2) { + tsigdur.at(i) = active; continue; } - syllable = *current; + active = getTimeSigDuration(barandtime.at(i).at(1)); + tsigdur.at(i) = active; + } - auto pos = syllable.find(" "); - if (pos != string::npos) { - syllable = syllable.substr(0, pos); + vector pickup(barandtime.size(), false); + for (int i=0; i<(int)barandtime.size() - 1; i++) { + if (tsigdur.at(i) == bardur.at(i)) { + // actual and expected are the same + continue; } - - // if there is an elision of words and the second word is more - // than one syllable, then end the word at the apostrophe. - pos = syllable.find("'"); - if (pos != string::npos) { - if (syllable.back() == '-') { - syllable = syllable.substr(0, pos+1); + if (tsigdur.at(i) == tsigdur.at(i+1)) { + if (bardur.at(i) + bardur.at(i+1) == tsigdur.at(i)) { + pickup.at(i+1) = true; + i++; + continue; } } + } - if (syllable.size() == 0) { - // strange problem - return ""; + // check for first-measure pickup + if (firstmeasure >= 0) { + if (bardur.at(firstmeasure) < tsigdur.at(firstmeasure)) { + pickup.at(firstmeasure) = true; } - if (syllable.at(0) != '-') { - // word was not terminated properly? - cerr << "Syllable error at syllable : " << syllable; - cerr << ", line: " << current->getLineNumber(); - cerr << ", field: " << current->getFieldNumber(); + } + + if (m_debugQ) { + cerr << "============================" << endl; + for (int i=0; i<(int)barandtime.size(); i++) { + cerr << pickup.at(i); + cerr << "\t"; + cerr << bardur.at(i); + cerr << "\t"; + cerr << tsigdur.at(i); + cerr << "\t"; + for (int j=0; j<(int)barandtime[i].size(); j++) { + cerr << barandtime.at(i).at(j) << "\t"; + } cerr << endl; - } else { - syllable = syllable.substr(1); } - transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower); - winfo.endtime = getEndtime(current); - hre.replaceDestructive(syllable, "", "[<>.:?!;,\"]", "g"); - winfo.syllables.push_back(syllable); - winfo.starttimes.push_back(current->getDurationFromStart()); - int cline = current->getLineIndex(); - int cfield = current->getFieldIndex(); - winfo.endtimes.push_back(m_endtimes[cline][cfield]); - winfo.notecounts.push_back(counts[cline][cfield]); - winfo.bars.push_back(m_measures[cline]); - output += syllable; - if (output.back() == '-') { - output.resize(output.size() - 1); - current = current->getNextToken(); - winfo.syllables.back().resize((int)winfo.syllables.back().size() - 1); + cerr << endl; + } + + // Markup pickup measure notes/rests + for (int i=0; i<(int)pickup.size() - 1; i++) { + if (!pickup[i]) { continue; - } else { - // last syllable in word - break; } + markPickupContent(barandtime.at(i).at(0), barandtime.at(i+1).at(0)); } - winfo.word = output; - return output; + // Pickup/incomplete measures covering three or more barlines are not considered + // (these could be used with dashed barlines or similar). + } ////////////////////////////// // -// Tool_melisma::getEndtime -- +// Tool_meter::markPickupContent -- // -HumNum Tool_melisma::getEndtime(HTp text) { - int line = text->getLineIndex(); - int field = text->getFieldIndex(); - return m_endtimes[line][field]; +void Tool_meter::markPickupContent(HTp stok, HTp etok) { + int endline = etok->getLineIndex(); + HTp current = stok; + while (current) { + int line = current->getLineIndex(); + if (line > endline) { + break; + } + if (current->isData()) { + HTp field = current; + int track = field->getTrack(); + while (field) { + int ttrack = field->getTrack(); + if (ttrack != track) { + break; + } + if (field->isNull()) { + field = field->getNextFieldToken(); + continue; + } + field->setValue("auto", "pickup", 1); + HumNum nbt = etok->getDurationFromStart() - field->getDurationFromStart(); + stringstream ntime; + ntime.str(""); + ntime << nbt.getNumerator() << "/" << nbt.getDenominator(); + field->setValue("auto", "nextBarTime", ntime.str()); + field = field->getNextFieldToken(); + } + } + if (current == etok) { + break; + } + current = current->getNextToken(); + } } -///////////////////////////// +////////////////////////////// // -// Tool_melisma::markMelismas -- +// Tool_meter::getTimeSigDuration -- // -void Tool_melisma::markMelismas(HumdrumFile& infile, vector>& counts) { - int mincount = getInteger("min"); - if (mincount < 2) { - mincount = 2; - } - for (int i=0; i<(int)counts.size(); i++) { - for (int j=0; j<(int)counts[i].size(); j++) { - if (counts[i][j] >= mincount) { - HTp token = infile.token(i, j); - markMelismaNotes(token, counts[i][j]); - } - } +HumNum Tool_meter::getTimeSigDuration(HTp tsig) { + HumNum output = 0; + HumRegex hre; + if (hre.search(tsig, "^\\*M(\\d+)/(\\d+%?\\d*)")) { + int top = hre.getMatchInt(1); + string bot = hre.getMatch(2); + HumNum botdur = Convert::recipToDuration(bot); + output = botdur * top; } - infile.appendLine("!!!RDF**kern: @ = marked note (melisma)"); - infile.createLinesFromTokens(); + return output; } ////////////////////////////// // -// Tool_melisma::markMelismaNotes -- +// Tool_meter::printMeterData -- // -void Tool_melisma::markMelismaNotes(HTp text, int count) { - int counter = 0; +void Tool_meter::printMeterData(HumdrumFile& infile) { + bool foundLabel = false; + bool foundData = false; - HTp current = text->getPreviousFieldToken(); - while (current) { - if (current->isKern()) { - break; - } - current = current->getPreviousFieldToken(); - } - if (!current) { - return; - } - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->isRest()) { - current = current->getNextToken(); + for (int i=0; iisNoteAttack()) { - counter++; + + if ((!foundData) && (!foundLabel) && (!m_nolabelQ) && infile[i].isData()) { + printLabelLine(infile[i]); + foundData = true; } - string text = *current; - text += "@"; - current->setText(text); - if (counter >= count) { - break; + + bool hasLabel = false; + if ((!m_nolabelQ) && (!foundLabel) && (!foundData)) { + if (searchForLabels(infile[i])) { + hasLabel = true; + foundLabel = true; + } } - current = current->getNextToken(); + printHumdrumLine(infile[i], hasLabel); } } @@ -97709,534 +101256,667 @@ void Tool_melisma::markMelismaNotes(HTp text, int count) { ////////////////////////////// // -// Tool_melisma::replaceLyrics -- +// printLabelLine -- // -void Tool_melisma::replaceLyrics(HumdrumFile& infile, vector>& counts) { - for (int i=0; i<(int)counts.size(); i++) { - for (int j=0; j<(int)counts[i].size(); j++) { - if (counts[i][j] == -1) { - continue; - } - string text = to_string(counts[i][j]); - HTp token = infile.token(i, j); - token->setText(text); +void Tool_meter::printLabelLine(HumdrumLine& line) { + bool forceInterpretation = true; + bool printLabels = true; + for (int i=0; iisKern()) { + i = printKernAndAnalysisSpine(line, i, printLabels, forceInterpretation); + } else { + m_humdrum_text << "*"; + } + if (i < line.getFieldCount() - 1) { + m_humdrum_text << "\t"; } } - infile.createLinesFromTokens(); + m_humdrum_text << "\n"; } ////////////////////////////// // -// Tool_melisma::getNoteCounts -- +// Tool_meter::printHumdrumLine -- // -void Tool_melisma::getNoteCounts(HumdrumFile& infile, vector>& counts) { - infile.initializeArray(counts, -1); - initBarlines(infile); - HumNum negativeOne = -1; - infile.initializeArray(m_endtimes, negativeOne); - vector lyrics; - infile.getSpineStartList(lyrics, "**text"); - for (int i=0; i<(int)lyrics.size(); i++) { - getNoteCountsForLyric(counts, lyrics[i]); +void Tool_meter::printHumdrumLine(HumdrumLine& line, bool printLabels) { + + for (int i=0; iisKern()) { + i = printKernAndAnalysisSpine(line, i, printLabels); + } else { + m_humdrum_text << token; + } + if (i < line.getFieldCount() - 1) { + m_humdrum_text << "\t"; + } } + m_humdrum_text << "\n"; } ////////////////////////////// // -// Tool_melisma::initBarlines -- +// Tool_meter::searchForLabels -- // -void Tool_melisma::initBarlines(HumdrumFile& infile) { - m_measures.resize(infile.getLineCount()); - fill(m_measures.begin(), m_measures.end(), 0); +bool Tool_meter::searchForLabels(HumdrumLine& line) { + if (!line.isInterpretation()) { + return false; + } HumRegex hre; - for (int i=1; i>& counts, HTp lyricStart) { - HTp current = lyricStart; - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - int line = current->getLineIndex(); - int field = current->getFieldIndex(); - counts[line][field] = getCountForSyllable(current); - current = current->getNextToken(); +HumNum Tool_meter::getHumNum(HTp token, const string& parameter) { + HumRegex hre; + HumNum output; + string value = token->getValue("auto", parameter); + if (hre.search(value, "(\\d+)/(\\d+)")) { + output = hre.getMatchInt(1); + output /= hre.getMatchInt(2); + } else if (hre.search(value, "(\\d+)")) { + output = hre.getMatchInt(1); } + return output; } ////////////////////////////// // -// Tool_melisma::getCountForSyllable -- +// Tool_meter::getHumNumString -- // -int Tool_melisma::getCountForSyllable(HTp token) { - if (token->back() == '&') { - return 1; - } - HTp nexttok = token->getNextToken(); - int eline = token->getLineIndex(); - int efield = token->getFieldIndex(); - m_endtimes[eline][efield] = token->getDurationFromStart() + token->getDuration(); - while (nexttok) { - if (!nexttok->isData()) { - nexttok = nexttok->getNextToken(); - continue; +string Tool_meter::getHumNumString(HumNum input) { + stringstream tem; + input.printTwoPart(tem); + return tem.str(); +} + + + +////////////////////////////// +// +// Tool_meter::printKernAndAnalysisSpine -- +// + +int Tool_meter::printKernAndAnalysisSpine(HumdrumLine& line, int index, bool printLabels, bool forceInterpretation) { + HTp starttok = line.token(index); + int track = starttok->getTrack(); + int counter = 0; + + string analysis = "."; + string numerator = "."; + string denominator = "."; + string meter = "."; + bool hasNote = false; + bool hasRest = false; + + for (int i=index; igetTrack(); + if (ttrack != track) { + break; } - if (nexttok->isNull()) { - nexttok = nexttok->getNextToken(); - continue; + if (counter > 0) { + m_humdrum_text << "\t"; + } + counter++; + if (forceInterpretation) { + m_humdrum_text << "*"; + } else { + m_humdrum_text << token; } - // found non-null data token - break; - } - HumdrumFile& infile = *token->getOwner()->getOwner(); - int endline = infile.getLineCount() - 1; - if (nexttok) { - endline = nexttok->getLineIndex(); - } - int output = 0; - HTp current = token->getPreviousFieldToken(); - while (current) { - if (current->isKern()) { - break; + if (line.isData() && !forceInterpretation) { + if (token->isNull()) { + // analysis = "."; + } else if (token->isRest() && !m_restQ) { + // analysis = "."; + } else if ((!token->isNoteAttack()) && !(m_restQ && token->isRest())) { + // analysis = "."; + } else if ((analysis == ".") && (token->getValueBool("auto", "hasData"))) { + string data = token->getValue("auto", "zeroBeat"); + if (m_restQ) { + if (token->isRest()) { + hasRest = true; + } else { + hasNote = true; + } + } + HumNum value; + HumNum nvalue; + HumNum dvalue; + if (!data.empty()) { + value = getHumNum(token, "zeroBeat"); + if (m_numeratorQ) { + nvalue = getHumNum(token, "numerator"); + numerator = getHumNumString(nvalue); + } + if (m_denominatorQ) { + dvalue = getHumNum(token, "denominator"); + denominator = getHumNumString(dvalue); + } + if (m_tsigQ) { + meter = numerator; + meter += "/"; + meter += denominator; + } + } + if (!m_zeroQ) { + value += 1; + } + if (m_floatQ) { + stringstream tem; + if (m_digits) { + tem << std::setprecision(m_digits + 1) << value.getFloat(); + } else { + tem << value.getFloat(); + } + analysis = tem.str(); + if (m_commaQ) { + HumRegex hre; + hre.replaceDestructive(analysis, ",", "\\."); + } + } else { + analysis = getHumNumString(value); + } + } + } else if (line.isInterpretation() || forceInterpretation) { + if (token->compare(0, 2, "**") == 0) { + analysis = "**cdata-beat"; + if (m_tsigQ) { + meter = "**cdata-tsig"; + } + if (m_numeratorQ) { + numerator = "**cdata-num"; + } + if (m_denominatorQ) { + denominator = "**cdata-den"; + } + } else if (*token == "*-") { + analysis = "*-"; + numerator = "*-"; + denominator = "*-"; + meter = "*-"; + } else if (token->isTimeSignature()) { + analysis = *token; + } else { + analysis = "*"; + numerator = "*"; + denominator = "*"; + meter = "*"; + if (printLabels) { + if (m_quarterQ) { + analysis = "*vi:4ths:"; + } else if (m_eighthQ) { + analysis = "*vi:8ths:"; + } else if (m_halfQ) { + analysis = "*vi:half:"; + } else if (m_wholeQ) { + analysis = "*vi:whole:"; + } else if (m_sixteenthQ) { + analysis = "*vi:16ths:"; + } else { + analysis = "*vi:beat:"; + } + numerator = "*vi:top:"; + denominator = "*vi:bot:"; + meter = "*vi:tsig:"; + if (m_joinQ) { + numerator = ""; + denominator = ""; + meter = ""; + } + } + } + } else if (line.isBarline()) { + analysis = *token; + numerator = *token; + denominator = *token; + meter = *token; + } else if (line.isCommentLocal()) { + analysis = "!"; + numerator = "!"; + denominator = "!"; + meter = "!"; + } else { + cerr << "STRANGE LINE: " << line << endl; } - current = current->getPreviousFieldToken(); - } - if (!current) { - return 0; } - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; + + if (m_joinQ) { + if (line.isData() && !forceInterpretation) { + if (m_tsigQ) { + m_humdrum_text << "\t" << meter; + } else { + if (m_numeratorQ) { + m_humdrum_text << "\t" << numerator; + } + if (m_denominatorQ) { + m_humdrum_text << "\t" << denominator; + } + } } - if (current->isRest()) { - current = current->getNextToken(); - continue; + if (!m_nobeatQ) { + if (line.isData() && !forceInterpretation) { + m_humdrum_text << ":"; + } else { + m_humdrum_text << "\t"; + } + m_humdrum_text << analysis; + if (line.isData() && hasRest && !hasNote) { + m_humdrum_text << "r"; + } } - if (!current->isNoteAttack()) { - // ignore tied notes - m_endtimes[eline][efield] = current->getDurationFromStart() + current->getDuration(); - current = current->getNextToken(); - continue; + } else { + if (!m_nobeatQ) { + m_humdrum_text << "\t" << analysis; + if (line.isData() && hasRest && !hasNote) { + m_humdrum_text << "r"; + } } - int line = current->getLineIndex(); - if (line < endline) { - m_endtimes[eline][efield] = current->getDurationFromStart() + current->getDuration(); - output++; + if (m_tsigQ) { + m_humdrum_text << "\t" << meter; } else { - break; + if (m_numeratorQ) { + m_humdrum_text << "\t" << numerator; + } + if (m_denominatorQ) { + m_humdrum_text << "\t" << denominator; + } } - current = current->getNextToken(); - } - - return output; -} - + } - - -///////////////////////////////// -// -// Tool_mens2kern::Tool_mens2kern -- Set the recognized options for the tool. -// - -Tool_mens2kern::Tool_mens2kern(void) { - define("debug=b", "print debugging statements"); + return index + counter - 1; } -///////////////////////////////// +////////////////////////////// // -// Tool_mens2kern::run -- Do the main work of the tool. +// Tool_meter::getMeterData -- // -bool Tool_mens2kern::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i curNum(maxtrack + 1, 0); + vector curDen(maxtrack + 1, 0); + vector curBeat(maxtrack + 1, 0); + vector curBarTime(maxtrack + 1, 0); -bool Tool_mens2kern::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + for (int i=0; i& curNum, + vector& curDen, vector& curBeat, + vector& curBarTime) { + int fieldCount = line.getFieldCount(); -////////////////////////////// -// -// Tool_mens2kern::processFile -- -// + if (!line.hasSpines()) { + return; + } -void Tool_mens2kern::processFile(HumdrumFile& infile) { - vector melody; - int scount = infile.getStrandCount(); - for (int i=0; iisDataType("**mens")) { - continue; - } - HTp sstop = infile.getStrandEnd(i); - HTp current = sstart; - while (current && (current != sstop)) { - if (current->isNull()) { - // ignore null data tokens - current = current->getNextToken(); + HumRegex hre; + if (line.isBarline()) { + for (int i=0; iisKern()) { continue; } - melody.push_back(current); - current = current->getNextToken(); + if (hre.search(token, "-")) { + // invisible barline: ignore + continue; + } + int track = token->getTrack(); + HumNum curTime = token->getDurationFromStart(); + curBarTime.at(track) = curTime; } - processMelody(melody); - melody.clear(); + return; } - infile.createLinesFromTokens(); -} - + if (line.isInterpretation()) { + // check for time signatures + for (int i=0; iisKern()) { + continue; + } + if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) { + int top = hre.getMatchInt(1); + int bot = hre.getMatchInt(2); + int track = token->getTrack(); + curNum.at(track) = top; + curDen.at(track) = bot; + curBeat.at(track) = 0; + } else if (hre.search(token, "^\\*beat:\\s*([\\d.%]+)\\s*$")) { + int track = token->getTrack(); + string recip = hre.getMatch(1); + curBeat.at(track) = Convert::recipToDuration(recip); + } + } + return; + } + if (line.isData()) { + // check for time signatures + for (int i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + if ((!m_restQ) && token->isRest()) { + continue; + } + if (!token->isNoteAttack() && !(m_restQ && token->isRest())) { + continue; + } + int pickup = token->getValueInt("auto", "pickup"); + int track = token->getTrack(); + stringstream value; + value.str(""); + value << curNum.at(track); + token->setValue("auto", "numerator", value.str()); + value.str(""); + value << curDen.at(track); + token->setValue("auto", "denominator", value.str()); + HumNum curTime = token->getDurationFromStart(); + HumNum q; + if (pickup) { + HumNum meterDur = curNum.at(track); + meterDur /= curDen.at(track); + meterDur *= 4; + HumNum nbt = getHumNum(token, "nextBarTime"); + q = meterDur - nbt; + } else { + q = curTime - curBarTime.at(track); + } + value.str(""); + value << q; + token->setValue("auto", "q", value.str()); + bool compound = false; + int multiple = curNum.at(track).getNumerator() / 3; + int remainder = curNum.at(track).getNumerator() % 3; + int bottom = curDen.at(track).getNumerator(); + if ((curBeat.at(track) == 0) && (bottom >= 8) && (multiple > 1) && (remainder == 0)) { + compound = true; + } -////////////////////////////// -// -// Tool_mens2kern::processMelody -- -// + HumNum qq = q; + if (m_quarterQ) { + // do nothing (prior calculations are done in quarter notes) + } else if (m_halfQ) { + qq /= 2; + } else if (m_wholeQ) { + qq /= 4; + } else if (m_eighthQ) { + qq *= 2; + } else if (m_sixteenthQ) { + qq *= 4; + } else { + // convert quarter note metric positions into beat positions + if (compound) { + qq *= curDen.at(track); + qq /= 4; + qq /= 3; + } else if (curBeat.at(track) > 0) { + qq /= curBeat.at(track); + } else { + qq *= curDen.at(track); + qq /= 4; + } + } -void Tool_mens2kern::processMelody(vector& melody) { - int maximodus = 0; - int modus = 0; - int tempus = 0; - int prolatio = 0; - int semibrevis_def = 0; - int brevis_def = 0; - int longa_def = 0; - int maxima_def = 0; - string regexopts; - HumRegex hre; - string rhythm; - bool imperfecta; - bool perfecta; - bool altera; + value.str(""); + value << qq; + token->setValue("auto", "zeroBeat", value.str()); + token->setValue("auto", "hasData", 1); - for (int i=0; i<(int)melody.size(); i++) { - if (*melody[i] == "**mens") { - // convert spine to **kern data: - melody[i]->setText("**kern"); } + return; + } +} - if (melody[i]->isMensuration()) { - getMensuralInfo(melody[i], maximodus, modus, tempus, prolatio); - // Default value of notes from maxima to semibrevis in minims: - semibrevis_def = prolatio; - brevis_def = tempus * semibrevis_def; - longa_def = modus * brevis_def; - maxima_def = maximodus * longa_def; - if (m_debugQ) { - cerr << "LEVELS X_def = " << maxima_def - << " | L_def = " << longa_def - << " | S_def = " << brevis_def - << " | s_def = " << semibrevis_def << endl; - } - } - if (!melody[i]->isData()) { - continue; - } - string text = melody[i]->getText(); - imperfecta = hre.search(text, "i") ? true : false; - perfecta = hre.search(text, "p") ? true : false; - altera = hre.search(text, "\\+") ? true : false; - if (hre.search(text, "([XLSsMmUu])")) { - rhythm = hre.getMatch(1); - } else { - cerr << "Error: token " << melody[i] << " has no rhythm" << endl; - cerr << " ON LINE: " << melody[i]->getLineNumber() << endl; - continue; - } - string kernRhythm = mens2kernRhythm(rhythm, altera, perfecta, imperfecta, maxima_def, longa_def, brevis_def, semibrevis_def); +///////////////////////////////// +// +// Tool_gridtest::Tool_metlev -- Set the recognized options for the tool. +// - hre.replaceDestructive(text, kernRhythm, rhythm); - // Remove any dot of division/augmentation - hre.replaceDestructive(text, "", ":"); - // remove perfection/imperfection/alteration markers - hre.replaceDestructive(text, "", "[pi\\+]"); - if (text.empty()) { - text = "."; - } - melody[i]->setText(text); - } +Tool_metlev::Tool_metlev(void) { + define("a|append=b", "append data analysis to input file"); + define("p|prepend=b", "prepend data analysis to input file"); + define("c|composite=b", "generate composite rhythm"); + define("i|integer=b", "quantize metric levels to int values"); + define("x|attacks-only=b", "only mark lines with note attacks"); + define("G|no-grace-notes=b", "do not mark grace note lines"); + define("k|kern-spine=i:1", "analyze only given kern spine"); + define("e|exinterp=s:blev", "exclusive interpretation type for output"); } -////////////////////////////// + +/////////////////////////////// // -// Tool_mens2kern::getMensuralInfo -- +// Tool_metlev::run -- Primary interfaces to the tool. // -void Tool_mens2kern::getMensuralInfo(HTp token, int& maximodus, int& modus, - int& tempus, int& prolatio) { - HumRegex hre; - if (!hre.search(token, "^\\*met\\(.*?\\)_(\\d+)")) { - // need to interpret symbols without underscores. - if (token->getText() == "*met(C)") { - maximodus = 2; - modus = 2; - tempus = 2; - prolatio = 2; - } else if (token->getText() == "*met(O)") { - maximodus = 2; - modus = 2; - tempus = 3; - prolatio = 2; - } else if (token->getText() == "*met(C.)") { - maximodus = 2; - modus = 2; - tempus = 2; - prolatio = 3; - } else if (token->getText() == "*met(O.)") { - maximodus = 2; - modus = 2; - tempus = 3; - prolatio = 3; - } else if (token->getText() == "*met(C|)") { - maximodus = 2; - modus = 2; - tempus = 2; - prolatio = 2; - } else if (token->getText() == "*met(O|)") { - maximodus = 2; - modus = 2; - tempus = 3; - prolatio = 2; - } else if (token->getText() == "*met(C.|)") { - maximodus = 2; - modus = 2; - tempus = 2; - prolatio = 3; - } else if (token->getText() == "*met(O.|)") { - maximodus = 2; - modus = 2; - tempus = 3; - prolatio = 3; - } else if (token->getText() == "*met(C2)") { - maximodus = 2; - modus = 2; - tempus = 2; - prolatio = 2; - } else if (token->getText() == "*met(C3)") { - maximodus = 2; - modus = 2; - tempus = 3; - prolatio = 2; - } else if (token->getText() == "*met(O2)") { - maximodus = 2; - modus = 3; - tempus = 2; - prolatio = 2; - } else if (token->getText() == "*met(O3)") { - maximodus = 3; - modus = 3; - tempus = 3; - prolatio = 2; - } else if (token->getText() == "*met(C3/2)") { - maximodus = 2; - modus = 2; - tempus = 2; - prolatio = 3; - } else if (token->getText() == "*met(C|3/2)") { - maximodus = 2; - modus = 2; - tempus = 3; - prolatio = 2; - } - } else { - string levels = hre.getMatch(1); - if (levels.size() >= 1) { - maximodus = levels[0] - '0'; - } - if (levels.size() >= 2) { - modus = levels[1] - '0'; - } - if (levels.size() >= 3) { - tempus = levels[2] - '0'; - } - if (levels.size() >= 4) { - prolatio = levels[3] - '0'; - } - } - - if (m_debugQ) { - cerr << "MENSURAL INFO: maximodus = " << maximodus - << " | modus = " << modus - << " | tempus = " << tempus - << " | prolatio = " << prolatio << endl; +bool Tool_metlev::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i beatlev(lineCount, NAN); + int track = 0; + if (m_kernspines.size() > 0) { + track = m_kernspines[0]->getTrack(); + } else { + m_error_text << "No **kern spines in input file" << endl; + return false; } - else if (rhythm == "M") { - if (perfecta) { val_note = 1.5 * minima_def; } - else if (altera) { val_note = 2 * minima_def; } - else { val_note = minima_def; } + infile.getMetricLevels(beatlev, track, NAN); + + for (int i=0; i= 0) && (kspine < (int)m_kernspines.size())) { + vector > results; + fillVoiceResults(results, infile, beatlev); + if (kspine == (int)m_kernspines.size() - 1) { + infile.appendDataSpine(results.back(), "nan", exinterp); + } else { + int track = m_kernspines[kspine+1]->getTrack(); + infile.insertDataSpineBefore(track, results[kspine], + "nan", exinterp); + } + infile.createLinesFromTokens(); + return true; + } + } else if (getBoolean("append")) { + infile.appendDataSpine(beatlev, "nan", exinterp); + infile.createLinesFromTokens(); + return true; + } else if (getBoolean("prepend")) { + infile.prependDataSpine(beatlev, "nan", exinterp); + infile.createLinesFromTokens(); + return true; + } else if (getBoolean("composite")) { + infile.prependDataSpine(beatlev, "nan", exinterp); + infile.printFieldIndex(0, m_humdrum_text); + infile.clear(); + infile.readString(m_humdrum_text.str()); + } else { + vector > results; + fillVoiceResults(results, infile, beatlev); + infile.appendDataSpine(results.back(), "nan", exinterp); + for (int i = (int)results.size()-1; i>0; i--) { + int track = m_kernspines[i]->getTrack(); + infile.insertDataSpineBefore(track, results[i-1], "nan", exinterp); + } + infile.createLinesFromTokens(); + return true; } - else if (rhythm == "U") { - if (perfecta) { val_note = 1.5 * fusa_def; } - else { val_note = fusa_def; } + + return false; +} + + + +////////////////////////////// +// +// Tool_metlev::fillVoiceResults -- Split the metric level analysis into values +// for each voice. +// + +void Tool_metlev::fillVoiceResults(vector >& results, + HumdrumFile& infile, vector& beatlev) { + + results.resize(m_kernspines.size()); + for (int i=0; i<(int)results.size(); i++) { + results[i].resize(beatlev.size()); + fill(results[i].begin(), results[i].end(), NAN); } - else if (rhythm == "u") { - if (perfecta) { val_note = 1.5 * semifusa_def; } - else { val_note = semifusa_def; } + int track; + vector rtracks(infile.getTrackCount() + 1, -1); + for (int i=0; i<(int)m_kernspines.size(); i++) { + int track = m_kernspines[i]->getTrack(); + rtracks[track] = i; } - else { cerr << "UNKNOWN RHYTHM: " << rhythm << endl; return ""; } - switch ((int)(val_note * 10000)) { - case 1250: return "16"; break; // sixteenth note - case 1875: return "16."; break; // dotted sixteenth note - case 2500: return "8"; break; // eighth note - case 3750: return "8."; break; // dotted eighth note - case 5000: return "4"; break; // quarter note - case 7500: return "4."; break; // dotted quarter note - case 10000: return "2"; break; // half note - case 15000: return "2."; break; // dotted half note - case 20000: return "1"; break; // whole note - case 30000: return "1."; break; // dotted whole note - case 40000: return "0"; break; // breve note - case 60000: return "0."; break; // dotted breve note - case 90000: return "2%9"; break; // or ["0.", "1."]; - case 80000: return "00"; break; // long note - case 120000: return "00."; break; // dotted long note - case 180000: return "1%9"; break; // or ["00.", "0."]; - case 270000: return "2%27"; break; // or ["0.", "1.", "0.", "1.", "0.", "1."]; - case 160000: return "000"; break; // maxima note - case 240000: return "000."; break; // dotted maxima note - case 360000: return "1%18"; break; // or ["000.", "00."]; - case 540000: return "1%27"; break; // or ["00.", "0.", "00.", "0.", "00.", "0."]; - case 810000: return "2%81"; break; // or ["00.", "0.", "00.", "0.", "00.", "0.", "0.", "1.", "0.", "1.", "0.", "1."]; - default: - cerr << "Error: unknown val_note: " << val_note << endl; + bool attacksQ = getBoolean("attacks-only"); + vector nonnullcount(m_kernspines.size(), 0); + vector attackcount(m_kernspines.size(), 0); + HTp token; + int voice; + int i, j; + for (i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + track = token->getTrack(); + voice = rtracks[track]; + nonnullcount[voice]++; + if (token->isNoteAttack()) { + attackcount[voice]++; + } + } + for (int v=0; v<(int)m_kernspines.size(); v++) { + if (attacksQ) { + if (attackcount[v]) { + results[v][i] = beatlev[i]; + attackcount[v] = 0; + } + } else { + if (nonnullcount[v]) { + results[v][i] = beatlev[i]; + } + nonnullcount[v] = 0; + } + } } - - return ""; } @@ -98244,37 +101924,31 @@ string Tool_mens2kern::mens2kernRhythm(const string& rhythm, bool altera, bool p ///////////////////////////////// // -// Tool_meter::Tool_meter -- Set the recognized options for the tool. +// Tool_modori::Tool_modori -- Set the recognized options for the tool. // -Tool_meter::Tool_meter(void) { - define("c|comma=b", "display decimal points as commas"); - define("d|denominator=b", "display denominator spine"); - define("e|eighth=b", "metric positions in eighth notes rather than beats"); - define("f|float=b", "floating-point beat values instead of rational numbers"); - define("h|half=b", "metric positions in half notes rather than beats"); - define("j|join=b", "join time signature information and metric positions into a single token"); - define("n|numerator=b", "display numerator spine"); - define("q|quarter=b", "metric positions in quarter notes rather than beats"); - define("r|rest=b", "add meteric positions of rests"); - define("s|sixteenth=b", "metric positions in sixteenth notes rather than beats"); - define("t|time-signature|tsig|m|meter=b", "display active time signature for each note"); - define("w|whole=b", "metric positions in whole notes rather than beats"); - define("z|zero=b", "start of measure is beat 0 rather than beat 1"); - - define("B|no-beat=b", "Do not display metric positions (beats)"); - define("D|digits=i:0", "number of digits after decimal point"); - define("L|no-label=b", "do not add labels to analysis spines"); +Tool_modori::Tool_modori(void) { + define("m|modern=b", "prepare score for modern style"); + define("o|original=b", "prepare score for original style"); + define("d|info=b", "display key/clef/mensuration information"); + define("I|no-instrument-name|no-instrument-names=b", "do not change part labels"); + define("A|no-instrument-abbreviation|no-instrument-abbreviations=b", "do not change part label abbreviations"); + define("C|no-clef|no-clefs=b", "do not change clefs"); + define("K|no-key|no-keys=b", "do not change key signatures"); + define("L|no-lyrics=b", "do not change **text exclusive interpretations"); + define("M|no-mensuration|no-mensurations=b", "do not change mensurations"); + define("R|no-references=b", "do not change reference records keys"); + define("T|no-text=b", "do not change !LO:(TX|DY) layout parameters"); } ///////////////////////////////// // -// Tool_meter::run -- Do the main work of the tool. +// Tool_modori::run -- Do the main work of the tool. // -bool Tool_meter::run(HumdrumFileSet& infiles) { +bool Tool_modori::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i 15) { - m_digits = 15; - } - - if (m_joinQ && !(m_tsigQ || m_numeratorQ || m_denominatorQ)) { - m_tsigQ = true; - } - if (m_joinQ) { - m_nobeatQ = false; - } - if (m_joinQ && m_numeratorQ && m_denominatorQ) { - m_tsigQ = true; - } - - if (m_tsigQ) { - m_numeratorQ = true; - m_denominatorQ = true; - } - - // Only one fix-width metric position allowed, prioritize - // largest given duration: - if (m_wholeQ) { - m_halfQ = false; - m_quarterQ = false; - m_eighthQ = false; - m_sixteenthQ = false; - } else if (m_halfQ) { - m_wholeQ = false; - m_quarterQ = false; - m_eighthQ = false; - m_sixteenthQ = false; - } else if (m_quarterQ) { - m_wholeQ = false; - m_halfQ = false; - m_eighthQ = false; - m_sixteenthQ = false; - } else if (m_eighthQ) { - m_wholeQ = false; - m_halfQ = false; - m_quarterQ = false; - m_sixteenthQ = false; - } else if (m_sixteenthQ) { - m_wholeQ = false; - m_halfQ = false; - m_quarterQ = false; - m_eighthQ = false; +void Tool_modori::initialize(void) { + m_infoQ = getBoolean("info"); + m_modernQ = getBoolean("modern"); + m_originalQ = getBoolean("original"); + if (m_modernQ && m_originalQ) { + // if both options are used, ignore -m: + m_modernQ = false; } - + m_nokeyQ = getBoolean("no-key"); + m_noclefQ = getBoolean("no-clef"); + m_nolotextQ = getBoolean("no-text"); + m_nolyricsQ = getBoolean("no-lyrics"); + m_norefsQ = getBoolean("no-references"); + m_nomensurationQ = getBoolean("no-mensuration"); + m_nolabelsQ = getBoolean("no-instrument-names"); + m_nolabelAbbrsQ = getBoolean("no-instrument-abbreviations"); } ////////////////////////////// // -// Tool_meter::processFile -- -// - -void Tool_meter::processFile(HumdrumFile& infile) { - analyzePickupMeasures(infile); - getMeterData(infile); - printMeterData(infile); -} - - -////////////////////////////// -// -// Tool_meter::analyzePickupMeasures -- +// Tool_modori::processFile -- // -void Tool_meter::analyzePickupMeasures(HumdrumFile& infile) { - vector sstarts; - infile.getKernSpineStartList(sstarts); - for (int i=0; i<(int)sstarts.size(); i++) { - analyzePickupMeasures(sstarts[i]); - } -} - +void Tool_modori::processFile(HumdrumFile& infile) { + m_keys.clear(); + m_labels.clear(); + m_labelAbbrs.clear(); + m_clefs.clear(); + m_mensurations.clear(); + m_references.clear(); + m_lyrics.clear(); + m_lotext.clear(); -void Tool_meter::analyzePickupMeasures(HTp sstart) { - // First dimension are visible barlines. - // Second dimension are time signature(s) within the barlines. - vector> barandtime; - barandtime.reserve(1000); - barandtime.resize(1); - barandtime[0].push_back(sstart); - HTp current = sstart->getNextToken(); - while (current) { - if (current->isTimeSignature()) { - barandtime.back().push_back(current); - } else if (current->isBarline()) { - if (current->find("-") != std::string::npos) { - current = current->getNextToken(); - continue; - } - barandtime.resize(barandtime.size() + 1); - barandtime.back().push_back(current); - } else if (*current == "*-") { - barandtime.resize(barandtime.size() + 1); - barandtime.back().push_back(current); - break; - } - current = current->getNextToken(); - } + int maxtrack = infile.getMaxTrack(); + m_keys.resize(maxtrack+1); + m_labels.resize(maxtrack+1); + m_labelAbbrs.resize(maxtrack+1); + m_clefs.resize(maxtrack+1); + m_mensurations.resize(maxtrack+1); + m_references.reserve(1000); + m_lyrics.reserve(1000); + m_lotext.reserve(1000); - // Extract the actual duration of measures: - vector bardur(barandtime.size(), 0); - for (int i=0; i<(int)barandtime.size() - 1; i++) { - HumNum starttime = barandtime[i][0]->getDurationFromStart(); - HumNum endtime = barandtime.at(i+1)[0]->getDurationFromStart(); - HumNum duration = endtime - starttime; - bardur.at(i) = duration; - } + HumRegex hre; + int exinterpLine = -1; - // Extract the expected duration of measures: - vector tsigdur(barandtime.size(), 0); - int firstmeasure = -1; - HumNum active = 0; - for (int i=0; i<(int)barandtime.size() - 1; i++) { - if (firstmeasure < 0) { - if (bardur.at(i) > 0) { - firstmeasure = i; + for (int i=0; i pickup(barandtime.size(), false); - for (int i=0; i<(int)barandtime.size() - 1; i++) { - if (tsigdur.at(i) == bardur.at(i)) { - // actual and expected are the same + if (!infile[i].isInterpretation()) { continue; } - if (tsigdur.at(i) == tsigdur.at(i+1)) { - if (bardur.at(i) + bardur.at(i+1) == tsigdur.at(i)) { - pickup.at(i+1) = true; - i++; + HumNum timeval = infile[i].getDurationFromStart(); + for (int j=0; jisExclusiveInterpretation()) { + exinterpLine = i; continue; } - } - } + if (!token->isKern()) { + continue; + } + if (*token == "*") { + continue; + } + int track = token->getTrack(); + if (token->isKeySignature()) { + m_keys[track][timeval].push_back(token); + } else if (token->isOriginalKeySignature()) { + m_keys[track][timeval].push_back(token); + } else if (token->isModernKeySignature()) { + m_keys[track][timeval].push_back(token); - // check for first-measure pickup - if (firstmeasure >= 0) { - if (bardur.at(firstmeasure) < tsigdur.at(firstmeasure)) { - pickup.at(firstmeasure) = true; - } - } + } else if (token->isInstrumentName()) { + m_labels[track][timeval].push_back(token); + } else if (token->isOriginalInstrumentName()) { + m_labels[track][timeval].push_back(token); + } else if (token->isModernInstrumentName()) { + m_labels[track][timeval].push_back(token); - if (m_debugQ) { - cerr << "============================" << endl; - for (int i=0; i<(int)barandtime.size(); i++) { - cerr << pickup.at(i); - cerr << "\t"; - cerr << bardur.at(i); - cerr << "\t"; - cerr << tsigdur.at(i); - cerr << "\t"; - for (int j=0; j<(int)barandtime[i].size(); j++) { - cerr << barandtime.at(i).at(j) << "\t"; + } else if (token->isInstrumentAbbreviation()) { + m_labelAbbrs[track][timeval].push_back(token); + } else if (token->isOriginalInstrumentAbbreviation()) { + m_labelAbbrs[track][timeval].push_back(token); + } else if (token->isModernInstrumentAbbreviation()) { + m_labelAbbrs[track][timeval].push_back(token); + + } else if (token->isClef()) { + m_clefs[track][timeval].push_back(token); + } else if (token->isOriginalClef()) { + m_clefs[track][timeval].push_back(token); + } else if (token->isModernClef()) { + m_clefs[track][timeval].push_back(token); + + } else if (token->isMensuration()) { + m_mensurations[track][timeval].push_back(token); + } else if (token->isOriginalMensuration()) { + m_mensurations[track][timeval].push_back(token); + } else if (token->isModernMensuration()) { + m_mensurations[track][timeval].push_back(token); } - cerr << endl; } - cerr << endl; } - // Markup pickup measure notes/rests - for (int i=0; i<(int)pickup.size() - 1; i++) { - if (!pickup[i]) { - continue; + if (exinterpLine >= 0) { + processExclusiveInterpretationLine(infile, exinterpLine); + } + + storeModOriReferenceRecords(infile); + + if (m_infoQ) { + if (m_modernQ || m_originalQ) { + m_humdrum_text << infile; } - markPickupContent(barandtime.at(i).at(0), barandtime.at(i+1).at(0)); + printInfo(); } - // Pickup/incomplete measures covering three or more barlines are not considered - // (these could be used with dashed barlines or similar). + if (!(m_modernQ || m_originalQ)) { + // nothing to do + return; + } + switchModernOriginal(infile); + printModoriOutput(infile); } - ////////////////////////////// // -// Tool_meter::markPickupContent -- +// Tool_modori::processExclusiveInterpretationLine -- // -void Tool_meter::markPickupContent(HTp stok, HTp etok) { - int endline = etok->getLineIndex(); - HTp current = stok; - while (current) { - int line = current->getLineIndex(); - if (line > endline) { - break; +void Tool_modori::processExclusiveInterpretationLine(HumdrumFile& infile, int line) { + vector staffish; + vector staff; + vector> nonstaff; + bool init = false; + bool changed = false; + + if (!infile[line].isExclusive()) { + return; + } + + for (int i=0; iisExclusiveInterpretation()) { + continue; } - if (current->isData()) { - HTp field = current; - int track = field->getTrack(); - while (field) { - int ttrack = field->getTrack(); - if (ttrack != track) { - break; - } - if (field->isNull()) { - field = field->getNextFieldToken(); - continue; - } - field->setValue("auto", "pickup", 1); - HumNum nbt = etok->getDurationFromStart() - field->getDurationFromStart(); - stringstream ntime; - ntime.str(""); - ntime << nbt.getNumerator() << "/" << nbt.getDenominator(); - field->setValue("auto", "nextBarTime", ntime.str()); - field = field->getNextFieldToken(); + if (token->isStaff()) { + staff.push_back(token); + nonstaff.resize(nonstaff.size() + 1); + init = 1; + } else { + if (init) { + nonstaff.back().push_back(token); } } - if (current == etok) { - break; + if (token->isStaff()) { + staffish.push_back(token); + } else if (*token == "**mod-kern") { + staffish.push_back(token); + } else if (*token == "**mod-mens") { + staffish.push_back(token); + } else if (*token == "**ori-kern") { + staffish.push_back(token); + } else if (*token == "**ori-mens") { + staffish.push_back(token); } - current = current->getNextToken(); } -} - - -////////////////////////////// -// -// Tool_meter::getTimeSigDuration -- -// - -HumNum Tool_meter::getTimeSigDuration(HTp tsig) { - HumNum output = 0; - HumRegex hre; - if (hre.search(tsig, "^\\*M(\\d+)/(\\d+%?\\d*)")) { - int top = hre.getMatchInt(1); - string bot = hre.getMatch(2); - HumNum botdur = Convert::recipToDuration(bot); - output = botdur * top; + for (int i=0; i<(int)staff.size(); i++) { + changed |= processStaffCompanionSpines(nonstaff[i]); + } + + changed |= processStaffSpines(staffish); + + if (changed) { + infile[line].createLineFromTokens(); } - return output; } ////////////////////////////// // -// Tool_meter::printMeterData -- +// Tool_modori::processStaffSpines -- // -void Tool_meter::printMeterData(HumdrumFile& infile) { - bool foundLabel = false; - bool foundData = false; - - for (int i=0; i& tokens) { - bool hasLabel = false; - if ((!m_nolabelQ) && (!foundLabel) && (!foundData)) { - if (searchForLabels(infile[i])) { - hasLabel = true; - foundLabel = true; - } + HumRegex hre; + bool changed = false; + for (int i=0; i<(int)tokens.size(); i++) { + if (hre.search(tokens[i], "^\\*\\*(ori|mod)-(.*)")) { + string newexinterp = "**" + hre.getMatch(2) + "-" + hre.getMatch(1); + tokens[i]->setText(newexinterp); + changed = true; + } else if (hre.search(tokens[i], "^\\*\\*(.*?)-(ori|mod)$")) { + string newexinterp = "**" + hre.getMatch(2) + "-" + hre.getMatch(1); + tokens[i]->setText(newexinterp); + changed = true; } - printHumdrumLine(infile[i], hasLabel); } + + return changed; } ////////////////////////////// // -// printLabelLine -- +// Tool_modori::processStaffCompanionSpines -- // -void Tool_meter::printLabelLine(HumdrumLine& line) { - bool forceInterpretation = true; - bool printLabels = true; - for (int i=0; iisKern()) { - i = printKernAndAnalysisSpine(line, i, printLabels, forceInterpretation); +bool Tool_modori::processStaffCompanionSpines(vector tokens) { + + vector mods; + vector oris; + vector other; + + for (int i=0; i<(int)tokens.size(); i++) { + if (tokens[i]->find("**mod-") != string::npos) { + mods.push_back(tokens[i]); + } else if (tokens[i]->find("**ori-") != string::npos) { + oris.push_back(tokens[i]); } else { - m_humdrum_text << "*"; - } - if (i < line.getFieldCount() - 1) { - m_humdrum_text << "\t"; + other.push_back(tokens[i]); } } - m_humdrum_text << "\n"; -} + bool gchanged = false; + if (mods.empty() && oris.empty()) { + // Nothing to do. + return false; + } -////////////////////////////// -// -// Tool_meter::printHumdrumLine -- -// + // mods and oris should not be mixed, so if there are no + // other spines, then also give up: + if (other.empty()) { + return false; + } -void Tool_meter::printHumdrumLine(HumdrumLine& line, bool printLabels) { - for (int i=0; iisKern()) { - i = printKernAndAnalysisSpine(line, i, printLabels); - } else { - m_humdrum_text << token; + if (m_modernQ) { + bool changed = false; + // Swap (**mod-XXX and **XXX) to (**XXX and **ori-XXX) + + for (int i=0; i<(int)other.size(); i++) { + if (other[i] == NULL) { + continue; + } + string target = "**mod-" + other[i]->substr(2); + for (int j=0; j<(int)mods.size(); j++) { + if (mods[j] == NULL) { + continue; + } + if (*mods[j] != target) { + continue; + } + mods[j]->setText(*other[i]); + mods[j] = NULL; + changed = true; + gchanged = true; + } + if (changed) { + string replacement = "**ori-" + other[i]->substr(2); + other[i]->setText(replacement); + other[i] = NULL; + } } - if (i < line.getFieldCount() - 1) { - m_humdrum_text << "\t"; + + } else if (m_originalQ) { + bool changed = false; + // Swap (**ori-XXX and **XXX) to (**XXX and **mod-XXX) + + for (int i=0; i<(int)other.size(); i++) { + if (other[i] == NULL) { + continue; + } + string target = "**ori-" + other[i]->substr(2); + for (int j=0; j<(int)oris.size(); j++) { + if (oris[j] == NULL) { + continue; + } + if (*oris[j] != target) { + continue; + } + oris[j]->setText(*other[i]); + oris[j] = NULL; + changed = true; + gchanged = true; + } + if (changed) { + string replacement = "**mod-" + other[i]->substr(2); + other[i]->setText(replacement); + other[i] = NULL; + } } } - m_humdrum_text << "\n"; + + return gchanged; } ////////////////////////////// // -// Tool_meter::searchForLabels -- +// Tool_modori::storeModOriReferenceRecors -- // -bool Tool_meter::searchForLabels(HumdrumLine& line) { - if (!line.isInterpretation()) { - return false; +void Tool_modori::storeModOriReferenceRecords(HumdrumFile& infile) { + m_references.clear(); + + vector refs = infile.getGlobalReferenceRecords(); + vector keys(refs.size()); + for (int i=0; i<(int)refs.size(); i++) { + string key = refs.at(i)->getReferenceKey(); + keys.at(i) = key; } + + vector modernIndex; + vector originalIndex; + HumRegex hre; - for (int i=0; i= 0) { + m_references.push_back(make_pair(refs[index]->token(0), refs[pairing]->token(0))); + } + } + } -////////////////////////////// -// -// Tool_meter::getHumNum -- -// - -HumNum Tool_meter::getHumNum(HTp token, const string& parameter) { - HumRegex hre; - HumNum output; - string value = token->getValue("auto", parameter); - if (hre.search(value, "(\\d+)/(\\d+)")) { - output = hre.getMatchInt(1); - output /= hre.getMatchInt(2); - } else if (hre.search(value, "(\\d+)")) { - output = hre.getMatchInt(1); + if (m_originalQ || m_infoQ) { + // Store *-ori reference records if there is a pairing: + int pairing = -1; + string target; + for (int i=0; i<(int)originalIndex.size(); i++) { + int index = originalIndex[i]; + pairing = getPairedReference(index, keys); + if (pairing >= 0) { + target = keys[index]; + m_references.push_back(make_pair(refs[index]->token(0), refs[pairing]->token(0))); + } + } } - return output; } ////////////////////////////// // -// Tool_meter::getHumNumString -- +// Tool_modori::getPairedReference -- // -string Tool_meter::getHumNumString(HumNum input) { - stringstream tem; - input.printTwoPart(tem); - return tem.str(); +int Tool_modori::getPairedReference(int index, vector& keys) { + string key = keys.at(index); + string tkey = key; + if (tkey.size() > 4) { + tkey.resize(tkey.size() - 4); + } else { + return -1; + } + + for (int i=0; i<(int)keys.size(); i++) { + int ii = index + i; + if (ii < (int)keys.size()) { + if (tkey == keys.at(ii)) { + return ii; + } + } + ii = index - i; + if (ii >= 0) { + if (tkey == keys.at(ii)) { + return ii; + } + } + } + return -1; } ////////////////////////////// // -// Tool_meter::printKernAndAnalysisSpine -- +// Tool_modori::switchModernOriginal -- // -int Tool_meter::printKernAndAnalysisSpine(HumdrumLine& line, int index, bool printLabels, bool forceInterpretation) { - HTp starttok = line.token(index); - int track = starttok->getTrack(); - int counter = 0; +void Tool_modori::switchModernOriginal(HumdrumFile& infile) { - string analysis = "."; - string numerator = "."; - string denominator = "."; - string meter = "."; - bool hasNote = false; - bool hasRest = false; + set changed; - for (int i=index; igetTrack(); - if (ttrack != track) { - break; - } - if (counter > 0) { - m_humdrum_text << "\t"; - } - counter++; - if (forceInterpretation) { - m_humdrum_text << "*"; - } else { - m_humdrum_text << token; + if (!m_nokeyQ) { + for (int t=1; t<(int)m_keys.size(); ++t) { + for (auto it = m_keys.at(t).begin(); it != m_keys.at(t).end(); ++it) { + if (it->second.size() != 2) { + continue; + } + bool status = swapKeyStyle(it->second.at(0), it->second.at(1)); + if (status) { + int line = it->second.at(0)->getLineIndex(); + changed.insert(line); + line = it->second.at(1)->getLineIndex(); + changed.insert(line); + } + } } + } - if (line.isData() && !forceInterpretation) { - if (token->isNull()) { - // analysis = "."; - } else if (token->isRest() && !m_restQ) { - // analysis = "."; - } else if ((!token->isNoteAttack()) && !(m_restQ && token->isRest())) { - // analysis = "."; - } else if ((analysis == ".") && (token->getValueBool("auto", "hasData"))) { - string data = token->getValue("auto", "zeroBeat"); - if (m_restQ) { - if (token->isRest()) { - hasRest = true; - } else { - hasNote = true; - } + if (!m_nolabelsQ) { + for (int t=1; t<(int)m_labels.size(); ++t) { + for (auto it = m_labels.at(t).begin(); it != m_labels.at(t).end(); ++it) { + if (it->second.size() != 2) { + continue; } - HumNum value; - HumNum nvalue; - HumNum dvalue; - if (!data.empty()) { - value = getHumNum(token, "zeroBeat"); - if (m_numeratorQ) { - nvalue = getHumNum(token, "numerator"); - numerator = getHumNumString(nvalue); - } - if (m_denominatorQ) { - dvalue = getHumNum(token, "denominator"); - denominator = getHumNumString(dvalue); - } - if (m_tsigQ) { - meter = numerator; - meter += "/"; - meter += denominator; - } + bool status = swapInstrumentNameStyle(it->second.at(0), it->second.at(1)); + if (status) { + int line = it->second.at(0)->getLineIndex(); + changed.insert(line); + line = it->second.at(1)->getLineIndex(); + changed.insert(line); } - if (!m_zeroQ) { - value += 1; + } + } + } + + if (!m_nolabelAbbrsQ) { + for (int t=1; t<(int)m_labelAbbrs.size(); ++t) { + for (auto it = m_labelAbbrs.at(t).begin(); it != m_labelAbbrs.at(t).end(); ++it) { + if (it->second.size() != 2) { + continue; } - if (m_floatQ) { - stringstream tem; - if (m_digits) { - tem << std::setprecision(m_digits + 1) << value.getFloat(); - } else { - tem << value.getFloat(); - } - analysis = tem.str(); - if (m_commaQ) { - HumRegex hre; - hre.replaceDestructive(analysis, ",", "\\."); - } - } else { - analysis = getHumNumString(value); + bool status = swapInstrumentAbbreviationStyle(it->second.at(0), it->second.at(1)); + if (status) { + int line = it->second.at(0)->getLineIndex(); + changed.insert(line); + line = it->second.at(1)->getLineIndex(); + changed.insert(line); } } - } else if (line.isInterpretation() || forceInterpretation) { - if (token->compare(0, 2, "**") == 0) { - analysis = "**cdata-beat"; - if (m_tsigQ) { - meter = "**cdata-tsig"; + } + } + + if (!m_nolyricsQ) { + bool adjust = false; + int line = -1; + for (int i=0; i<(int)m_lyrics.size(); i++) { + HTp token = m_lyrics[i]; + line = token->getLineIndex(); + if (m_modernQ) { + if (*token == "**text") { + adjust = true; + token->setText("**ori-text"); + } else if (*token == "**mod-text") { + adjust = true; + token->setText("**text"); } - if (m_numeratorQ) { - numerator = "**cdata-num"; + } else { + if (*token == "**text") { + adjust = true; + token->setText("**mod-text"); + } else if (*token == "**ori-text") { + adjust = true; + token->setText("**text"); } - if (m_denominatorQ) { - denominator = "**cdata-den"; + } + } + if (adjust && (line >= 0)) { + infile[line].createLineFromTokens(); + } + } + + if (!m_nolotextQ) { + HumRegex hre; + for (int i=0; i<(int)m_lotext.size(); i++) { + HTp token = m_lotext[i]; + int line = token->getLineIndex(); + if (hre.search(token, "^!!?LO:(TX|DY).*:mod=")) { + string text = *token; + hre.replaceDestructive(text, ":ori=", ":t="); + hre.replaceDestructive(text, ":t=", ":mod="); + token->setText(text); + changed.insert(line); + } else if (hre.search(token, "^!!?LO:(TX|DY).*:ori=")) { + string text = *token; + hre.replaceDestructive(text, ":mod=", ":t="); + hre.replaceDestructive(text, ":t=", ":ori="); + token->setText(text); + changed.insert(line); + } + } + } + + if (!m_norefsQ) { + HumRegex hre; + for (int i=0; i<(int)m_references.size(); i++) { + HTp first = m_references[i].first; + HTp second = m_references[i].second; + + if (m_modernQ) { + if (hre.search(first, "^!!![^:]*?-mod:")) { + string text = *first; + hre.replaceDestructive(text, ":", "-...:"); + first->setText(text); + infile[first->getLineIndex()].createLineFromTokens(); + + text = *second; + hre.replaceDestructive(text, "-ori:", ":"); + second->setText(text); + infile[second->getLineIndex()].createLineFromTokens(); } - } else if (*token == "*-") { - analysis = "*-"; - numerator = "*-"; - denominator = "*-"; - meter = "*-"; - } else if (token->isTimeSignature()) { - analysis = *token; - } else { - analysis = "*"; - numerator = "*"; - denominator = "*"; - meter = "*"; - if (printLabels) { - if (m_quarterQ) { - analysis = "*vi:4ths:"; - } else if (m_eighthQ) { - analysis = "*vi:8ths:"; - } else if (m_halfQ) { - analysis = "*vi:half:"; - } else if (m_wholeQ) { - analysis = "*vi:whole:"; - } else if (m_sixteenthQ) { - analysis = "*vi:16ths:"; - } else { - analysis = "*vi:beat:"; + } else if (m_originalQ) { + if (hre.search(first, "^!!![^:]*?-ori:")) { + string text = *first; + hre.replaceDestructive(text, ":", "-...:"); + first->setText(text); + infile[first->getLineIndex()].createLineFromTokens(); + + text = *second; + hre.replaceDestructive(text, "-mod:", ":"); + second->setText(text); + infile[second->getLineIndex()].createLineFromTokens(); + } + } + + } + } + + // Mensurations are only used for "original" display. It is possible + // to use a modern metric signature (common time or cut time) but these + // are not currently allowed. Only one *met at a given time position + // is allowed. + + if (!m_nomensurationQ) { + for (int t=1; t<(int)m_mensurations.size(); ++t) { + for (auto it = m_mensurations.at(t).begin(); it != m_mensurations.at(t).end(); ++it) { + if (it->second.size() == 1) { + // swap omet to met, or met to omet: + bool status = flipMensurationStyle(it->second.at(0)); + if (status) { + int line = it->second.at(0)->getLineIndex(); + changed.insert(line); } - numerator = "*vi:top:"; - denominator = "*vi:bot:"; - meter = "*vi:tsig:"; - if (m_joinQ) { - numerator = ""; - denominator = ""; - meter = ""; + } else if (it->second.size() == 2) { + // swap omet/met or mmet/met: + bool status = swapMensurationStyle(it->second.at(0), it->second.at(1)); + if (status) { + int line = it->second.at(0)->getLineIndex(); + changed.insert(line); + line = it->second.at(1)->getLineIndex(); + changed.insert(line); } } } - } else if (line.isBarline()) { - analysis = *token; - numerator = *token; - denominator = *token; - meter = *token; - } else if (line.isCommentLocal()) { - analysis = "!"; - numerator = "!"; - denominator = "!"; - meter = "!"; - } else { - cerr << "STRANGE LINE: " << line << endl; } } - if (m_joinQ) { - if (line.isData() && !forceInterpretation) { - if (m_tsigQ) { - m_humdrum_text << "\t" << meter; - } else { - if (m_numeratorQ) { - m_humdrum_text << "\t" << numerator; + if (!m_noclefQ) { + for (int t=1; t<(int)m_clefs.size(); ++t) { + for (auto it = m_clefs.at(t).begin(); it != m_clefs.at(t).end(); ++it) { + if (it->second.size() != 2) { + continue; } - if (m_denominatorQ) { - m_humdrum_text << "\t" << denominator; + bool status = swapClefStyle(it->second.at(0), it->second.at(1)); + if (status) { + int line = it->second.at(0)->getLineIndex(); + changed.insert(line); + line = it->second.at(1)->getLineIndex(); + changed.insert(line); } } } - if (!m_nobeatQ) { - if (line.isData() && !forceInterpretation) { - m_humdrum_text << ":"; - } else { - m_humdrum_text << "\t"; - } - m_humdrum_text << analysis; - if (line.isData() && hasRest && !hasNote) { - m_humdrum_text << "r"; + } + + for (auto it = changed.begin(); it != changed.end(); ++it) { + int line = *it; + infile[line].createLineFromTokens(); + } + + updateLoMo(infile); +} + + +////////////////////////////// +// +// Tool_modori::printModoriOutput -- +// + +void Tool_modori::printModoriOutput(HumdrumFile& infile) { + string state; + if (m_modernQ) { + + // convert to modern + for (int i=0; i curNum(maxtrack + 1, 0); - vector curDen(maxtrack + 1, 0); - vector curBeat(maxtrack + 1, 0); - vector curBarTime(maxtrack + 1, 0); - - for (int i=0; i& curNum, - vector& curDen, vector& curBeat, - vector& curBarTime) { - - int fieldCount = line.getFieldCount(); - - if (!line.hasSpines()) { - return; - } - +void Tool_modori::processLoMo(HTp lomo) { HumRegex hre; - if (line.isBarline()) { - for (int i=0; iisKern()) { - continue; - } - if (hre.search(token, "-")) { - // invisible barline: ignore - continue; - } - int track = token->getTrack(); - HumNum curTime = token->getDurationFromStart(); - curBarTime.at(track) = curTime; - } - return; - } - if (line.isInterpretation()) { - // check for time signatures - for (int i=0; iisKern()) { - continue; + if (m_modernQ) { + string text = lomo->getText(); + string modtext; + string oritext; + string base; + string rest; + if (hre.search(text, "(.*):mod=([^:]*)(.*)")) { + base = hre.getMatch(1); + modtext = hre.getMatch(2); + rest = hre.getMatch(3); + hre.replaceDestructive(modtext, ":", ":", "g"); + HTp current = lomo->getNextToken(); + // null parameter allows next following null token + // to be swapped out + bool nullQ = hre.search(text, ":null:"); + if (!nullQ) { + while (current) { + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + break; + } } - if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) { - int top = hre.getMatchInt(1); - int bot = hre.getMatchInt(2); - int track = token->getTrack(); - curNum.at(track) = top; - curDen.at(track) = bot; - curBeat.at(track) = 0; - } else if (hre.search(token, "^\\*beat:\\s*([\\d.%]+)\\s*$")) { - int track = token->getTrack(); - string recip = hre.getMatch(1); - curBeat.at(track) = Convert::recipToDuration(recip); + if (current) { + string oritext = current->getText(); + hre.replaceDestructive(oritext, ":", ":", "g"); + current->setText(modtext); + string newtext = base; + newtext += ":ori="; + newtext += oritext; + newtext += rest; + lomo->setText(newtext); + lomo->getLine()->createLineFromTokens(); + current->getLine()->createLineFromTokens(); } } - return; - } - - if (line.isData()) { - // check for time signatures - for (int i=0; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - if ((!m_restQ) && token->isRest()) { - continue; - } - if (!token->isNoteAttack() && !(m_restQ && token->isRest())) { - continue; - } - int pickup = token->getValueInt("auto", "pickup"); - int track = token->getTrack(); - stringstream value; - value.str(""); - value << curNum.at(track); - token->setValue("auto", "numerator", value.str()); - value.str(""); - value << curDen.at(track); - token->setValue("auto", "denominator", value.str()); - HumNum curTime = token->getDurationFromStart(); - HumNum q; - if (pickup) { - HumNum meterDur = curNum.at(track); - meterDur /= curDen.at(track); - meterDur *= 4; - HumNum nbt = getHumNum(token, "nextBarTime"); - q = meterDur - nbt; - } else { - q = curTime - curBarTime.at(track); - } - value.str(""); - value << q; - token->setValue("auto", "q", value.str()); - bool compound = false; - int multiple = curNum.at(track).getNumerator() / 3; - int remainder = curNum.at(track).getNumerator() % 3; - int bottom = curDen.at(track).getNumerator(); - if ((curBeat.at(track) == 0) && (bottom >= 8) && (multiple > 1) && (remainder == 0)) { - compound = true; - } - HumNum qq = q; - if (m_quarterQ) { - // do nothing (prior calculations are done in quarter notes) - } else if (m_halfQ) { - qq /= 2; - } else if (m_wholeQ) { - qq /= 4; - } else if (m_eighthQ) { - qq *= 2; - } else if (m_sixteenthQ) { - qq *= 4; - } else { - // convert quarter note metric positions into beat positions - if (compound) { - qq *= curDen.at(track); - qq /= 4; - qq /= 3; - } else if (curBeat.at(track) > 0) { - qq /= curBeat.at(track); - } else { - qq *= curDen.at(track); - qq /= 4; + } else if (m_originalQ) { + string text = lomo->getText(); + string modtext; + string oritext; + string base; + string rest; + if (hre.search(text, "(.*):ori=([^:]*)(.*)")) { + base = hre.getMatch(1); + oritext = hre.getMatch(2); + rest = hre.getMatch(3); + hre.replaceDestructive(oritext, ":", ":", "g"); + HTp current = lomo->getNextToken(); + // null parameter allows next following null token + // to be swapped out + bool nullQ = hre.search(text, ":null:"); + if (nullQ) { + while (current) { + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + break; } } - - value.str(""); - value << qq; - token->setValue("auto", "zeroBeat", value.str()); - token->setValue("auto", "hasData", 1); - + if (current) { + string modtext = current->getText(); + hre.replaceDestructive(modtext, ":", ":", "g"); + current->setText(oritext); + string newtext = base; + newtext += ":mod="; + newtext += modtext; + newtext += rest; + lomo->setText(newtext); + lomo->getLine()->createLineFromTokens(); + current->getLine()->createLineFromTokens(); + } } - return; } } - -///////////////////////////////// -// -// Tool_gridtest::Tool_metlev -- Set the recognized options for the tool. -// - -Tool_metlev::Tool_metlev(void) { - define("a|append=b", "append data analysis to input file"); - define("p|prepend=b", "prepend data analysis to input file"); - define("c|composite=b", "generate composite rhythm"); - define("i|integer=b", "quantize metric levels to int values"); - define("x|attacks-only=b", "only mark lines with note attacks"); - define("G|no-grace-notes=b", "do not mark grace note lines"); - define("k|kern-spine=i:1", "analyze only given kern spine"); - define("e|exinterp=s:blev", "exclusive interpretation type for output"); -} - - - -/////////////////////////////// +////////////////////////////// // -// Tool_metlev::run -- Primary interfaces to the tool. +// Tool_modori::flipMensurationStyle -- Returns true if swapped. // -bool Tool_metlev::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisMensuration()) { + // switch to invisible mensuration + text = "*omet"; + text += token->substr(4); + token->setText(text); + output = true; + } else if (token->isOriginalMensuration()) { + // switch to visible mensuration + text = "*met"; + text += token->substr(5); + token->setText(text); + output = true; } - return status; -} - - -bool Tool_metlev::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - return run(infile, out); -} - -bool Tool_metlev::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - out << infile; - return status; + return output; } -bool Tool_metlev::run(HumdrumFile& infile) { - int lineCount = infile.getLineCount(); - if (lineCount == 0) { - m_error_text << "No input data"; - return false; - } - string exinterp = getString("exinterp"); - if (exinterp.empty()) { - exinterp = "**blev"; - } else if (exinterp[0] != '*') { - exinterp.insert(0, "*"); - } - if (exinterp[1] != '*') { - exinterp.insert(0, "*"); - } +////////////////////////////// +// +// Tool_modori::swapKeyStyle -- Returns true if swapped. +// - m_kernspines = infile.getKernSpineStartList(); +bool Tool_modori::swapKeyStyle(HTp one, HTp two) { + bool mtype1 = false; + bool mtype2 = false; + bool otype1 = false; + bool otype2 = false; + bool ktype1 = false; + bool ktype2 = false; + bool output = false; - vector beatlev(lineCount, NAN); - int track = 0; - if (m_kernspines.size() > 0) { - track = m_kernspines[0]->getTrack(); - } else { - m_error_text << "No **kern spines in input file" << endl; - return false; + if (one->isKeySignature()) { + ktype1 = true; + } else if (one->isModernKeySignature()) { + mtype1 = true; + } else if (one->isOriginalKeySignature()) { + otype1 = true; } - infile.getMetricLevels(beatlev, track, NAN); - for (int i=0; iisKeySignature()) { + ktype2 = true; + } else if (two->isModernKeySignature()) { + mtype2 = true; + } else if (two->isOriginalKeySignature()) { + otype2 = true; } - if (getBoolean("kern-spine")) { - int kspine = getInteger("kern-spine") - 1; - if ((kspine >= 0) && (kspine < (int)m_kernspines.size())) { - vector > results; - fillVoiceResults(results, infile, beatlev); - if (kspine == (int)m_kernspines.size() - 1) { - infile.appendDataSpine(results.back(), "nan", exinterp); - } else { - int track = m_kernspines[kspine+1]->getTrack(); - infile.insertDataSpineBefore(track, results[kspine], - "nan", exinterp); - } - infile.createLinesFromTokens(); - return true; - } - } else if (getBoolean("append")) { - infile.appendDataSpine(beatlev, "nan", exinterp); - infile.createLinesFromTokens(); - return true; - } else if (getBoolean("prepend")) { - infile.prependDataSpine(beatlev, "nan", exinterp); - infile.createLinesFromTokens(); - return true; - } else if (getBoolean("composite")) { - infile.prependDataSpine(beatlev, "nan", exinterp); - infile.printFieldIndex(0, m_humdrum_text); - infile.clear(); - infile.readString(m_humdrum_text.str()); - } else { - vector > results; - fillVoiceResults(results, infile, beatlev); - infile.appendDataSpine(results.back(), "nan", exinterp); - for (int i = (int)results.size()-1; i>0; i--) { - int track = m_kernspines[i]->getTrack(); - infile.insertDataSpineBefore(track, results[i-1], "nan", exinterp); + if (m_modernQ) { + // Show the modern key signature. If one key is *mk and the + // other is *k then change *mk to *k and *k to *ok respectively. + if (ktype1 && mtype2) { + convertKeySignatureToOriginal(one); + convertKeySignatureToRegular(two); + output = true; + } else if (mtype1 && ktype2) { + convertKeySignatureToRegular(one); + convertKeySignatureToOriginal(two); + output = true; + } + } else if (m_originalQ) { + // Show the original key. If one key is *ok and the + // other is *k then change *ok to *k and *k to *mk respectively. + if (ktype1 && otype2) { + convertKeySignatureToModern(one); + convertKeySignatureToRegular(two); + output = true; + } else if (otype1 && ktype2) { + convertKeySignatureToRegular(one); + convertKeySignatureToModern(two); + output = true; } - infile.createLinesFromTokens(); - return true; } - - return false; + return output; } ////////////////////////////// // -// Tool_metlev::fillVoiceResults -- Split the metric level analysis into values -// for each voice. +// Tool_modori::swapInstrumentNameStyle -- Returns true if swapped. // -void Tool_metlev::fillVoiceResults(vector >& results, - HumdrumFile& infile, vector& beatlev) { +bool Tool_modori::swapInstrumentNameStyle(HTp one, HTp two) { + bool mtype1 = false; + bool mtype2 = false; + bool otype1 = false; + bool otype2 = false; + bool ktype1 = false; + bool ktype2 = false; + bool output = false; - results.resize(m_kernspines.size()); - for (int i=0; i<(int)results.size(); i++) { - results[i].resize(beatlev.size()); - fill(results[i].begin(), results[i].end(), NAN); + if (one->isInstrumentName()) { + ktype1 = true; + } else if (one->isModernInstrumentName()) { + mtype1 = true; + } else if (one->isOriginalInstrumentName()) { + otype1 = true; } - int track; - vector rtracks(infile.getTrackCount() + 1, -1); - for (int i=0; i<(int)m_kernspines.size(); i++) { - int track = m_kernspines[i]->getTrack(); - rtracks[track] = i; + + if (two->isInstrumentName()) { + ktype2 = true; + } else if (two->isModernInstrumentName()) { + mtype2 = true; + } else if (two->isOriginalInstrumentName()) { + otype2 = true; } - bool attacksQ = getBoolean("attacks-only"); - vector nonnullcount(m_kernspines.size(), 0); - vector attackcount(m_kernspines.size(), 0); - HTp token; - int voice; - int i, j; - for (i=0; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - track = token->getTrack(); - voice = rtracks[track]; - nonnullcount[voice]++; - if (token->isNoteAttack()) { - attackcount[voice]++; - } + if (m_modernQ) { + // Show the modern instrument name. If one name is *mI" and the + // other is *I" then change *mI" to *I" and *I" to *oI" respectively. + if (ktype1 && mtype2) { + convertInstrumentNameToOriginal(one); + convertInstrumentNameToRegular(two); + output = true; + } else if (mtype1 && ktype2) { + convertInstrumentNameToRegular(one); + convertInstrumentNameToOriginal(two); + output = true; } - for (int v=0; v<(int)m_kernspines.size(); v++) { - if (attacksQ) { - if (attackcount[v]) { - results[v][i] = beatlev[i]; - attackcount[v] = 0; - } - } else { - if (nonnullcount[v]) { - results[v][i] = beatlev[i]; - } - nonnullcount[v] = 0; - } + } else if (m_originalQ) { + // Show the original key. If one key is *ok and the + // other is *k then change *ok to *k and *k to *mk respectively. + if (ktype1 && otype2) { + convertInstrumentNameToModern(one); + convertInstrumentNameToRegular(two); + output = true; + } else if (otype1 && ktype2) { + convertInstrumentNameToRegular(one); + convertInstrumentNameToModern(two); + output = true; } } + return output; } - -///////////////////////////////// +////////////////////////////// // -// Tool_modori::Tool_modori -- Set the recognized options for the tool. +// Tool_modori::swapInstrumentAbbreviationStyle -- Returns true if swapped. // -Tool_modori::Tool_modori(void) { - define("m|modern=b", "prepare score for modern style"); - define("o|original=b", "prepare score for original style"); - define("d|info=b", "display key/clef/mensuration information"); - define("I|no-instrument-name|no-instrument-names=b", "do not change part labels"); - define("A|no-instrument-abbreviation|no-instrument-abbreviations=b", "do not change part label abbreviations"); - define("C|no-clef|no-clefs=b", "do not change clefs"); - define("K|no-key|no-keys=b", "do not change key signatures"); - define("L|no-lyrics=b", "do not change **text exclusive interpretations"); - define("M|no-mensuration|no-mensurations=b", "do not change mensurations"); - define("R|no-references=b", "do not change reference records keys"); - define("T|no-text=b", "do not change !LO:(TX|DY) layout parameters"); -} - - - -///////////////////////////////// -// -// Tool_modori::run -- Do the main work of the tool. -// +bool Tool_modori::swapInstrumentAbbreviationStyle(HTp one, HTp two) { + bool mtype1 = false; + bool mtype2 = false; + bool otype1 = false; + bool otype2 = false; + bool ktype1 = false; + bool ktype2 = false; + bool output = false; -bool Tool_modori::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisInstrumentAbbreviation()) { + ktype1 = true; + } else if (one->isModernInstrumentAbbreviation()) { + mtype1 = true; + } else if (one->isOriginalInstrumentAbbreviation()) { + otype1 = true; } - return status; -} - -bool Tool_modori::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (two->isInstrumentAbbreviation()) { + ktype2 = true; + } else if (two->isModernInstrumentAbbreviation()) { + mtype2 = true; + } else if (two->isOriginalInstrumentAbbreviation()) { + otype2 = true; } - return status; -} - -bool Tool_modori::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (m_modernQ) { + // Show the modern instrument name. If one name is *mI" and the + // other is *I" then change *mI" to *I" and *I" to *oI" respectively. + if (ktype1 && mtype2) { + convertInstrumentAbbreviationToOriginal(one); + convertInstrumentAbbreviationToRegular(two); + output = true; + } else if (mtype1 && ktype2) { + convertInstrumentAbbreviationToRegular(one); + convertInstrumentAbbreviationToOriginal(two); + output = true; + } + } else if (m_originalQ) { + // Show the original key. If one key is *ok and the + // other is *k then change *ok to *k and *k to *mk respectively. + if (ktype1 && otype2) { + convertInstrumentAbbreviationToModern(one); + convertInstrumentAbbreviationToRegular(two); + output = true; + } else if (otype1 && ktype2) { + convertInstrumentAbbreviationToRegular(one); + convertInstrumentAbbreviationToModern(two); + output = true; + } } - return status; -} - - -bool Tool_modori::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; + return output; } ////////////////////////////// // -// Tool_modori::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_modori::swapMensurationStyle -- Returns true if swapped. // -void Tool_modori::initialize(void) { - m_infoQ = getBoolean("info"); - m_modernQ = getBoolean("modern"); - m_originalQ = getBoolean("original"); - if (m_modernQ && m_originalQ) { - // if both options are used, ignore -m: - m_modernQ = false; +bool Tool_modori::swapMensurationStyle(HTp one, HTp two) { + bool mtype1 = false; + bool mtype2 = false; + bool otype1 = false; + bool otype2 = false; + bool ktype1 = false; + bool ktype2 = false; + bool output = false; + + if (one->isMensuration()) { + ktype1 = true; + } else if (one->isModernMensuration()) { + mtype1 = true; + } else if (one->isOriginalMensuration()) { + otype1 = true; } - m_nokeyQ = getBoolean("no-key"); - m_noclefQ = getBoolean("no-clef"); - m_nolotextQ = getBoolean("no-text"); - m_nolyricsQ = getBoolean("no-lyrics"); - m_norefsQ = getBoolean("no-references"); - m_nomensurationQ = getBoolean("no-mensuration"); - m_nolabelsQ = getBoolean("no-instrument-names"); - m_nolabelAbbrsQ = getBoolean("no-instrument-abbreviations"); + + if (two->isMensuration()) { + ktype2 = true; + } else if (two->isModernMensuration()) { + mtype2 = true; + } else if (two->isOriginalMensuration()) { + otype2 = true; + } + + if (m_modernQ) { + if (ktype1 && mtype2) { + convertMensurationToOriginal(one); + convertMensurationToRegular(two); + output = true; + } else if (mtype1 && ktype2) { + convertMensurationToRegular(one); + convertMensurationToOriginal(two); + output = true; + } + } else if (m_originalQ) { + if (ktype1 && otype2) { + convertMensurationToModern(one); + convertMensurationToRegular(two); + output = true; + } else if (otype1 && ktype2) { + convertMensurationToRegular(one); + convertMensurationToModern(two); + output = true; + } + } + return output; } ////////////////////////////// // -// Tool_modori::processFile -- +// Tool_modori::swapClefStyle -- Returns true if swapped. // -void Tool_modori::processFile(HumdrumFile& infile) { - m_keys.clear(); - m_labels.clear(); - m_labelAbbrs.clear(); - m_clefs.clear(); - m_mensurations.clear(); - m_references.clear(); - m_lyrics.clear(); - m_lotext.clear(); +bool Tool_modori::swapClefStyle(HTp one, HTp two) { + bool mtype1 = false; + bool mtype2 = false; + bool otype1 = false; + bool otype2 = false; + bool ktype1 = false; + bool ktype2 = false; + bool output = false; - int maxtrack = infile.getMaxTrack(); - m_keys.resize(maxtrack+1); - m_labels.resize(maxtrack+1); - m_labelAbbrs.resize(maxtrack+1); - m_clefs.resize(maxtrack+1); - m_mensurations.resize(maxtrack+1); - m_references.reserve(1000); - m_lyrics.reserve(1000); - m_lotext.reserve(1000); + if (one->isClef()) { + ktype1 = true; + } else if (one->isModernClef()) { + mtype1 = true; + } else if (one->isOriginalClef()) { + otype1 = true; + } - HumRegex hre; - int exinterpLine = -1; + if (two->isClef()) { + ktype2 = true; + } else if (two->isModernClef()) { + mtype2 = true; + } else if (two->isOriginalClef()) { + otype2 = true; + } - for (int i=0; iisExclusiveInterpretation()) { - exinterpLine = i; - continue; - } - if (!token->isKern()) { - continue; - } - if (*token == "*") { - continue; - } - int track = token->getTrack(); - if (token->isKeySignature()) { - m_keys[track][timeval].push_back(token); - } else if (token->isOriginalKeySignature()) { - m_keys[track][timeval].push_back(token); - } else if (token->isModernKeySignature()) { - m_keys[track][timeval].push_back(token); + } + return output; +} - } else if (token->isInstrumentName()) { - m_labels[track][timeval].push_back(token); - } else if (token->isOriginalInstrumentName()) { - m_labels[track][timeval].push_back(token); - } else if (token->isModernInstrumentName()) { - m_labels[track][timeval].push_back(token); - } else if (token->isInstrumentAbbreviation()) { - m_labelAbbrs[track][timeval].push_back(token); - } else if (token->isOriginalInstrumentAbbreviation()) { - m_labelAbbrs[track][timeval].push_back(token); - } else if (token->isModernInstrumentAbbreviation()) { - m_labelAbbrs[track][timeval].push_back(token); - } else if (token->isClef()) { - m_clefs[track][timeval].push_back(token); - } else if (token->isOriginalClef()) { - m_clefs[track][timeval].push_back(token); - } else if (token->isModernClef()) { - m_clefs[track][timeval].push_back(token); +////////////////////////////// +// +// Tool_modori::convertKeySignatureToModern -- +// - } else if (token->isMensuration()) { - m_mensurations[track][timeval].push_back(token); - } else if (token->isOriginalMensuration()) { - m_mensurations[track][timeval].push_back(token); - } else if (token->isModernMensuration()) { - m_mensurations[track][timeval].push_back(token); - } - } +void Tool_modori::convertKeySignatureToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?k(.*)")) { + string text = "*mk"; + text += hre.getMatch(1); + token->setText(text); } +} - if (exinterpLine >= 0) { - processExclusiveInterpretationLine(infile, exinterpLine); - } - storeModOriReferenceRecords(infile); - if (m_infoQ) { - if (m_modernQ || m_originalQ) { - m_humdrum_text << infile; - } - printInfo(); - } +////////////////////////////// +// +// Tool_modori::convertInstrumentNameToModern -- +// - if (!(m_modernQ || m_originalQ)) { - // nothing to do - return; +void Tool_modori::convertInstrumentNameToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I\"(.*)")) { + string text = "*mI\""; + text += hre.getMatch(1); + token->setText(text); } - - switchModernOriginal(infile); - printModoriOutput(infile); } + ////////////////////////////// // -// Tool_modori::processExclusiveInterpretationLine -- +// Tool_modori::convertInstrumentAbbreviationToModern -- // -void Tool_modori::processExclusiveInterpretationLine(HumdrumFile& infile, int line) { - vector staffish; - vector staff; - vector> nonstaff; - bool init = false; - bool changed = false; - - if (!infile[line].isExclusive()) { - return; +void Tool_modori::convertInstrumentAbbreviationToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I'(.*)")) { + string text = "*mI'"; + text += hre.getMatch(1); + token->setText(text); } +} - for (int i=0; iisExclusiveInterpretation()) { - continue; - } - if (token->isStaff()) { - staff.push_back(token); - nonstaff.resize(nonstaff.size() + 1); - init = 1; - } else { - if (init) { - nonstaff.back().push_back(token); - } - } - if (token->isStaff()) { - staffish.push_back(token); - } else if (*token == "**mod-kern") { - staffish.push_back(token); - } else if (*token == "**mod-mens") { - staffish.push_back(token); - } else if (*token == "**ori-kern") { - staffish.push_back(token); - } else if (*token == "**ori-mens") { - staffish.push_back(token); - } - } - for (int i=0; i<(int)staff.size(); i++) { - changed |= processStaffCompanionSpines(nonstaff[i]); + +////////////////////////////// +// +// Tool_modori::convertKeySignatureToOriginal -- +// + +void Tool_modori::convertKeySignatureToOriginal(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?k(.*)")) { + string text = "*ok"; + text += hre.getMatch(1); + token->setText(text); } +} - changed |= processStaffSpines(staffish); - if (changed) { - infile[line].createLineFromTokens(); + +////////////////////////////// +// +// Tool_modori::convertInstrumentNameToOriginal -- +// + +void Tool_modori::convertInstrumentNameToOriginal(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I\"(.*)")) { + string text = "*oI\""; + text += hre.getMatch(1); + token->setText(text); } } @@ -99563,182 +103198,111 @@ void Tool_modori::processExclusiveInterpretationLine(HumdrumFile& infile, int li ////////////////////////////// // -// Tool_modori::processStaffSpines -- +// Tool_modori::convertInstrumentAbbreviationToOriginal -- // -bool Tool_modori::processStaffSpines(vector& tokens) { - +void Tool_modori::convertInstrumentAbbreviationToOriginal(HTp token) { HumRegex hre; - bool changed = false; - for (int i=0; i<(int)tokens.size(); i++) { - if (hre.search(tokens[i], "^\\*\\*(ori|mod)-(.*)")) { - string newexinterp = "**" + hre.getMatch(2) + "-" + hre.getMatch(1); - tokens[i]->setText(newexinterp); - changed = true; - } else if (hre.search(tokens[i], "^\\*\\*(.*?)-(ori|mod)$")) { - string newexinterp = "**" + hre.getMatch(2) + "-" + hre.getMatch(1); - tokens[i]->setText(newexinterp); - changed = true; - } + if (hre.search(token, "^\\*[mo]?I'(.*)")) { + string text = "*oI'"; + text += hre.getMatch(1); + token->setText(text); } - - return changed; } ////////////////////////////// // -// Tool_modori::processStaffCompanionSpines -- +// Tool_modori::convertKeySignatureToRegular -- // -bool Tool_modori::processStaffCompanionSpines(vector tokens) { - - vector mods; - vector oris; - vector other; - - for (int i=0; i<(int)tokens.size(); i++) { - if (tokens[i]->find("**mod-") != string::npos) { - mods.push_back(tokens[i]); - } else if (tokens[i]->find("**ori-") != string::npos) { - oris.push_back(tokens[i]); - } else { - other.push_back(tokens[i]); - } +void Tool_modori::convertKeySignatureToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?k(.*)")) { + string text = "*k"; + text += hre.getMatch(1); + token->setText(text); } +} - bool gchanged = false; - if (mods.empty() && oris.empty()) { - // Nothing to do. - return false; - } - // mods and oris should not be mixed, so if there are no - // other spines, then also give up: - if (other.empty()) { - return false; - } +////////////////////////////// +// +// Tool_modori::convertInstrumentNameToRegular -- +// +void Tool_modori::convertInstrumentNameToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I\"(.*)")) { + string text = "*I\""; + text += hre.getMatch(1); + token->setText(text); + } +} - if (m_modernQ) { - bool changed = false; - // Swap (**mod-XXX and **XXX) to (**XXX and **ori-XXX) - for (int i=0; i<(int)other.size(); i++) { - if (other[i] == NULL) { - continue; - } - string target = "**mod-" + other[i]->substr(2); - for (int j=0; j<(int)mods.size(); j++) { - if (mods[j] == NULL) { - continue; - } - if (*mods[j] != target) { - continue; - } - mods[j]->setText(*other[i]); - mods[j] = NULL; - changed = true; - gchanged = true; - } - if (changed) { - string replacement = "**ori-" + other[i]->substr(2); - other[i]->setText(replacement); - other[i] = NULL; - } - } - } else if (m_originalQ) { - bool changed = false; - // Swap (**ori-XXX and **XXX) to (**XXX and **mod-XXX) +////////////////////////////// +// +// Tool_modori::convertInstrumentAbbreviationToRegular -- +// - for (int i=0; i<(int)other.size(); i++) { - if (other[i] == NULL) { - continue; - } - string target = "**ori-" + other[i]->substr(2); - for (int j=0; j<(int)oris.size(); j++) { - if (oris[j] == NULL) { - continue; - } - if (*oris[j] != target) { - continue; - } - oris[j]->setText(*other[i]); - oris[j] = NULL; - changed = true; - gchanged = true; - } - if (changed) { - string replacement = "**mod-" + other[i]->substr(2); - other[i]->setText(replacement); - other[i] = NULL; - } - } +void Tool_modori::convertInstrumentAbbreviationToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I'(.*)")) { + string text = "*I'"; + text += hre.getMatch(1); + token->setText(text); } - - return gchanged; } ////////////////////////////// // -// Tool_modori::storeModOriReferenceRecors -- +// Tool_modori::convertClefToModern -- // -void Tool_modori::storeModOriReferenceRecords(HumdrumFile& infile) { - m_references.clear(); - - vector refs = infile.getGlobalReferenceRecords(); - vector keys(refs.size()); - for (int i=0; i<(int)refs.size(); i++) { - string key = refs.at(i)->getReferenceKey(); - keys.at(i) = key; +void Tool_modori::convertClefToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?clef(.*)")) { + string text = "*mclef"; + text += hre.getMatch(1); + token->setText(text); } +} + - vector modernIndex; - vector originalIndex; +////////////////////////////// +// +// Tool_modori::convertClefToOriginal -- +// + +void Tool_modori::convertClefToOriginal(HTp token) { HumRegex hre; - for (int i=0; i<(int)keys.size(); i++) { - if (m_modernQ || m_infoQ) { - if (hre.search(keys[i], "-mod$")) { - modernIndex.push_back(i); - } - } else if (m_originalQ || m_infoQ) { - if (hre.search(keys[i], "-ori$")) { - originalIndex.push_back(i); - } - } + if (hre.search(token, "^\\*[mo]?clef(.*)")) { + string text = "*oclef"; + text += hre.getMatch(1); + token->setText(text); } +} - if (m_modernQ || m_infoQ) { - // Store *-mod reference records if there is a pairing: - int pairing = -1; - for (int i=0; i<(int)modernIndex.size(); i++) { - int index = modernIndex[i]; - pairing = getPairedReference(index, keys); - if (pairing >= 0) { - m_references.push_back(make_pair(refs[index]->token(0), refs[pairing]->token(0))); - } - } - } - if (m_originalQ || m_infoQ) { - // Store *-ori reference records if there is a pairing: - int pairing = -1; - string target; - for (int i=0; i<(int)originalIndex.size(); i++) { - int index = originalIndex[i]; - pairing = getPairedReference(index, keys); - if (pairing >= 0) { - target = keys[index]; - m_references.push_back(make_pair(refs[index]->token(0), refs[pairing]->token(0))); - } - } + +////////////////////////////// +// +// Tool_modori::convertClefToRegular -- +// + +void Tool_modori::convertClefToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?clef(.*)")) { + string text = "*clef"; + text += hre.getMatch(1); + token->setText(text); } } @@ -99746,755 +103310,678 @@ void Tool_modori::storeModOriReferenceRecords(HumdrumFile& infile) { ////////////////////////////// // -// Tool_modori::getPairedReference -- +// Tool_modori::convertMensurationToModern -- // -int Tool_modori::getPairedReference(int index, vector& keys) { - string key = keys.at(index); - string tkey = key; - if (tkey.size() > 4) { - tkey.resize(tkey.size() - 4); - } else { - return -1; +void Tool_modori::convertMensurationToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?met\\((.*)")) { + string text = "*mmet("; + text += hre.getMatch(1); + token->setText(text); } +} - for (int i=0; i<(int)keys.size(); i++) { - int ii = index + i; - if (ii < (int)keys.size()) { - if (tkey == keys.at(ii)) { - return ii; - } - } - ii = index - i; - if (ii >= 0) { - if (tkey == keys.at(ii)) { - return ii; - } - } + + +////////////////////////////// +// +// Tool_modori::convertMensurationToOriginal -- +// + +void Tool_modori::convertMensurationToOriginal(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?met\\((.*)")) { + string text = "*omet("; + text += hre.getMatch(1); + token->setText(text); } - return -1; } ////////////////////////////// // -// Tool_modori::switchModernOriginal -- +// Tool_modori::convertMensurationToRegular -- // -void Tool_modori::switchModernOriginal(HumdrumFile& infile) { +void Tool_modori::convertMensurationToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?met\\((.*)")) { + string text = "*met("; + text += hre.getMatch(1); + token->setText(text); + } +} - set changed; - if (!m_nokeyQ) { - for (int t=1; t<(int)m_keys.size(); ++t) { - for (auto it = m_keys.at(t).begin(); it != m_keys.at(t).end(); ++it) { - if (it->second.size() != 2) { - continue; - } - bool status = swapKeyStyle(it->second.at(0), it->second.at(1)); - if (status) { - int line = it->second.at(0)->getLineIndex(); - changed.insert(line); - line = it->second.at(1)->getLineIndex(); - changed.insert(line); - } - } + +//////////////////// +// +// Tool_modori::printInfo -- +// + +void Tool_modori::printInfo(void) { + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! KEYS:" << endl; + + for (int t=1; t<(int)m_keys.size(); ++t) { + for (auto it = m_keys.at(t).begin(); it != m_keys.at(t).end(); ++it) { + m_humdrum_text << "!!\t" << it->first; + for (int j=0; j<(int)it->second.size(); ++j) { + m_humdrum_text << '\t' << it->second.at(j); + } + m_humdrum_text << endl; } } - if (!m_nolabelsQ) { - for (int t=1; t<(int)m_labels.size(); ++t) { - for (auto it = m_labels.at(t).begin(); it != m_labels.at(t).end(); ++it) { - if (it->second.size() != 2) { - continue; - } - bool status = swapInstrumentNameStyle(it->second.at(0), it->second.at(1)); - if (status) { - int line = it->second.at(0)->getLineIndex(); - changed.insert(line); - line = it->second.at(1)->getLineIndex(); - changed.insert(line); - } + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! CLEFS:" << endl; + + for (int t=1; t<(int)m_keys.size(); ++t) { + for (auto it = m_clefs.at(t).begin(); it != m_clefs.at(t).end(); ++it) { + m_humdrum_text << "!!\t" << it->first; + for (int j=0; j<(int)it->second.size(); ++j) { + m_humdrum_text << '\t' << it->second.at(j); } + m_humdrum_text << endl; } } - if (!m_nolabelAbbrsQ) { - for (int t=1; t<(int)m_labelAbbrs.size(); ++t) { - for (auto it = m_labelAbbrs.at(t).begin(); it != m_labelAbbrs.at(t).end(); ++it) { - if (it->second.size() != 2) { - continue; - } - bool status = swapInstrumentAbbreviationStyle(it->second.at(0), it->second.at(1)); - if (status) { - int line = it->second.at(0)->getLineIndex(); - changed.insert(line); - line = it->second.at(1)->getLineIndex(); - changed.insert(line); - } + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! MENSURATIONS:" << endl; + + for (int t=1; t<(int)m_mensurations.size(); ++t) { + for (auto it = m_mensurations.at(t).begin(); it != m_mensurations.at(t).end(); ++it) { + m_humdrum_text << "!!\t" << it->first; + for (int j=0; j<(int)it->second.size(); j++) { + m_humdrum_text << '\t' << it->second.at(j); } + m_humdrum_text << endl; } } - if (!m_nolyricsQ) { - bool adjust = false; - int line = -1; - for (int i=0; i<(int)m_lyrics.size(); i++) { - HTp token = m_lyrics[i]; - line = token->getLineIndex(); - if (m_modernQ) { - if (*token == "**text") { - adjust = true; - token->setText("**ori-text"); - } else if (*token == "**mod-text") { - adjust = true; - token->setText("**text"); - } - } else { - if (*token == "**text") { - adjust = true; - token->setText("**mod-text"); - } else if (*token == "**ori-text") { - adjust = true; - token->setText("**text"); - } - } - } - if (adjust && (line >= 0)) { - infile[line].createLineFromTokens(); - } + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! LYRICS:" << endl; + + for (int i=0; i<(int)m_lyrics.size(); i++) { + HTp token = m_lyrics[i]; + m_humdrum_text << "!!\t"; + m_humdrum_text << token; + m_humdrum_text << endl; } - if (!m_nolotextQ) { - HumRegex hre; - for (int i=0; i<(int)m_lotext.size(); i++) { - HTp token = m_lotext[i]; - int line = token->getLineIndex(); - if (hre.search(token, "^!!?LO:(TX|DY).*:mod=")) { - string text = *token; - hre.replaceDestructive(text, ":ori=", ":t="); - hre.replaceDestructive(text, ":t=", ":mod="); - token->setText(text); - changed.insert(line); - } else if (hre.search(token, "^!!?LO:(TX|DY).*:ori=")) { - string text = *token; - hre.replaceDestructive(text, ":mod=", ":t="); - hre.replaceDestructive(text, ":t=", ":ori="); - token->setText(text); - changed.insert(line); - } - } + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! TEXT:" << endl; + + for (int i=0; i<(int)m_lotext.size(); i++) { + m_humdrum_text << "!!\t" << m_lotext[i] << endl; } - if (!m_norefsQ) { - HumRegex hre; - for (int i=0; i<(int)m_references.size(); i++) { - HTp first = m_references[i].first; - HTp second = m_references[i].second; + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! REFERENCES:" << endl; - if (m_modernQ) { - if (hre.search(first, "^!!![^:]*?-mod:")) { - string text = *first; - hre.replaceDestructive(text, ":", "-...:"); - first->setText(text); - infile[first->getLineIndex()].createLineFromTokens(); + for (int i=0; i<(int)m_references.size(); i++) { + m_humdrum_text << "!!\t" << m_references[i].first << endl; + m_humdrum_text << "!!\t" << m_references[i].second << endl; + m_humdrum_text << "!!\n"; + } - text = *second; - hre.replaceDestructive(text, "-ori:", ":"); - second->setText(text); - infile[second->getLineIndex()].createLineFromTokens(); - } - } else if (m_originalQ) { - if (hre.search(first, "^!!![^:]*?-ori:")) { - string text = *first; - hre.replaceDestructive(text, ":", "-...:"); - first->setText(text); - infile[first->getLineIndex()].createLineFromTokens(); + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; +} - text = *second; - hre.replaceDestructive(text, "-mod:", ":"); - second->setText(text); - infile[second->getLineIndex()].createLineFromTokens(); - } - } - } - } - // Mensurations are only used for "original" display. It is possible - // to use a modern metric signature (common time or cut time) but these - // are not currently allowed. Only one *met at a given time position - // is allowed. - if (!m_nomensurationQ) { - for (int t=1; t<(int)m_mensurations.size(); ++t) { - for (auto it = m_mensurations.at(t).begin(); it != m_mensurations.at(t).end(); ++it) { - if (it->second.size() == 1) { - // swap omet to met, or met to omet: - bool status = flipMensurationStyle(it->second.at(0)); - if (status) { - int line = it->second.at(0)->getLineIndex(); - changed.insert(line); - } - } else if (it->second.size() == 2) { - // swap omet/met or mmet/met: - bool status = swapMensurationStyle(it->second.at(0), it->second.at(1)); - if (status) { - int line = it->second.at(0)->getLineIndex(); - changed.insert(line); - line = it->second.at(1)->getLineIndex(); - changed.insert(line); - } - } - } - } +////////////////////////////// +// +// SonorityDatabase::buildDatabase -- +// + +void SonorityDatabase::buildDatabase(HLp line) { + clear(); + if (line == NULL) { + return; + } + m_line = line; + bool nullQ = false; + if (!line->isData()) { + return; } + int lowesti = 0; + int lowest12 = 1000; - if (!m_noclefQ) { - for (int t=1; t<(int)m_clefs.size(); ++t) { - for (auto it = m_clefs.at(t).begin(); it != m_clefs.at(t).end(); ++it) { - if (it->second.size() != 2) { - continue; - } - bool status = swapClefStyle(it->second.at(0), it->second.at(1)); - if (status) { - int line = it->second.at(0)->getLineIndex(); - changed.insert(line); - line = it->second.at(1)->getLineIndex(); - changed.insert(line); - } + for (int i=0; igetFieldCount(); i++) { + HTp token = m_line->token(i); + if (!token->isKern()) { + continue; + } + if (token->isRest()) { + // ignoring rests, at least for now + continue; + } + if (token->isNull()) { + nullQ = true; + token = token->resolveNull(); + } + if (token->isNull()) { + continue; + } + int scount = token->getSubtokenCount(); + for (int j=0; j= 'a' && ch <= 'g') { + hpieces.resize(hpieces.size() + 1); + hpieces.back() += harmonic[i]; + } else if (ch == '-') { + hpieces.back() += ch; + } else if (ch == 'n') { + hpieces.back() += ch; + } else if (ch == '#') { + hpieces.back() += ch; } + } + hquery.resize(hpieces.size()); + for (int i=0; i<(int)hpieces.size(); i++) { + hquery[i].setString(hpieces[i]); } } -////////////////////////////// +///////////////////////////////// // -// Tool_modori::updateLoMo -- +// Tool_msearch::Tool_msearch -- Set the recognized options for the tool. // -void Tool_modori::updateLoMo(HumdrumFile& infile) { - for (int i=0; i<(int)m_lomo.size(); i++) { - processLoMo(m_lomo[i]); - } +Tool_msearch::Tool_msearch(void) { + define("debug=b", "diatonic search"); + define("q|query=s:4c4d4e4f4g", "combined rhythm/pitch query string"); + define("p|pitch=s:cdefg", "pitch query string"); + define("i|interval=s:2222", "interval query string"); + define("r|d|rhythm|duration=s:44444", "rhythm query string"); + define("t|text=s:", "lyrical text query string"); + define("O|no-overlap=b", "do not allow matches to overlap"); + define("x|cross=b", "search across parts"); + define("c|color=s", "highlight color"); + define("m|mark|marker=s:@", "marking character"); + define("M|no-mark|no-marker=b", "do not mark matches"); + define("Q|quiet=b", "quiet mode: do not summarize matches"); } -////////////////////////////// +///////////////////////////////// // -// Tool_modori::processLoMo -- +// Tool_msearch::run -- Do the main work of the tool. // -void Tool_modori::processLoMo(HTp lomo) { - HumRegex hre; +bool Tool_msearch::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; igetText(); - string modtext; - string oritext; - string base; - string rest; - if (hre.search(text, "(.*):mod=([^:]*)(.*)")) { - base = hre.getMatch(1); - modtext = hre.getMatch(2); - rest = hre.getMatch(3); - hre.replaceDestructive(modtext, ":", ":", "g"); - HTp current = lomo->getNextToken(); - // null parameter allows next following null token - // to be swapped out - bool nullQ = hre.search(text, ":null:"); - if (!nullQ) { - while (current) { - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - break; - } - } - if (current) { - string oritext = current->getText(); - hre.replaceDestructive(oritext, ":", ":", "g"); - current->setText(modtext); - string newtext = base; - newtext += ":ori="; - newtext += oritext; - newtext += rest; - lomo->setText(newtext); - lomo->getLine()->createLineFromTokens(); - current->getLine()->createLineFromTokens(); - } - } - } else if (m_originalQ) { - string text = lomo->getText(); - string modtext; - string oritext; - string base; - string rest; - if (hre.search(text, "(.*):ori=([^:]*)(.*)")) { - base = hre.getMatch(1); - oritext = hre.getMatch(2); - rest = hre.getMatch(3); - hre.replaceDestructive(oritext, ":", ":", "g"); - HTp current = lomo->getNextToken(); - // null parameter allows next following null token - // to be swapped out - bool nullQ = hre.search(text, ":null:"); - if (nullQ) { - while (current) { - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - break; - } - } - if (current) { - string modtext = current->getText(); - hre.replaceDestructive(modtext, ":", ":", "g"); - current->setText(oritext); - string newtext = base; - newtext += ":mod="; - newtext += modtext; - newtext += rest; - lomo->setText(newtext); - lomo->getLine()->createLineFromTokens(); - current->getLine()->createLineFromTokens(); - } +bool Tool_msearch::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; + } + return status; +} + + +bool Tool_msearch::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; + } + return status; +} + + +bool Tool_msearch::run(HumdrumFile& infile) { + m_sonorities.resize(infile.getLineCount()); + m_sonoritiesChecked.resize(infile.getLineCount()); + fill(m_sonoritiesChecked.begin(), m_sonoritiesChecked.end(), false); + m_debugQ = getBoolean("debug"); + m_quietQ = getBoolean("quiet"); + m_nooverlapQ = getBoolean("no-overlap"); + NoteGrid grid(infile); + if (m_debugQ) { + grid.printGridInfo(cerr); + // return 1; + } + initialize(); + + if (getBoolean("text")) { + m_text = getString("text"); + } + + if (m_text.empty()) { + vector query; + fillMusicQuery(query); + if (!query.empty()) { + doMusicSearch(infile, grid, query); } + } else { + vector query; + fillTextQuery(query, getString("text")); + doTextSearch(infile, grid, query); } + + infile.createLinesFromTokens(); + m_humdrum_text << infile; + + return 1; } ////////////////////////////// // -// Tool_modori::flipMensurationStyle -- Returns true if swapped. +// Tool_msearch::initialize -- // -bool Tool_modori::flipMensurationStyle(HTp token) { - bool output = false; - HumRegex hre; - string text; - if (token->isMensuration()) { - // switch to invisible mensuration - text = "*omet"; - text += token->substr(4); - token->setText(text); - output = true; - } else if (token->isOriginalMensuration()) { - // switch to visible mensuration - text = "*met"; - text += token->substr(5); - token->setText(text); - output = true; +void Tool_msearch::initialize(void) { + m_marker = getString("marker"); + // only allowing a single character for now: + m_markQ = !getBoolean("no-marker"); + if (!m_markQ) { + m_marker.clear(); + } else if (!m_marker.empty()) { + m_marker = m_marker[0]; } - - return output; } ////////////////////////////// // -// Tool_modori::swapKeyStyle -- Returns true if swapped. +// Tool_msearch::fillWords -- // -bool Tool_modori::swapKeyStyle(HTp one, HTp two) { - bool mtype1 = false; - bool mtype2 = false; - bool otype1 = false; - bool otype2 = false; - bool ktype1 = false; - bool ktype2 = false; - bool output = false; - - if (one->isKeySignature()) { - ktype1 = true; - } else if (one->isModernKeySignature()) { - mtype1 = true; - } else if (one->isOriginalKeySignature()) { - otype1 = true; +void Tool_msearch::fillWords(HumdrumFile& infile, vector& words) { + vector textspines; + infile.getSpineStartList(textspines, "**silbe"); + if (textspines.empty()) { + infile.getSpineStartList(textspines, "**text"); } - - if (two->isKeySignature()) { - ktype2 = true; - } else if (two->isModernKeySignature()) { - mtype2 = true; - } else if (two->isOriginalKeySignature()) { - otype2 = true; + for (int i=0; i<(int)textspines.size(); i++) { + fillWordsForTrack(words, textspines[i]); } +} - if (m_modernQ) { - // Show the modern key signature. If one key is *mk and the - // other is *k then change *mk to *k and *k to *ok respectively. - if (ktype1 && mtype2) { - convertKeySignatureToOriginal(one); - convertKeySignatureToRegular(two); - output = true; - } else if (mtype1 && ktype2) { - convertKeySignatureToRegular(one); - convertKeySignatureToOriginal(two); - output = true; + + +////////////////////////////// +// +// Tool_msearch::fillWordsForTrack -- +// + +void Tool_msearch::fillWordsForTrack(vector& words, + HTp starttoken) { + HTp tok = starttoken->getNextToken(); + while (tok != NULL) { + if (tok->empty()) { + tok = tok->getNextToken(); + continue; } - } else if (m_originalQ) { - // Show the original key. If one key is *ok and the - // other is *k then change *ok to *k and *k to *mk respectively. - if (ktype1 && otype2) { - convertKeySignatureToModern(one); - convertKeySignatureToRegular(two); - output = true; - } else if (otype1 && ktype2) { - convertKeySignatureToRegular(one); - convertKeySignatureToModern(two); - output = true; + if (tok->isNull()) { + tok = tok->getNextToken(); + continue; + } + if (!tok->isData()) { + tok = tok->getNextToken(); + continue; + } + if (tok->at(0) == '-') { + // append a syllable to the end of previous word + if (!words.empty()) { + words.back()->fullword += tok->substr(1, string::npos); + if (words.back()->fullword.back() == '-') { + words.back()->fullword.pop_back(); + } + } + tok = tok->getNextToken(); + continue; + } else { + // start a new word + TextInfo* temp = new TextInfo(); + temp->nexttoken = NULL; + if (!words.empty()) { + words.back()->nexttoken = tok; + } + temp->fullword = *tok; + if (!temp->fullword.empty()) { + if (temp->fullword.back() == '-') { + temp->fullword.pop_back(); + } + } + temp->starttoken = tok; + words.push_back(temp); + tok = tok->getNextToken(); + continue; } } - return output; } ////////////////////////////// // -// Tool_modori::swapInstrumentNameStyle -- Returns true if swapped. +// Tool_msearch::doTextSearch -- do a basic text search of all parts. // -bool Tool_modori::swapInstrumentNameStyle(HTp one, HTp two) { - bool mtype1 = false; - bool mtype2 = false; - bool otype1 = false; - bool otype2 = false; - bool ktype1 = false; - bool ktype2 = false; - bool output = false; +void Tool_msearch::doTextSearch(HumdrumFile& infile, NoteGrid& grid, + vector& query) { - if (one->isInstrumentName()) { - ktype1 = true; - } else if (one->isModernInstrumentName()) { - mtype1 = true; - } else if (one->isOriginalInstrumentName()) { - otype1 = true; - } + vector words; + words.reserve(10000); + fillWords(infile, words); + int tcount = 0; - if (two->isInstrumentName()) { - ktype2 = true; - } else if (two->isModernInstrumentName()) { - mtype2 = true; - } else if (two->isOriginalInstrumentName()) { - otype2 = true; + HumRegex hre; + for (int i=0; i<(int)query.size(); i++) { + for (int j=0; j<(int)words.size(); j++) { + if (hre.search(words.at(j)->fullword, query.at(i).word, "i")) { + tcount++; + markTextMatch(infile, *words[j]); + } + } } - if (m_modernQ) { - // Show the modern instrument name. If one name is *mI" and the - // other is *I" then change *mI" to *I" and *I" to *oI" respectively. - if (ktype1 && mtype2) { - convertInstrumentNameToOriginal(one); - convertInstrumentNameToRegular(two); - output = true; - } else if (mtype1 && ktype2) { - convertInstrumentNameToRegular(one); - convertInstrumentNameToOriginal(two); - output = true; + string textinterp = "**text"; + vector interps; + infile.getSpineStartList(interps); + //int textcount = 0; + int silbecount = 0; + for (int i=0; i<(int)interps.size(); i++) { + //if (interps[i]->getText() == "**text") { + // textcount++; + //} + if (interps[i]->getText() == "**silbe") { + silbecount++; } - } else if (m_originalQ) { - // Show the original key. If one key is *ok and the - // other is *k then change *ok to *k and *k to *mk respectively. - if (ktype1 && otype2) { - convertInstrumentNameToModern(one); - convertInstrumentNameToRegular(two); - output = true; - } else if (otype1 && ktype2) { - convertInstrumentNameToRegular(one); - convertInstrumentNameToModern(two); - output = true; + } + if (silbecount > 0) { + // giving priority to **silbe content + textinterp = "**silbe"; + } + + if (tcount && m_markQ) { + string content = "!!!RDF"; + content += textinterp; + content += ": "; + content += m_marker; + content += " = marked text"; + if (getBoolean("color")) { + content += ", color=\"" + getString("color") + "\""; } + infile.appendLine(content); + infile.createLinesFromTokens(); + } + + for (int i=0; i<(int)words.size(); i++) { + delete words[i]; + words[i] = NULL; + } + + if (!m_quietQ) { + addTextSearchSummary(infile, tcount, m_marker); } - return output; } ////////////////////////////// // -// Tool_modori::swapInstrumentAbbreviationStyle -- Returns true if swapped. +// Tool_msearch::printQuery -- // -bool Tool_modori::swapInstrumentAbbreviationStyle(HTp one, HTp two) { - bool mtype1 = false; - bool mtype2 = false; - bool otype1 = false; - bool otype2 = false; - bool ktype1 = false; - bool ktype2 = false; - bool output = false; +void Tool_msearch::printQuery(vector& query) { + for (int i=0; i<(int)query.size(); i++) { + cout << query[i]; + } +} - if (one->isInstrumentAbbreviation()) { - ktype1 = true; - } else if (one->isModernInstrumentAbbreviation()) { - mtype1 = true; - } else if (one->isOriginalInstrumentAbbreviation()) { - otype1 = true; + + +////////////////////////////// +// +// Tool_msearch::doMusicSearch -- do a basic melodic search of all parts. +// + +void Tool_msearch::doMusicSearch(HumdrumFile& infile, NoteGrid& grid, + vector& query) { + + m_matches.clear(); + + if (m_debugQ) { + printQuery(query); } - if (two->isInstrumentAbbreviation()) { - ktype2 = true; - } else if (two->isModernInstrumentAbbreviation()) { - mtype2 = true; - } else if (two->isOriginalInstrumentAbbreviation()) { - otype2 = true; + vector> attacks; + attacks.resize(grid.getVoiceCount()); + for (int i=0; i match; + int mcount = 0; + for (int i=0; i<(int)attacks.size(); i++) { + for (int j=0; j<(int)attacks[i].size(); j++) { + m_tomark.clear(); + bool status = checkForMusicMatch(attacks[i], j, query, match); + if (!status) { + m_tomark.clear(); + } + if (status && !match.empty()) { + mcount++; + markMatch(infile, match); + storeMatch(match); + // cerr << "FOUND MATCH AT " << i << ", " << j << endl; + // markNotes(attacks[i], j, (int)query.size()); + } } - } else if (m_originalQ) { - // Show the original key. If one key is *ok and the - // other is *k then change *ok to *k and *k to *mk respectively. - if (ktype1 && otype2) { - convertInstrumentAbbreviationToModern(one); - convertInstrumentAbbreviationToRegular(two); - output = true; - } else if (otype1 && ktype2) { - convertInstrumentAbbreviationToRegular(one); - convertInstrumentAbbreviationToModern(two); - output = true; + } + + if (mcount && m_markQ) { + string content = "!!!RDF**kern: " + m_marker + " = marked note"; + if (getBoolean("color")) { + content += ", color=\"" + getString("color") + "\""; } + infile.appendLine(content); + infile.createLinesFromTokens(); + } + if (!m_quietQ) { + addMusicSearchSummary(infile, mcount, m_marker); } - return output; } ////////////////////////////// // -// Tool_modori::swapMensurationStyle -- Returns true if swapped. +// Tool_msearch::addMusicSearchSummary -- // -bool Tool_modori::swapMensurationStyle(HTp one, HTp two) { - bool mtype1 = false; - bool mtype2 = false; - bool otype1 = false; - bool otype2 = false; - bool ktype1 = false; - bool ktype2 = false; - bool output = false; +void Tool_msearch::addMusicSearchSummary(HumdrumFile& infile, int mcount, const string& marker) { - if (one->isMensuration()) { - ktype1 = true; - } else if (one->isModernMensuration()) { - mtype1 = true; - } else if (one->isOriginalMensuration()) { - otype1 = true; + m_barnums = infile.getMeasureNumbers(); + + infile.appendLine("!!@@BEGIN: MUSIC_SEARCH_RESULT"); + string line; + + line = "!!@QUERY:\t"; + + if (getBoolean("query")) { + line += " -q "; + string qstring = getString("query"); + makeLowerCase(qstring); + if ((qstring.find(' ') != string::npos) || (qstring.find('(') != string::npos)) { + line += '"'; + line += qstring; + line += '"'; + } else { + line += qstring; + } } - if (two->isMensuration()) { - ktype2 = true; - } else if (two->isModernMensuration()) { - mtype2 = true; - } else if (two->isOriginalMensuration()) { - otype2 = true; + if (getBoolean("pitch")) { + line += " -p "; + string pstring = getString("pitch"); + makeLowerCase(pstring); + if ((pstring.find(' ') != string::npos) || (pstring.find('(') != string::npos)) { + line += '"'; + line += pstring; + line += '"'; + } else { + line += pstring; + } } - if (m_modernQ) { - if (ktype1 && mtype2) { - convertMensurationToOriginal(one); - convertMensurationToRegular(two); - output = true; - } else if (mtype1 && ktype2) { - convertMensurationToRegular(one); - convertMensurationToOriginal(two); - output = true; + if (getBoolean("rhythm")) { + line += " -r "; + string rstring = getString("rhythm"); + makeLowerCase(rstring); + if ((rstring.find(' ') != string::npos) || (rstring.find('(') != string::npos)) { + line += '"'; + line += rstring; + line += '"'; + } else { + line += rstring; } - } else if (m_originalQ) { - if (ktype1 && otype2) { - convertMensurationToModern(one); - convertMensurationToRegular(two); - output = true; - } else if (otype1 && ktype2) { - convertMensurationToRegular(one); - convertMensurationToModern(two); - output = true; + } + + if (getBoolean("interval")) { + line += " -i "; + string istring = getString("interval"); + makeLowerCase(istring); + if ((istring.find(' ') != string::npos) || (istring.find('(') != string::npos)) { + line += '"'; + line += istring; + line += '"'; + } else { + line += istring; } } - return output; + + infile.appendLine(line); + + line = "!!@MATCHES:\t"; + line += to_string(mcount); + infile.appendLine(line); + + if (m_markQ) { + line = "!!@MARKER:\t"; + line += marker; + infile.appendLine(line); + } + + // Print music match location here. + for (int i=0; i<(int)m_matches.size(); i++) { + addMatch(infile, m_matches[i]); + } + + infile.appendLine("!!@@END: MUSIC_SEARCH_RESULT"); } ////////////////////////////// // -// Tool_modori::swapClefStyle -- Returns true if swapped. +// Tool_msearch::addMatch -- +// +// Todo: +// * add duration of match // -bool Tool_modori::swapClefStyle(HTp one, HTp two) { - bool mtype1 = false; - bool mtype2 = false; - bool otype1 = false; - bool otype2 = false; - bool ktype1 = false; - bool ktype2 = false; - bool output = false; - - if (one->isClef()) { - ktype1 = true; - } else if (one->isModernClef()) { - mtype1 = true; - } else if (one->isOriginalClef()) { - otype1 = true; +void Tool_msearch::addMatch(HumdrumFile& infile, vector& match) { + if (match.empty()) { + return; } - - if (two->isClef()) { - ktype2 = true; - } else if (two->isModernClef()) { - mtype2 = true; - } else if (two->isOriginalClef()) { - otype2 = true; + if (match.back() == NULL) { + // strange problem + return; } + int startIndex = match.at(0)->getLineIndex(); + int endIndex = match.back()->getLineIndex(); + int startMeasure = m_barnums.at(startIndex); + int endMeasure = m_barnums.at(endIndex); - if (m_modernQ) { - // Show the modern key signature. If one key is *mk and the - // other is *k then change *mk to *k and *k to *ok respectively. - if (ktype1 && mtype2) { - convertClefToOriginal(one); - convertClefToRegular(two); - output = true; - } else if (mtype1 && ktype2) { - convertClefToRegular(one); - convertClefToOriginal(two); - output = true; - } - } else if (m_originalQ) { - // Show the original key. If one key is *ok and the - // other is *k then change *ok to *k and *k to *mk respectively. - if (ktype1 && otype2) { - convertClefToModern(one); - convertClefToRegular(two); - output = true; - } else if (otype1 && ktype2) { - convertClefToRegular(one); - convertClefToModern(two); - output = true; - } + infile.appendLine("!!@@BEGIN:\tMATCH"); + + string measure = "!!@MEASURE: "; + + measure += to_string(startMeasure); + if (startMeasure != endMeasure) { + measure += " "; + measure += to_string(endMeasure); } - return output; + infile.appendLine(measure); + + infile.appendLine("!!@@END:\tMATCH"); } ////////////////////////////// // -// Tool_modori::convertKeySignatureToModern -- +// Tool_msearch::makeLowerCase -- // -void Tool_modori::convertKeySignatureToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?k(.*)")) { - string text = "*mk"; - text += hre.getMatch(1); - token->setText(text); +void Tool_msearch::makeLowerCase(string& inout) { + for (int i=0; i<(int)inout.size(); i++) { + inout[i] = tolower(inout[i]); } } @@ -100502,31 +103989,74 @@ void Tool_modori::convertKeySignatureToModern(HTp token) { ////////////////////////////// // -// Tool_modori::convertInstrumentNameToModern -- +// Tool_msearch::addTextSearchSummary -- // -void Tool_modori::convertInstrumentNameToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?I\"(.*)")) { - string text = "*mI\""; - text += hre.getMatch(1); - token->setText(text); +void Tool_msearch::addTextSearchSummary(HumdrumFile& infile, int mcount, const string& marker) { + infile.appendLine("!!@@BEGIN: TEXT_SEARCH_RESULT"); + string line; + + line = "!!@QUERY:\t"; + + if (getBoolean("text")) { + line += " -t "; + string tstring = getString("text"); + if (tstring.find(' ') != string::npos) { + line += '"'; + line += tstring; + line += '"'; + } else { + line += tstring; + } + } + + infile.appendLine(line); + + line = "!!@MATCHES:\t"; + line += to_string(mcount); + infile.appendLine(line); + + if (m_markQ) { + line = "!!@MARKER:\t"; + line += marker; + infile.appendLine(line); } + + // Print match location here. + infile.appendLine("!!@@END: TEXT_SEARCH_RESULT"); } ////////////////////////////// // -// Tool_modori::convertInstrumentAbbreviationToModern -- +// Tool_msearch::markNote -- // -void Tool_modori::convertInstrumentAbbreviationToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?I'(.*)")) { - string text = "*mI'"; - text += hre.getMatch(1); - token->setText(text); +void Tool_msearch::markNote(HTp token, int index) { + if (index < 0) { + return; + } + if (!token->isChord()) { + if (token->find(m_marker) == string::npos) { + string text = *token; + text += m_marker; + token->setText(text); + } + return; + } + vector subtoks = token->getSubtokens(); + if (index >= (int)subtoks.size()) { + return; + } + if (subtoks[index].find(m_marker) == string::npos) { + subtoks[index] += m_marker; + string output = subtoks[0]; + for (int i=1; i<(int)subtoks.size(); i++) { + output += " "; + output += subtoks[i]; + } + token->setText(output); } } @@ -100534,15 +104064,46 @@ void Tool_modori::convertInstrumentAbbreviationToModern(HTp token) { ////////////////////////////// // -// Tool_modori::convertKeySignatureToOriginal -- +// Tool_msearch::markMatch -- assumes monophonic music. // -void Tool_modori::convertKeySignatureToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?k(.*)")) { - string text = "*ok"; - text += hre.getMatch(1); - token->setText(text); +void Tool_msearch::markMatch(HumdrumFile& infile, vector& match) { + for (int i=0; i<(int)m_tomark.size(); i++) { + markNote(m_tomark[i].first, m_tomark[i].second); + } + if (match.empty()) { + return; + } + HTp mstart = match[0]->getToken(); + HTp mend = NULL; + if (match.back() != NULL) { + mend = match.back()->getToken(); + } else { + // there is an extra NULL token at the end of the music to allow + // marking tied notes. + } + HTp tok = mstart; + string text; + while (tok && (tok != mend)) { + if (!tok->isData()) { + tok = tok->getNextToken(); + continue; + } + if (tok->isNull()) { + tok = tok->getNextToken(); + continue; + } + if (tok->empty()) { + // skip marking null tokens + tok = tok->getNextToken(); + continue; + } + markNote(tok, 0); + tok = tok->getNextToken(); + if (tok && !tok->isKern()) { + cerr << "STRANGE LINKING WITH TEXT SPINE" << endl; + break; + } } } @@ -100550,15 +104111,57 @@ void Tool_modori::convertKeySignatureToOriginal(HTp token) { ////////////////////////////// // -// Tool_modori::convertInstrumentNameToOriginal -- +// Tool_msearch::markTextMatch -- assumes monophonic voices. // -void Tool_modori::convertInstrumentNameToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?I\"(.*)")) { - string text = "*oI\""; - text += hre.getMatch(1); - token->setText(text); +void Tool_msearch::markTextMatch(HumdrumFile& infile, TextInfo& word) { + HTp mstart = word.starttoken; + HTp mnext = word.nexttoken; + // while (mstart && !mstart->isKern()) { + // mstart = mstart->getPreviousFieldToken(); + // } + // HTp mend = word.nexttoken; + // while (mend && !mend->isKern()) { + // mend = mend->getPreviousFieldToken(); + // } + + if (mstart) { + if (!mstart->isData()) { + return; + } else if (mstart->isNull()) { + return; + } + } + + //if (mend) { + // if (!mend->isData()) { + // mend = NULL; + // } else if (mend->isNull()) { + // mend = NULL; + // } + //} + + HTp tok = mstart; + string text; + while (tok && (tok != mnext)) { + if (!tok->isData()) { + tok = tok->getNextToken(); + continue; + } + if (tok->isNull()) { + tok = tok->getNextToken(); + continue; + } + text = tok->getText(); + if ((!text.empty()) && (text.back() == '-')) { + text.pop_back(); + text += m_marker; + text += '-'; + } else { + text += m_marker; + } + tok->setText(text); + tok = tok->getNextToken(); } } @@ -100566,2163 +104169,2181 @@ void Tool_modori::convertInstrumentNameToOriginal(HTp token) { ////////////////////////////// // -// Tool_modori::convertInstrumentAbbreviationToOriginal -- +// Tool_msearch::checkForMusicMatch -- See if the given position +// in the music matches the query. // -void Tool_modori::convertInstrumentAbbreviationToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?I'(.*)")) { - string text = "*oI'"; - text += hre.getMatch(1); - token->setText(text); +bool Tool_msearch::checkForMusicMatch(vector& notes, int index, + vector& query, vector& match) { + + match.clear(); + int maxi = (int)notes.size() - index; + if ((int)query.size() > maxi) { + // Search would extend off of the end of the music, so cannot be a match. + match.clear(); + return false; } -} + int c = 0; + + for (int i=0; i<(int)query.size(); i++) { + int currindex = index + i - c; + int lastindex = index + i -c - 1; + int nextindex = index + i -c + 1; + if (nextindex >= (int)notes.size()) { + nextindex = -1; + } + + if (currindex < 0) { + cerr << "STRANGE NEGATIVE INDEX " << currindex << endl; + break; + } + + // If the query item can be anything, it automatically matches: + if (query[i].anything) { + match.push_back(notes[currindex]); + continue; + } + + ////////////////////////////// + // + // RHYTHM + // + + if (!query[i].anyrhythm) { + if (notes[currindex]->getDuration() != query[i].duration) { + match.clear(); + return false; + } + } + ////////////////////////////// + // + // INTERVALS + // -////////////////////////////// -// -// Tool_modori::convertKeySignatureToRegular -- -// + if (query[i].dinterval > -1000) { + // match to a specific diatonic interval to the next note -void Tool_modori::convertKeySignatureToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?k(.*)")) { - string text = "*k"; - text += hre.getMatch(1); - token->setText(text); - } -} + double currpitch; + double nextpitch; + currpitch = notes[currindex]->getAbsDiatonicPitch(); + if (nextindex >= 0) { + nextpitch = notes[nextindex]->getAbsDiatonicPitch(); + } else { + nextpitch = -123456789.0; + } -////////////////////////////// -// -// Tool_modori::convertInstrumentNameToRegular -- -// + // maybe be careful of rests getting into this calculation: + int interval = (int)(nextpitch - currpitch); -void Tool_modori::convertInstrumentNameToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?I\"(.*)")) { - string text = "*I\""; - text += hre.getMatch(1); - token->setText(text); - } -} + if (interval != query[i].dinterval) { + match.clear(); + return false; + } + } else if (query[i].cinterval > -1000) { + // match to a specific chromatic interval to the next note + double currpitch; + double nextpitch; + currpitch = notes[currindex]->getAbsBase40Pitch(); -////////////////////////////// -// -// Tool_modori::convertInstrumentAbbreviationToRegular -- -// + if (nextindex >= 0) { + nextpitch = notes[nextindex]->getAbsBase40Pitch(); + } else { + nextpitch = -123456789.0; + } -void Tool_modori::convertInstrumentAbbreviationToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?I'(.*)")) { - string text = "*I'"; - text += hre.getMatch(1); - token->setText(text); - } -} + // maybe be careful of rests getting into this calculation: + int interval = (int)(nextpitch - currpitch); + if (interval != query[i].cinterval) { + match.clear(); + return false; + } + } else if (!query[i].anyinterval) { -////////////////////////////// -// -// Tool_modori::convertClefToModern -- -// + double currpitch; + double nextpitch; + double lastpitch; -void Tool_modori::convertClefToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?clef(.*)")) { - string text = "*mclef"; - text += hre.getMatch(1); - token->setText(text); - } -} + currpitch = notes[currindex]->getAbsDiatonicPitchClass(); + if (nextindex >= 0) { + nextpitch = notes[nextindex]->getAbsDiatonicPitchClass(); + } else { + nextpitch = -123456789.0; + } + if (lastindex >= 0) { + lastpitch = notes[nextindex]->getAbsDiatonicPitchClass(); + } else { + lastpitch = -987654321.0; + } -////////////////////////////// -// -// Tool_modori::convertClefToOriginal -- -// + if (query[i].anypitch) { + // search forward interval + if (nextindex < 0) { + // Match can not go off the edge of the music. + match.clear(); + return false; + } else { + // check here if either note is a rest + if (notes[currindex]->isRest() || notes[nextindex]->isRest()) { + match.clear(); + return false; + } -void Tool_modori::convertClefToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?clef(.*)")) { - string text = "*oclef"; - text += hre.getMatch(1); - token->setText(text); - } -} + if (query[i].direction > 0) { + if (nextpitch - currpitch <= 0.0) { + match.clear(); + return false; + } + } if (query[i].direction < 0) { + if (nextpitch - currpitch >= 0.0) { + match.clear(); + return false; + } + } else if (query[i].direction == 0.0) { + if (nextpitch - currpitch != 0) { + match.clear(); + return false; + } + } + } + } else { + // search backward interval + if (lastindex < 0) { + // Match can not go off the edge of the music. + match.clear(); + return false; + } else { + // check here if either note is a rest. + if (notes[currindex]->isRest() || notes[nextindex]->isRest()) { + match.clear(); + return false; + } + if (query[i].direction > 0) { + if (lastpitch - currpitch <= 0.0) { + match.clear(); + return false; + } + } if (query[i].direction < 0) { + if (lastpitch - currpitch >= 0.0) { + match.clear(); + return false; + } + } else if (query[i].direction == 0.0) { + if (lastpitch - currpitch != 0) { + match.clear(); + return false; + } + } + } + } + } + ////////////////////////////// + // + // PITCH + // -////////////////////////////// -// -// Tool_modori::convertClefToRegular -- -// + if (!query[i].anypitch) { + double qpitch = query[i].pc; + double npitch = 0; + if (notes[currindex]->isRest()) { + if (Convert::isNaN(qpitch)) { + // both notes are rests, so they match + match.push_back(notes[currindex]); + continue; + } else { + // query is not a rest but test note is + match.clear(); + return false; + } + } else if (Convert::isNaN(qpitch)) { + // query is a rest but test note is not + match.clear(); + return false; + } -void Tool_modori::convertClefToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?clef(.*)")) { - string text = "*clef"; - text += hre.getMatch(1); - token->setText(text); - } -} + if (query[i].base == 40) { + npitch = notes[currindex]->getAbsBase40PitchClass(); + } else if (query[i].base == 12) { + npitch = ((int)notes[currindex]->getAbsMidiPitch()) % 12; + } else if (query[i].base == 7) { + npitch = ((int)notes[currindex]->getAbsDiatonicPitch()) % 7; + } else { + npitch = notes[currindex]->getAbsBase40PitchClass(); + } + if (qpitch != npitch) { + match.clear(); + return false; + } + } + if (!query[i].harmonic.empty()) { + query[i].parseHarmonicQuery(); + bool status = doHarmonicPitchSearch(query[i], notes[currindex]->getToken()); + if (!status) { + return false; + } + } -////////////////////////////// -// -// Tool_modori::convertMensurationToModern -- -// + // All requirements for the note were matched, so store note + // and continue to next note if needed. + match.push_back(notes[currindex]); + } -void Tool_modori::convertMensurationToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?met\\((.*)")) { - string text = "*mmet("; - text += hre.getMatch(1); - token->setText(text); + // Add extra token for marking tied notes at end of match + if (index + (int)query.size() < (int)notes.size()) { + match.push_back(notes[index + (int)query.size() - c]); + } else { + match.push_back(NULL); } + + return true; } ////////////////////////////// // -// Tool_modori::convertMensurationToOriginal -- +// Tool_msearch::doHarmonicPitchSearch -- // -void Tool_modori::convertMensurationToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?met\\((.*)")) { - string text = "*omet("; - text += hre.getMatch(1); - token->setText(text); +bool Tool_msearch::doHarmonicPitchSearch(MSearchQueryToken& query, HTp token) { + if (query.harmonic.empty()) { + return true; } -} - + int lindex = token->getLineIndex(); + if (m_verticalOnlyQ && m_sonoritiesChecked[lindex]) { + // Only count once if searching only for vertical sonoroties + // Later make this more efficient perhaps by not searching every + // note for vertical-only searches, but rather search + // the sonorities in one pass (but maybe this will not actually + // be more efficient). + return false; + } + m_sonoritiesChecked[lindex] = true; + SonorityDatabase& sonorities = m_sonorities[lindex]; + if (sonorities.isEmpty()) { + sonorities.buildDatabase(token->getLine()); + } -////////////////////////////// -// -// Tool_modori::convertMensurationToRegular -- -// + bool exactQ = false; + bool onlyQ = false; -void Tool_modori::convertMensurationToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?met\\((.*)")) { - string text = "*met("; - text += hre.getMatch(1); - token->setText(text); + if (query.harmonic.find("==") != string::npos) { + exactQ = true; + } else if (query.harmonic.find("=") != string::npos) { + onlyQ = true; } -} + vector diatonicCountsQuery(7, 0); + vector diatonicCountsMatch(7, 0); + vector diatonicCountsData(7, 0); + vector chromaticCountsQuery(40, 0); + vector chromaticCountsMatch(40, 0); + vector chromaticCountsData(40, 0); + for (int i=0; ifirst; - for (int j=0; j<(int)it->second.size(); ++j) { - m_humdrum_text << '\t' << it->second.at(j); - } - m_humdrum_text << endl; + // Don't check for same pitch-class twice: + if (diatonicCountsMatch.at(query.hquery[i].getBase7Pc())) { + continue; + } } - } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! CLEFS:" << endl; + int status = checkHarmonicPitchMatch(query.hquery[i], sonorities, false); - for (int t=1; t<(int)m_keys.size(); ++t) { - for (auto it = m_clefs.at(t).begin(); it != m_clefs.at(t).end(); ++it) { - m_humdrum_text << "!!\t" << it->first; - for (int j=0; j<(int)it->second.size(); ++j) { - m_humdrum_text << '\t' << it->second.at(j); - } - m_humdrum_text << endl; + if (!status) { + return false; } - } - - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! MENSURATIONS:" << endl; - for (int t=1; t<(int)m_mensurations.size(); ++t) { - for (auto it = m_mensurations.at(t).begin(); it != m_mensurations.at(t).end(); ++it) { - m_humdrum_text << "!!\t" << it->first; - for (int j=0; j<(int)it->second.size(); j++) { - m_humdrum_text << '\t' << it->second.at(j); - } - m_humdrum_text << endl; + if (query.hquery[i].hasAccidental()) { + chromaticCountsMatch.at(query.hquery[i].getBase40Pc()) += status; + } else { + diatonicCountsMatch.at(query.hquery[i].getBase7Pc()) += status; } - } + sum += status; - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! LYRICS:" << endl; + } - for (int i=0; i<(int)m_lyrics.size(); i++) { - HTp token = m_lyrics[i]; - m_humdrum_text << "!!\t"; - m_humdrum_text << token; - m_humdrum_text << endl; + if ((!exactQ) && (!onlyQ)) { + return true; } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! TEXT:" << endl; - for (int i=0; i<(int)m_lotext.size(); i++) { - m_humdrum_text << "!!\t" << m_lotext[i] << endl; + if (exactQ && (sum != sonorities.getNoteCount())) { + return false; } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! REFERENCES:" << endl; + if (exactQ) { + for (int i=0; i<(int)diatonicCountsMatch.size(); i++) { + if (diatonicCountsMatch[i] != diatonicCountsQuery[i]) { + return false; + } + } + for (int i=0; i<(int)chromaticCountsMatch.size(); i++) { + if (chromaticCountsMatch[i] != chromaticCountsQuery[i]) { + return false; + } + } + } else if (onlyQ) { + SonorityDatabase son2; + for (int i=0; i<(int)query.hpieces.size(); i++) { + son2.addNote(query.hpieces[i]); + } - for (int i=0; i<(int)m_references.size(); i++) { - m_humdrum_text << "!!\t" << m_references[i].first << endl; - m_humdrum_text << "!!\t" << m_references[i].second << endl; - m_humdrum_text << "!!\n"; + for (int k=0; kisData()) { - return; - } - int lowesti = 0; - int lowest12 = 1000; +int Tool_msearch::checkHarmonicPitchMatch(SonorityNoteData& query, + SonorityDatabase& sonorities, bool suppressQ) { + bool isChromatic = query.hasAccidental(); + bool isLowest = query.hasUpperCase(); - for (int i=0; igetFieldCount(); i++) { - HTp token = m_line->token(i); - if (!token->isKern()) { - continue; - } - if (token->isRest()) { - // ignoring rests, at least for now - continue; - } - if (token->isNull()) { - nullQ = true; - token = token->resolveNull(); - } - if (token->isNull()) { - continue; - } - int scount = token->getSubtokenCount(); - for (int j=0; j tomark; -////////////////////////////// -// -// SonorityDatabase::addNote -- -// + // this algorithm highlights all vertical sonorities of given pitch class. + int output = 0; + if (isChromatic) { + int cpitch = query.getBase40Pc(); + int cpc = cpitch % 40; + for (int i=0; i= 'a' && ch <= 'g') { - hpieces.resize(hpieces.size() + 1); - hpieces.back() += harmonic[i]; - } else if (ch == '-') { - hpieces.back() += ch; - } else if (ch == 'n') { - hpieces.back() += ch; - } else if (ch == '#') { - hpieces.back() += ch; - } - } +void Tool_msearch::fillTextQuery(vector& query, + const string& input) { + query.clear(); + bool inquote = false; - hquery.resize(hpieces.size()); - for (int i=0; i<(int)hpieces.size(); i++) { - hquery[i].setString(hpieces[i]); + query.resize(1); + + for (int i=0; i<(int)input.size(); i++) { + if (input[i] == '"') { + inquote = !inquote; + query.resize(query.size() + 1); + continue; + } + if (isspace(input[i])) { + query.resize(query.size() + 1); + } + query.back().word.push_back(input[i]); + if (inquote) { + query.back().link = true; + } } } -///////////////////////////////// +////////////////////////////// // -// Tool_msearch::Tool_msearch -- Set the recognized options for the tool. +// Tool_msearch::fillMusicQuery -- // -Tool_msearch::Tool_msearch(void) { - define("debug=b", "diatonic search"); - define("q|query=s:4c4d4e4f4g", "combined rhythm/pitch query string"); - define("p|pitch=s:cdefg", "pitch query string"); - define("i|interval=s:2222", "interval query string"); - define("r|d|rhythm|duration=s:44444", "rhythm query string"); - define("t|text=s:", "lyrical text query string"); - define("O|no-overlap=b", "do not allow matches to overlap"); - define("x|cross=b", "search across parts"); - define("c|color=s", "highlight color"); - define("m|mark|marker=s:@", "marking character"); - define("M|no-mark|no-marker=b", "do not mark matches"); - define("Q|quiet=b", "quiet mode: do not summarize matches"); -} - - +void Tool_msearch::fillMusicQuery(vector& query) { + query.clear(); -///////////////////////////////// -// -// Tool_msearch::run -- Do the main work of the tool. -// + string qinput; + string pinput; + string iinput; + string rinput; -bool Tool_msearch::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i query; - fillMusicQuery(query); - if (!query.empty()) { - doMusicSearch(infile, grid, query); + if (query.size() == 1) { + if (query[0].anything) { + query.clear(); } - } else { - vector query; - fillTextQuery(query, getString("text")); - doTextSearch(infile, grid, query); } - infile.createLinesFromTokens(); - m_humdrum_text << infile; - - return 1; } ////////////////////////////// // -// Tool_msearch::initialize -- +// Tool_msearch::fillMusicQueryPitch -- // -void Tool_msearch::initialize(void) { - m_marker = getString("marker"); - // only allowing a single character for now: - m_markQ = !getBoolean("no-marker"); - if (!m_markQ) { - m_marker.clear(); - } else if (!m_marker.empty()) { - m_marker = m_marker[0]; - } +void Tool_msearch::fillMusicQueryPitch(vector& query, + const string& input) { + fillMusicQueryInterleaved(query, input); } ////////////////////////////// // -// Tool_msearch::fillWords -- +// Tool_msearch::fillMusicQueryRhythm -- // -void Tool_msearch::fillWords(HumdrumFile& infile, vector& words) { - vector textspines; - infile.getSpineStartList(textspines, "**silbe"); - if (textspines.empty()) { - infile.getSpineStartList(textspines, "**text"); +void Tool_msearch::fillMusicQueryRhythm(vector& query, + const string& input) { + string output; + output.reserve(input.size() * 4); + + for (int i=0; i<(int)input.size(); i++) { + output += input[i]; + output += ' '; } - for (int i=0; i<(int)textspines.size(); i++) { - fillWordsForTrack(words, textspines[i]); + + // remove spaces to allow rhythms: + // 64 => 64 + // 32 => 32 + // 16 => 16 + for (int i=0; i<(int)output.size(); i++) { + if ((i > 1) && (output[i] == '6') && (output[i-1] == ' ') && (output[i-2] == '1')) { + output.erase(i-1, 1); + i--; + } + if ((i > 1) && (output[i] == '2') && (output[i-1] == ' ') && (output[i-2] == '3')) { + output.erase(i-1, 1); + i--; + } + if ((i > 1) && (output[i] == '4') && (output[i-1] == ' ') && (output[i-2] == '6')) { + output.erase(i-1, 1); + i--; + } + if ((i > 0) && (output[i] == '.')) { + output.erase(i-1, 1); + i--; + } } + + fillMusicQueryInterleaved(query, output, true); + } ////////////////////////////// // -// Tool_msearch::fillWordsForTrack -- +// Tool_msearch::convertPitchesToIntervals -- // -void Tool_msearch::fillWordsForTrack(vector& words, - HTp starttoken) { - HTp tok = starttoken->getNextToken(); - while (tok != NULL) { - if (tok->empty()) { - tok = tok->getNextToken(); - continue; - } - if (tok->isNull()) { - tok = tok->getNextToken(); - continue; +string Tool_msearch::convertPitchesToIntervals(const string& input) { + if (input.empty()) { + return ""; + } + for (int i=0; i<(int)input.size(); i++) { + if (isdigit(input[i])) { + return input; } - if (!tok->isData()) { - tok = tok->getNextToken(); - continue; + if (tolower(input[i] == 'r')) { + // not allowing rests for now + return input; } - if (tok->at(0) == '-') { - // append a syllable to the end of previous word - if (!words.empty()) { - words.back()->fullword += tok->substr(1, string::npos); - if (words.back()->fullword.back() == '-') { - words.back()->fullword.pop_back(); + } + vector pitches; + + for (int i=0; i<(int)input.size(); i++) { + char ch = tolower(input[i]); + if (ch >= 'a' && ch <= 'g') { + string val; + val += ch; + pitches.push_back(val); + if (i > 0) { + if (input[i-1] == '^') { + pitches.back().insert(0, "^"); } - } - tok = tok->getNextToken(); - continue; - } else { - // start a new word - TextInfo* temp = new TextInfo(); - temp->nexttoken = NULL; - if (!words.empty()) { - words.back()->nexttoken = tok; - } - temp->fullword = *tok; - if (!temp->fullword.empty()) { - if (temp->fullword.back() == '-') { - temp->fullword.pop_back(); + if (input[i-1] == 'v') { + pitches.back().insert(0, "v"); } } - temp->starttoken = tok; - words.push_back(temp); - tok = tok->getNextToken(); continue; } - } -} - - - -////////////////////////////// -// -// Tool_msearch::doTextSearch -- do a basic text search of all parts. -// - -void Tool_msearch::doTextSearch(HumdrumFile& infile, NoteGrid& grid, - vector& query) { - - vector words; - words.reserve(10000); - fillWords(infile, words); - int tcount = 0; - - HumRegex hre; - for (int i=0; i<(int)query.size(); i++) { - for (int j=0; j<(int)words.size(); j++) { - if (hre.search(words.at(j)->fullword, query.at(i).word, "i")) { - tcount++; - markTextMatch(infile, *words[j]); + if (!pitches.empty()) { + if (ch == 'n') { + pitches.back() += 'n'; + } else if (ch == '-') { + pitches.back() += '-'; + } else if (ch == '#') { + pitches.back() += '#'; } } } - string textinterp = "**text"; - vector interps; - infile.getSpineStartList(interps); - //int textcount = 0; - int silbecount = 0; - for (int i=0; i<(int)interps.size(); i++) { - //if (interps[i]->getText() == "**text") { - // textcount++; - //} - if (interps[i]->getText() == "**silbe") { - silbecount++; - } - } - if (silbecount > 0) { - // giving priority to **silbe content - textinterp = "**silbe"; + if (pitches.size() <= 1) { + return ""; } - if (tcount && m_markQ) { - string content = "!!!RDF"; - content += textinterp; - content += ": "; - content += m_marker; - content += " = marked text"; - if (getBoolean("color")) { - content += ", color=\"" + getString("color") + "\""; + vector chromatic(pitches.size(), false); + for (int i=0; i<(int)pitches.size(); i++) { + for (int j=(int)pitches[i].size()-1; j>0; j--) { + int ch = pitches[i][j]; + if ((ch == 'n') || (ch == '-') || (ch == '#')) { + chromatic[i] = true; + break; + } } - infile.appendLine(content); - infile.createLinesFromTokens(); } - for (int i=0; i<(int)words.size(); i++) { - delete words[i]; - words[i] = NULL; + string output; + int p1; + int p2; + int base40; + int base7; + int sign; + for (int i=0; i<(int)pitches.size() - 1; i++) { + if (chromatic[i] && chromatic[i+1]) { + p1 = Convert::kernToBase40(pitches[i]); + p2 = Convert::kernToBase40(pitches[i+1]); + base40 = p2 - p1; + sign = base40 < 0 ? -1 : +1; + if (sign < 0) { + base40 = -base40; + } + string value = ""; + if (sign < 0) { + value += "-"; + } + value += Convert::base40ToIntervalAbbr(base40); + output += value; + output += " "; + } else { + p1 = Convert::kernToBase7(pitches[i]); + p2 = Convert::kernToBase7(pitches[i+1]); + base7 = p2 - p1; + sign = base7 < 0 ? -1 : +1; + if (sign < 0) { + base7 = -base7; + } + string value = ""; + if (sign < 0) { + value += "-"; + } + value += to_string(base7 + 1); + output += value; + output += " "; + } } - if (!m_quietQ) { - addTextSearchSummary(infile, tcount, m_marker); + if (output.size() > 0) { + if (output.back() == ' ') { + output.resize((int)output.size() - 1); + } } -} - - -////////////////////////////// -// -// Tool_msearch::printQuery -- -// - -void Tool_msearch::printQuery(vector& query) { - for (int i=0; i<(int)query.size(); i++) { - cout << query[i]; - } + return output; } ////////////////////////////// // -// Tool_msearch::doMusicSearch -- do a basic melodic search of all parts. +// Tool_msearch::fillMusicQueryInterval -- // -void Tool_msearch::doMusicSearch(HumdrumFile& infile, NoteGrid& grid, - vector& query) { +void Tool_msearch::fillMusicQueryInterval(vector& query, + const string& input) { - m_matches.clear(); + string newinput = convertPitchesToIntervals(input); - if (m_debugQ) { - printQuery(query); - } + char ch; + int counter = 0; + MSearchQueryToken temp; + MSearchQueryToken *active = &temp; - vector> attacks; - attacks.resize(grid.getVoiceCount()); - for (int i=0; i 0) { + active = &query.at(counter); + } else { + // what is this for? } - vector match; - int mcount = 0; - for (int i=0; i<(int)attacks.size(); i++) { - for (int j=0; j<(int)attacks[i].size(); j++) { - m_tomark.clear(); - bool status = checkForMusicMatch(attacks[i], j, query, match); - if (!status) { - m_tomark.clear(); + int sign = 1; + string alteration; + for (int i=0; i<(int)newinput.size(); i++) { + ch = newinput[i]; + if (ch == ' ') { + // skip over spaces + continue; + } + if ((ch == 'P') || (ch == 'p')) { + alteration = "P"; + continue; + } + if ((ch == 'd') || (ch == 'D')) { + if ((!alteration.empty()) && (alteration[0] == 'd')) { + alteration += "d"; + } else { + alteration = "d"; } - if (status && !match.empty()) { - mcount++; - markMatch(infile, match); - storeMatch(match); - // cerr << "FOUND MATCH AT " << i << ", " << j << endl; - // markNotes(attacks[i], j, (int)query.size()); + continue; + } + if ((ch == 'A') || (ch == 'a')) { + if ((!alteration.empty()) && (alteration[0] == 'A')) { + alteration += "A"; + } else { + alteration = "A"; } + continue; } - } - - if (mcount && m_markQ) { - string content = "!!!RDF**kern: " + m_marker + " = marked note"; - if (getBoolean("color")) { - content += ", color=\"" + getString("color") + "\""; + if ((ch == 'M') || (ch == 'm')) { + alteration = ch; + continue; } - infile.appendLine(content); - infile.createLinesFromTokens(); - } - if (!m_quietQ) { - addMusicSearchSummary(infile, mcount, m_marker); - } -} - - - -////////////////////////////// -// -// Tool_msearch::addMusicSearchSummary -- -// - -void Tool_msearch::addMusicSearchSummary(HumdrumFile& infile, int mcount, const string& marker) { - - m_barnums = infile.getMeasureNumbers(); - - infile.appendLine("!!@@BEGIN: MUSIC_SEARCH_RESULT"); - string line; - - line = "!!@QUERY:\t"; + if (ch == '-') { + sign = -1; + continue; + } + if (ch == '+') { + sign = +1; + continue; + } + ch = tolower(ch); - if (getBoolean("query")) { - line += " -q "; - string qstring = getString("query"); - makeLowerCase(qstring); - if ((qstring.find(' ') != string::npos) || (qstring.find('(') != string::npos)) { - line += '"'; - line += qstring; - line += '"'; - } else { - line += qstring; + if (!isdigit(ch)) { + // skip over non-digits (sign of interval + // will be read retroactively). + continue; } - } - if (getBoolean("pitch")) { - line += " -p "; - string pstring = getString("pitch"); - makeLowerCase(pstring); - if ((pstring.find(' ') != string::npos) || (pstring.find('(') != string::npos)) { - line += '"'; - line += pstring; - line += '"'; - } else { - line += pstring; - } - } + // check for intervals. Intervals will trigger a + // new element in the query list - if (getBoolean("rhythm")) { - line += " -r "; - string rstring = getString("rhythm"); - makeLowerCase(rstring); - if ((rstring.find(' ') != string::npos) || (rstring.find('(') != string::npos)) { - line += '"'; - line += rstring; - line += '"'; + active->anything = false; + active->anyinterval = false; + // active->direction = 1; + + if (alteration.empty()) { + // store a diatonic interval + active->dinterval = (ch - '0') - 1; // zero-indexed interval + active->dinterval *= sign; } else { - line += rstring; + active->cinterval = makeBase40Interval((ch - '0') - 1, alteration); + active->cinterval *= sign; } - } + sign = 1; + alteration.clear(); - if (getBoolean("interval")) { - line += " -i "; - string istring = getString("interval"); - makeLowerCase(istring); - if ((istring.find(' ') != string::npos) || (istring.find('(') != string::npos)) { - line += '"'; - line += istring; - line += '"'; + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); } else { - line += istring; + active = &temp; } } - infile.appendLine(line); - - line = "!!@MATCHES:\t"; - line += to_string(mcount); - infile.appendLine(line); - - if (m_markQ) { - line = "!!@MARKER:\t"; - line += marker; - infile.appendLine(line); - } - - // Print music match location here. - for (int i=0; i<(int)m_matches.size(); i++) { - addMatch(infile, m_matches[i]); + // The last element in the interval search is set to + // any pitch, because the interval was already checked + // to the next note, and this value is needed to highlight + // the next note of the interval. + active->anything = true; + active->anyinterval = true; + if (active == &temp) { + query.push_back(temp); + temp.clear(); } - infile.appendLine("!!@@END: MUSIC_SEARCH_RESULT"); } ////////////////////////////// // -// Tool_msearch::addMatch -- -// -// Todo: -// * add duration of match +// Tool_msearch::makeBase40Interval -- // -void Tool_msearch::addMatch(HumdrumFile& infile, vector& match) { - if (match.empty()) { - return; +int Tool_msearch::makeBase40Interval(int diatonic, const string& alteration) { + int sign = 1; + if (diatonic < 0) { + sign = -1; + diatonic = -diatonic; } - if (match.back() == NULL) { - // strange problem - return; + bool perfectQ = false; + int base40 = 0; + switch (diatonic) { + case 0: // unison + base40 = 0; + perfectQ = true; + break; + case 1: // second + base40 = 6; + perfectQ = false; + break; + case 2: // third + base40 = 12; + perfectQ = false; + break; + case 3: // fourth + base40 = 17; + perfectQ = true; + break; + case 4: // fifth + base40 = 23; + perfectQ = true; + break; + case 5: // sixth + base40 = 29; + perfectQ = false; + break; + case 6: // seventh + base40 = 35; + perfectQ = false; + break; + case 7: // octave + base40 = 40; + perfectQ = true; + break; + case 8: // ninth + base40 = 46; + perfectQ = false; + break; + case 9: // tenth + base40 = 52; + perfectQ = false; + break; + default: + cerr << "cannot handle this interval yet. Setting to unison" << endl; + base40 = 0; + perfectQ = 1; } - int startIndex = match.at(0)->getLineIndex(); - int endIndex = match.back()->getLineIndex(); - int startMeasure = m_barnums.at(startIndex); - int endMeasure = m_barnums.at(endIndex); - - infile.appendLine("!!@@BEGIN:\tMATCH"); - - string measure = "!!@MEASURE: "; - measure += to_string(startMeasure); - if (startMeasure != endMeasure) { - measure += " "; - measure += to_string(endMeasure); + if (perfectQ) { + if (alteration == "P") { + // do nothing since the interval is already perfect + } else if ((!alteration.empty()) && (alteration[0] == 'd')) { + if (alteration.size() <= 2) { + base40 -= (int)alteration.size(); + } else { + cerr << "TOO MUCH DIMINISHED, IGNORING" << endl; + } + } else if ((!alteration.empty()) && (alteration[0] == 'A')) { + if (alteration.size() <= 2) { + base40 += (int)alteration.size(); + } else { + cerr << "TOO MUCH AUGMENTED, IGNORING" << endl; + } + } + } else { + if (alteration == "M") { + // do nothing since the interval is already major + } else if (alteration == "m") { + base40--; + } else if ((!alteration.empty()) && (alteration[0] == 'd')) { + if (alteration.size() <= 2) { + base40 -= (int)alteration.size() + 1; + } else { + cerr << "TOO MUCH DIMINISHED, IGNORING" << endl; + } + } else if ((!alteration.empty()) && (alteration[0] == 'A')) { + if (alteration.size() <= 2) { + base40 += (int)alteration.size(); + } else { + cerr << "TOO MUCH AUGMENTED, IGNORING" << endl; + } + } } - infile.appendLine(measure); - - infile.appendLine("!!@@END:\tMATCH"); + base40 *= sign; + return base40; } ////////////////////////////// // -// Tool_msearch::makeLowerCase -- +// Tool_msearch::fillMusicQueryInterleaved -- // -void Tool_msearch::makeLowerCase(string& inout) { - for (int i=0; i<(int)inout.size(); i++) { - inout[i] = tolower(inout[i]); - } -} +void Tool_msearch::fillMusicQueryInterleaved(vector& query, + const string& input, bool rhythmQ) { + string newinput = input; + char ch; + int counter = 0; + MSearchQueryToken temp; + MSearchQueryToken *active = &temp; + string paren; + if (query.size() > 0) { + active = &query.at(counter); + } else { + // what is this for? + } -////////////////////////////// -// -// Tool_msearch::addTextSearchSummary -- -// + for (int i=0; i<(int)newinput.size(); i++) { + paren.clear(); + ch = tolower(newinput[i]); + if (ch == '(') { + paren += ch; + newinput[i] = ' '; + // A harmonic search initiated + int j = i; + bool keepQ = true; + bool diatonicQ = false; + for (j=i+1; j<(int)newinput.size(); j++) { + char ch2 = tolower(newinput[j]); + if (ch2 == ')') { + paren += ch2; + newinput[j] = ' '; + break; + } + if (ch2 >= 'a' && ch2 <= 'g') { + if (diatonicQ) { + keepQ = false; + } else { + diatonicQ = true; + } + } + if (keepQ) { + paren += newinput[j]; + continue; + } else { + paren += newinput[j]; + newinput[j] = ' '; + } + } + if (!paren.empty()) { + active->harmonic = paren; + paren.clear(); + } + continue; + } -void Tool_msearch::addTextSearchSummary(HumdrumFile& infile, int mcount, const string& marker) { - infile.appendLine("!!@@BEGIN: TEXT_SEARCH_RESULT"); - string line; + if (ch == '=') { + continue; + } + if (ch == ' ') { + // skip over multiple spaces + if (i > 0) { + if (newinput[i-1] == ' ') { + continue; + } + } + } - line = "!!@QUERY:\t"; + if (ch == '^') { + active->anything = false; + active->anyinterval = false; + active->direction = -1; + continue; + } + if (ch == 'v') { + active->anything = false; + active->anyinterval = false; + active->direction = 1; + continue; + } - if (getBoolean("text")) { - line += " -t "; - string tstring = getString("text"); - if (tstring.find(' ') != string::npos) { - line += '"'; - line += tstring; - line += '"'; - } else { - line += tstring; + // process rhythm. This must go first then intervals then pitches + if (isdigit(ch) || (ch == '.')) { + active->anything = false; + active->anyrhythm = false; + active->rhythm += ch; + if (i < (int)newinput.size() - 1) { + if (newinput[i+1] == ' ') { + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + continue; + } + } else { + // this is the last charcter in the input string + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + } } - } - infile.appendLine(line); + // check for intervals. Intervals will trigger a + // new element in the query list + // A new type ^ or v will not increment the query list + // (and they will expect a pitch after them). + if (ch == '/') { + active->anything = false; + active->anyinterval = false; + active->direction = 1; + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + continue; + } else if (ch == '\\') { + active->anything = false; + active->anyinterval = false; + active->direction = -1; + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + continue; + } else if (ch == '=') { + active->anything = false; + active->anyinterval = false; + active->direction = 0; + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + continue; + } - line = "!!@MATCHES:\t"; - line += to_string(mcount); - infile.appendLine(line); + // check for actual pitches + if ((ch >= 'a' && ch <= 'g')) { + active->anything = false; + active->anypitch = false; + active->base = 7; + active->pc = (ch - 'a' + 5) % 7; + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + continue; + } else if (ch == 'r') { + active->anything = false; + active->anypitch = false; + active->base = 7; + active->pc = GRIDREST; + if (active == &temp) { + query.push_back(temp); + temp.clear(); + } + counter++; + if ((int)query.size() > counter) { + active = &query.at(counter); + } else { + active = &temp; + } + continue; + } - if (m_markQ) { - line = "!!@MARKER:\t"; - line += marker; - infile.appendLine(line); + // accidentals: + if ((!query.empty()) && (ch == 'n') && (!Convert::isNaN(query.back().pc))) { + query.back().base = 40; + query.back().pc = Convert::base7ToBase40((int)query.back().pc + 70) % 40; + } else if ((!query.empty()) && (ch == '#') && (!Convert::isNaN(query.back().pc))) { + query.back().base = 40; + query.back().pc = (Convert::base7ToBase40((int)query.back().pc + 70) + 1) % 40; + } else if ((!query.empty()) && (ch == '-') && (!Convert::isNaN(query.back().pc))) { + query.back().base = 40; + query.back().pc = (Convert::base7ToBase40((int)query.back().pc + 70) - 1) % 40; + } + // deal with double sharps and double flats here } - // Print match location here. - infile.appendLine("!!@@END: TEXT_SEARCH_RESULT"); -} - - - -////////////////////////////// -// -// Tool_msearch::markNote -- -// - -void Tool_msearch::markNote(HTp token, int index) { - if (index < 0) { - return; - } - if (!token->isChord()) { - if (token->find(m_marker) == string::npos) { - string text = *token; - text += m_marker; - token->setText(text); + // Convert rhythms to durations + for (int i=0; i<(int)query.size(); i++) { + if (query[i].anyrhythm) { + continue; } - return; - } - vector subtoks = token->getSubtokens(); - if (index >= (int)subtoks.size()) { - return; - } - if (subtoks[index].find(m_marker) == string::npos) { - subtoks[index] += m_marker; - string output = subtoks[0]; - for (int i=1; i<(int)subtoks.size(); i++) { - output += " "; - output += subtoks[i]; + if (query[i].rhythm.empty()) { + continue; } - token->setText(output); + query[i].duration = Convert::recipToDuration(query[i].rhythm); } + + // what is this for (end condition)? + //if ((!query.empty()) && (query[0].base <= 0)) { + // temp.clear(); + // temp.anything = true; + // query.insert(query.begin(), temp); + //} } ////////////////////////////// // -// Tool_msearch::markMatch -- assumes monophonic music. +// checkVerticalOnly -- // -void Tool_msearch::markMatch(HumdrumFile& infile, vector& match) { - for (int i=0; i<(int)m_tomark.size(); i++) { - markNote(m_tomark[i].first, m_tomark[i].second); +bool Tool_msearch::checkVerticalOnly(const string& input) { + if (input.empty()) { + return false; } - if (match.empty()) { - return; + if (input.size() < 2) { + return false; } - HTp mstart = match[0]->getToken(); - HTp mend = NULL; - if (match.back() != NULL) { - mend = match.back()->getToken(); - } else { - // there is an extra NULL token at the end of the music to allow - // marking tied notes. + if (input[0] != '(') { + return false; } - HTp tok = mstart; - string text; - while (tok && (tok != mend)) { - if (!tok->isData()) { - tok = tok->getNextToken(); - continue; - } - if (tok->isNull()) { - tok = tok->getNextToken(); - continue; - } - if (tok->empty()) { - // skip marking null tokens - tok = tok->getNextToken(); - continue; + if (input.back() != ')') { + return false; + } + for (int i=1; i<(int)input.size()-1; i++) { + // Maybe allow internal () if there is nothing outside of them. + if (input[i] == '(') { + return false; } - markNote(tok, 0); - tok = tok->getNextToken(); - if (tok && !tok->isKern()) { - cerr << "STRANGE LINKING WITH TEXT SPINE" << endl; - break; + if (input[i] == ')') { + return false; } } + return true; } ////////////////////////////// // -// Tool_msearch::markTextMatch -- assumes monophonic voices. +// Tool_msearch::storeMatch -- Store a search result for later printing +// in the input file footer. // -void Tool_msearch::markTextMatch(HumdrumFile& infile, TextInfo& word) { - HTp mstart = word.starttoken; - HTp mnext = word.nexttoken; - // while (mstart && !mstart->isKern()) { - // mstart = mstart->getPreviousFieldToken(); - // } - // HTp mend = word.nexttoken; - // while (mend && !mend->isKern()) { - // mend = mend->getPreviousFieldToken(); - // } - - if (mstart) { - if (!mstart->isData()) { - return; - } else if (mstart->isNull()) { - return; - } +void Tool_msearch::storeMatch(vector& match) { + m_matches.resize(m_matches.size() + 1); + m_matches.back().resize(match.size()); + for (int i=0; i<(int)match.size(); i++) { + m_matches.back().at(i) = match.at(i); } +} - //if (mend) { - // if (!mend->isData()) { - // mend = NULL; - // } else if (mend->isNull()) { - // mend = NULL; - // } - //} - HTp tok = mstart; - string text; - while (tok && (tok != mnext)) { - if (!tok->isData()) { - tok = tok->getNextToken(); - continue; - } - if (tok->isNull()) { - tok = tok->getNextToken(); - continue; - } - text = tok->getText(); - if ((!text.empty()) && (text.back() == '-')) { - text.pop_back(); - text += m_marker; - text += '-'; - } else { - text += m_marker; - } - tok->setText(text); - tok = tok->getNextToken(); + +////////////////////////////// +// +// operator<< -- print MSearchQueryToken item. +// + +ostream& operator<<(ostream& out, MSearchQueryToken& item) { + out << "ITEM: " << endl; + out << "\tANYTHING:\t" << item.anything << endl; + out << "\tANYPITCH:\t" << item.anypitch << endl; + out << "\tANYINTERVAL:\t" << item.anyinterval << endl; + out << "\tANYRHYTHM:\t" << item.anyrhythm << endl; + out << "\tPC:\t\t" << item.pc << endl; + out << "\tBASE:\t\t" << item.base << endl; + out << "\tDIRECTION:\t" << item.direction << endl; + out << "\tDINTERVAL:\t" << item.dinterval << endl; + out << "\tCINTERVAL:\t" << item.cinterval << endl; + out << "\tRHYTHM:\t\t" << item.rhythm << endl; + out << "\tDURATION:\t" << item.duration << endl; + if (!item.harmonic.empty()) { + out << "\tHARMONIC:\t" << item.harmonic << endl; } + return out; } ////////////////////////////// // -// Tool_msearch::checkForMusicMatch -- See if the given position -// in the music matches the query. +// Tool_musedata2hum::Tool_musedata2hum -- // -bool Tool_msearch::checkForMusicMatch(vector& notes, int index, - vector& query, vector& match) { +Tool_musedata2hum::Tool_musedata2hum(void) { + // Options& options = m_options; + // options.define("k|kern=b","display corresponding **kern data"); - match.clear(); - int maxi = (int)notes.size() - index; - if ((int)query.size() > maxi) { - // Search would extend off of the end of the music, so cannot be a match. - match.clear(); - return false; - } + define("g|group=s:score", "the data group to process"); + define("r|recip=b", "output **recip spine"); + define("s|stems=b", "include stems in output"); + define("omv|no-omv=b", "exclude extracted OMV record in output data"); +} - int c = 0; - for (int i=0; i<(int)query.size(); i++) { - int currindex = index + i - c; - int lastindex = index + i -c - 1; - int nextindex = index + i -c + 1; - if (nextindex >= (int)notes.size()) { - nextindex = -1; - } - if (currindex < 0) { - cerr << "STRANGE NEGATIVE INDEX " << currindex << endl; - break; - } +////////////////////////////// +// +// initialize -- +// - // If the query item can be anything, it automatically matches: - if (query[i].anything) { - match.push_back(notes[currindex]); - continue; - } +void Tool_musedata2hum::initialize(void) { + m_stemsQ = getBoolean("stems"); + m_recipQ = getBoolean("recip"); + m_group = getString("group"); + m_noOmvQ = getBoolean("no-omv"); +} - ////////////////////////////// - // - // RHYTHM - // - if (!query[i].anyrhythm) { - if (notes[currindex]->getDuration() != query[i].duration) { - match.clear(); - return false; - } - } - ////////////////////////////// - // - // INTERVALS - // +////////////////////////////// +// +// Tool_musedata2hum::setOptions -- +// - if (query[i].dinterval > -1000) { - // match to a specific diatonic interval to the next note +void Tool_musedata2hum::setOptions(int argc, char** argv) { + m_options.process(argc, argv); +} - double currpitch; - double nextpitch; - currpitch = notes[currindex]->getAbsDiatonicPitch(); +void Tool_musedata2hum::setOptions(const vector& argvlist) { + m_options.process(argvlist); +} - if (nextindex >= 0) { - nextpitch = notes[nextindex]->getAbsDiatonicPitch(); - } else { - nextpitch = -123456789.0; - } - // maybe be careful of rests getting into this calculation: - int interval = (int)(nextpitch - currpitch); - if (interval != query[i].dinterval) { - match.clear(); - return false; - } - } else if (query[i].cinterval > -1000) { - // match to a specific chromatic interval to the next note +////////////////////////////// +// +// Tool_musedata2hum::getOptionDefinitions -- Used to avoid +// duplicating the definitions in the test main() function. +// - double currpitch; - double nextpitch; +Options Tool_musedata2hum::getOptionDefinitions(void) { + return m_options; +} - currpitch = notes[currindex]->getAbsBase40Pitch(); - if (nextindex >= 0) { - nextpitch = notes[nextindex]->getAbsBase40Pitch(); - } else { - nextpitch = -123456789.0; - } - // maybe be careful of rests getting into this calculation: - int interval = (int)(nextpitch - currpitch); +////////////////////////////// +// +// Tool_musedata2hum::convert -- Convert a MusicXML file into +// Humdrum content. +// - if (interval != query[i].cinterval) { - match.clear(); - return false; - } +bool Tool_musedata2hum::convertFile(ostream& out, const string& filename) { + MuseDataSet mds; + int result = mds.readFile(filename); + if (!result) { + cerr << "\nMuseData file [" << filename << "] has syntax errors\n"; + cerr << "Error description:\t" << mds.getError() << "\n"; + exit(1); + } + return convert(out, mds); +} - } else if (!query[i].anyinterval) { - double currpitch; - double nextpitch; - double lastpitch; +bool Tool_musedata2hum::convert(ostream& out, istream& input) { + MuseDataSet mds; + mds.read(input); + return convert(out, mds); +} - currpitch = notes[currindex]->getAbsDiatonicPitchClass(); - if (nextindex >= 0) { - nextpitch = notes[nextindex]->getAbsDiatonicPitchClass(); - } else { - nextpitch = -123456789.0; - } +bool Tool_musedata2hum::convertString(ostream& out, const string& input) { + MuseDataSet mds; + int result = mds.readString(input); + if (!result) { + cout << "\nXML content has syntax errors\n"; + cout << "Error description:\t" << mds.getError() << "\n"; + exit(1); + } + return convert(out, mds); +} - if (lastindex >= 0) { - lastpitch = notes[nextindex]->getAbsDiatonicPitchClass(); - } else { - lastpitch = -987654321.0; - } - if (query[i].anypitch) { - // search forward interval - if (nextindex < 0) { - // Match can not go off the edge of the music. - match.clear(); - return false; - } else { - // check here if either note is a rest - if (notes[currindex]->isRest() || notes[nextindex]->isRest()) { - match.clear(); - return false; - } +bool Tool_musedata2hum::convert(ostream& out, MuseDataSet& mds) { + int partcount = mds.getFileCount(); + if (partcount == 0) { + cerr << "Error: No parts found in data:" << endl; + cerr << mds << endl; + return false; + } + initialize(); - if (query[i].direction > 0) { - if (nextpitch - currpitch <= 0.0) { - match.clear(); - return false; - } - } if (query[i].direction < 0) { - if (nextpitch - currpitch >= 0.0) { - match.clear(); - return false; - } - } else if (query[i].direction == 0.0) { - if (nextpitch - currpitch != 0) { - match.clear(); - return false; - } - } - } - } else { - // search backward interval - if (lastindex < 0) { - // Match can not go off the edge of the music. - match.clear(); - return false; - } else { - // check here if either note is a rest. - if (notes[currindex]->isRest() || notes[nextindex]->isRest()) { - match.clear(); - return false; - } + m_tempo = mds.getMidiTempo(); - if (query[i].direction > 0) { - if (lastpitch - currpitch <= 0.0) { - match.clear(); - return false; - } - } if (query[i].direction < 0) { - if (lastpitch - currpitch >= 0.0) { - match.clear(); - return false; - } - } else if (query[i].direction == 0.0) { - if (lastpitch - currpitch != 0) { - match.clear(); - return false; - } - } - } - } - } + vector groupMemberIndex = mds.getGroupIndexList(m_group); + if (groupMemberIndex.empty()) { + cerr << "Error: no files in the " << m_group << " membership." << endl; + return false; + } - ////////////////////////////// - // - // PITCH - // + HumGrid outdata; + bool status = true; + for (int i=0; i<(int)groupMemberIndex.size(); i++) { + status &= convertPart(outdata, mds, groupMemberIndex[i], i, (int)groupMemberIndex.size()); + } - if (!query[i].anypitch) { - double qpitch = query[i].pc; - double npitch = 0; - if (notes[currindex]->isRest()) { - if (Convert::isNaN(qpitch)) { - // both notes are rests, so they match - match.push_back(notes[currindex]); - continue; + HumdrumFile outfile; + outdata.transferTokens(outfile); + outfile.generateLinesFromTokens(); + stringstream sss; + sss << outfile; + outfile.readString(sss.str()); + + if (needsAboveBelowKernRdf()) { + outfile.appendLine("!!!RDF**kern: > = above"); + outfile.appendLine("!!!RDF**kern: < = above"); + } + + outfile.createLinesFromTokens(); + + Tool_trillspell trillspell; + trillspell.run(outfile); + + // Convert comments in header of first part: + int ii = groupMemberIndex[0]; + bool ending = false; + HumRegex hre; + for (int i=0; i< mds[ii].getLineCount(); i++) { + if (mds[ii][i].isAnyNote()) { + break; + } + if (mds[ii].getLine(i).compare(0, 2, "@@") == 0) { + string output = mds[ii].getLine(i); + if (output == "@@@") { + ending = true; + continue; + } + for (int j=0; j<(int)output.size(); j++) { + if (output[j] == '@') { + output[j] = '!'; } else { - // query is not a rest but test note is - match.clear(); - return false; + break; } - } else if (Convert::isNaN(qpitch)) { - // query is a rest but test note is not - match.clear(); - return false; } - - if (query[i].base == 40) { - npitch = notes[currindex]->getAbsBase40PitchClass(); - } else if (query[i].base == 12) { - npitch = ((int)notes[currindex]->getAbsMidiPitch()) % 12; - } else if (query[i].base == 7) { - npitch = ((int)notes[currindex]->getAbsDiatonicPitch()) % 7; - } else { - npitch = notes[currindex]->getAbsBase40PitchClass(); + if (hre.search(output, "!!!\\s*([^!:]+)\\s*:")) { + string key = hre.getMatch(1); + m_usedReferences[key] = true; } - - if (qpitch != npitch) { - match.clear(); - return false; + if (ending) { + m_postReferences.push_back(output); + } else { + out << output << endl; } } + } - if (!query[i].harmonic.empty()) { - query[i].parseHarmonicQuery(); - bool status = doHarmonicPitchSearch(query[i], notes[currindex]->getToken()); - if (!status) { - return false; - } + if (!m_usedReferences["COM"]) { + string composer = mds[ii].getComposer(); + if (!composer.empty()) { + out << "!!!COM: " << composer << endl; } - - // All requirements for the note were matched, so store note - // and continue to next note if needed. - match.push_back(notes[currindex]); } - // Add extra token for marking tied notes at end of match - if (index + (int)query.size() < (int)notes.size()) { - match.push_back(notes[index + (int)query.size() - c]); - } else { - match.push_back(NULL); + if (!m_usedReferences["CDT"]) { + string cdate = mds[ii].getComposerDate(); + if (!cdate.empty()) { + out << "!!!CDT: " << cdate << endl; + } } - return true; -} - - - -////////////////////////////// -// -// Tool_msearch::doHarmonicPitchSearch -- -// - -bool Tool_msearch::doHarmonicPitchSearch(MSearchQueryToken& query, HTp token) { - if (query.harmonic.empty()) { - return true; + if (!m_usedReferences["OTL"]) { + string worktitle = mds[ii].getWorkTitle(); + if (!worktitle.empty()) { + out << "!!!OTL: " << worktitle << endl; + } } - int lindex = token->getLineIndex(); - if (m_verticalOnlyQ && m_sonoritiesChecked[lindex]) { - // Only count once if searching only for vertical sonoroties - // Later make this more efficient perhaps by not searching every - // note for vertical-only searches, but rather search - // the sonorities in one pass (but maybe this will not actually - // be more efficient). - return false; - } - m_sonoritiesChecked[lindex] = true; - SonorityDatabase& sonorities = m_sonorities[lindex]; - if (sonorities.isEmpty()) { - sonorities.buildDatabase(token->getLine()); + if (!m_noOmvQ) { + if (!m_usedReferences["OMV"]) { + string movementtitle = mds[ii].getMovementTitle(); + if (!movementtitle.empty()) { + out << "!!!OMV: " << movementtitle << endl; + } + } } - bool exactQ = false; - bool onlyQ = false; - - if (query.harmonic.find("==") != string::npos) { - exactQ = true; - } else if (query.harmonic.find("=") != string::npos) { - onlyQ = true; + if (!m_usedReferences["OPS"]) { + string opus = mds[ii].getOpus(); + if (!opus.empty()) { + out << "!!!OPS: " << opus << endl; + } } - vector diatonicCountsQuery(7, 0); - vector diatonicCountsMatch(7, 0); - vector diatonicCountsData(7, 0); - vector chromaticCountsQuery(40, 0); - vector chromaticCountsMatch(40, 0); - vector chromaticCountsData(40, 0); + if (!m_usedReferences["ONM"]) { + string number = mds[ii].getNumber(); + if (!number.empty()) { + out << "!!!ONM: " << number << endl; + } + } - for (int i=0; i outputs; + for (int i=mds[lastone].getLineCount() - 1; i>=0; i--) { + if (mds[lastone][i].isAnyNote()) { + break; } - for (int i=0; i<(int)chromaticCountsMatch.size(); i++) { - if (chromaticCountsMatch[i] != chromaticCountsQuery[i]) { - return false; + if (mds[lastone].getLine(i).compare(0, 2, "@@") == 0) { + string output = mds[lastone].getLine(i); + for (int j=0; j<(int)output.size(); j++) { + if (output[j] == '@') { + output[j] = '!'; + } else { + break; + } } + outputs.push_back(output); } - } else if (onlyQ) { - SonorityDatabase son2; - for (int i=0; i<(int)query.hpieces.size(); i++) { - son2.addNote(query.hpieces[i]); - } + } - for (int k=0; k=0; i--) { + out << outputs[i] << endl; } - return true; + return status; } ////////////////////////////// // -// Tool_msearch::checkHarmonicPitchMatch -- Returns the number of matched notes. +// Tool_musedata2hum::printLine -- Print line of Humdrum file +// contents. If there is any layout parameter in the line tokens, +// then print an extra line with these. Currently only checking for +// a single parameter. // -int Tool_msearch::checkHarmonicPitchMatch(SonorityNoteData& query, - SonorityDatabase& sonorities, bool suppressQ) { - bool isChromatic = query.hasAccidental(); - bool isLowest = query.hasUpperCase(); - - if (isLowest) { - if (isChromatic) { - int cpc = query.getBase40Pc(); - if (cpc != sonorities.getLowest().getBase40Pc()) { - return 0; - } - } else { - int dpc = query.getBase7Pc(); - if (dpc != sonorities.getLowest().getBase7Pc()) { - return 0; - } +void Tool_musedata2hum::printLine(ostream& out, HumdrumLine& line) { + vector lo(line.getFieldCount()); + int count = 0; + for (int i=0; igetValue("auto", "LO"); + if (!value.empty()) { + lo.at(i) = value; + count++; } } - - pair tomark; - - // this algorithm highlights all vertical sonorities of given pitch class. - int output = 0; - if (isChromatic) { - int cpitch = query.getBase40Pc(); - int cpc = cpitch % 40; - for (int i=0; i 0) { + for (int i=0; i<(int)lo.size(); i++) { + if (lo[i].empty()) { + out << "!"; + } else { + out << lo[i]; } - } - } else { - int dpitch = query.getBase7Pc(); - int dpc = dpitch % 7; - for (int i=0; i& query, - const string& input) { - query.clear(); - bool inquote = false; - - query.resize(1); +bool Tool_musedata2hum::convertPart(HumGrid& outdata, MuseDataSet& mds, int index, int partindex, int maxstaff) { + MuseData& part = mds[index]; + m_lastfigure = NULL; + m_lastnote = NULL; + m_lastbarnum = -1; + m_part = partindex; + // maybe maxpart? + m_maxstaff = maxstaff; - for (int i=0; i<(int)input.size(); i++) { - if (input[i] == '"') { - inquote = !inquote; - query.resize(query.size() + 1); - continue; - } - if (isspace(input[i])) { - query.resize(query.size() + 1); - } - query.back().word.push_back(input[i]); - if (inquote) { - query.back().link = true; - } + bool status = true; + int i = 0; + while (i < part.getLineCount()) { + m_measureLineIndex = i; + i = convertMeasure(outdata, part, partindex, i); } + + storePartName(outdata, part, partindex); + + return status; } -////////////////////////////// +/////////////////////////////// // -// Tool_msearch::fillMusicQuery -- +// Tool_musedata2hum::storePartName -- // -void Tool_msearch::fillMusicQuery(vector& query) { - query.clear(); - - string qinput; - string pinput; - string iinput; - string rinput; - - if (getBoolean("query")) { - qinput = getString("query"); +void Tool_musedata2hum::storePartName(HumGrid& outdata, MuseData& part, int index) { + string name = part.getPartName(); + if (!name.empty()) { + outdata.setPartName(index, name); } +} - if (getBoolean("pitch")) { - pinput = getString("pitch"); - m_verticalOnlyQ = checkVerticalOnly(pinput); - } - if (getBoolean("interval")) { - iinput = getString("interval"); - } - if (getBoolean("rhythm")) { - rinput = getString("rhythm"); - } +////////////////////////////// +// +// Tool_musedata2hum::convertMeasure -- +// - if (!rinput.empty()) { - fillMusicQueryRhythm(query, rinput); +int Tool_musedata2hum::convertMeasure(HumGrid& outdata, MuseData& part, int partindex, int startindex) { + if (part.getLineCount() == 0) { + return 1; } - - if (!qinput.empty()) { - fillMusicQueryInterleaved(query, qinput); + HumNum starttime = part[startindex].getAbsBeat(); + HumNum filedur = part.getFileDuration(); + HumNum diff = filedur - starttime; + if (diff == 0) { + // last barline in score, so ignore + return startindex + 1;; } - if (!pinput.empty()) { - fillMusicQueryPitch(query, pinput); + GridMeasure* gm = getMeasure(outdata, starttime); + int i = startindex; + for (i=startindex; i= part.getLineCount()) { + endtime = part[i-1].getAbsBeat(); + } else { + endtime = part[i].getAbsBeat(); } - if (query.size() == 1) { - if (query[0].anything) { - query.clear(); + // set duration of measures (so it will be printed in conversion to Humdrum): + gm->setDuration(endtime - starttime); + gm->setTimestamp(starttime); + gm->setTimeSigDur(m_timesigdur); + + if ((i < part.getLineCount()) && part[i].isBarline()) { + if (partindex == 0) { + // For now setting the barline style from the + // lowest staff. This is mostly because + // MEI/verovio can handle only one style + // on a system barline. But also because + // GridMeasure objects only has a setting + // for a single barline style. + setMeasureStyle(outdata.back(), part[i]); + setMeasureNumber(outdata.back(), part[i]); + // gm->setBarStyle(MeasureStyle::Plain); } } + return i; } ////////////////////////////// // -// Tool_msearch::fillMusicQueryPitch -- +// Tool_musedata2hum::setMeasureNumber -- // -void Tool_msearch::fillMusicQueryPitch(vector& query, - const string& input) { - fillMusicQueryInterleaved(query, input); +void Tool_musedata2hum::setMeasureNumber(GridMeasure* gm, MuseRecord& mr) { + int pos = -1; + string line = mr.getLine(); + bool space = false; + for (int i=0; i<(int)line.size(); i++) { + if (isspace(line[i])) { + space = true; + continue; + } + if (!space) { + continue; + } + if (isdigit(line[i])) { + pos = i; + break; + } + } + if (pos < 0) { + gm->setMeasureNumber(-1); + return; + } + int num = stoi(line.substr(pos)); + if (m_lastbarnum >= 0) { + int temp = num; + num = m_lastbarnum; + m_lastbarnum = temp; + } + gm->setMeasureNumber(num); } ////////////////////////////// // -// Tool_msearch::fillMusicQueryRhythm -- +// Tool_musedata2hum::setMeasureStyle -- // -void Tool_msearch::fillMusicQueryRhythm(vector& query, - const string& input) { - string output; - output.reserve(input.size() * 4); - - for (int i=0; i<(int)input.size(); i++) { - output += input[i]; - output += ' '; - } - - // remove spaces to allow rhythms: - // 64 => 64 - // 32 => 32 - // 16 => 16 - for (int i=0; i<(int)output.size(); i++) { - if ((i > 1) && (output[i] == '6') && (output[i-1] == ' ') && (output[i-2] == '1')) { - output.erase(i-1, 1); - i--; - } - if ((i > 1) && (output[i] == '2') && (output[i-1] == ' ') && (output[i-2] == '3')) { - output.erase(i-1, 1); - i--; +void Tool_musedata2hum::setMeasureStyle(GridMeasure* gm, MuseRecord& mr) { + string line = mr.getLine(); + string barstyle = mr.getMeasureFlags(); + if (line.compare(0, 7, "mheavy2") == 0) { + if (barstyle.find(":|") != string::npos) { + gm->setStyle(MeasureStyle::RepeatBackward); + } else { + gm->setStyle(MeasureStyle::Final); } - if ((i > 1) && (output[i] == '4') && (output[i-1] == ' ') && (output[i-2] == '6')) { - output.erase(i-1, 1); - i--; + } else if (line.compare(0, 7, "mheavy3") == 0) { + if (barstyle.find("|:") != string::npos) { + gm->setStyle(MeasureStyle::RepeatForward); } - if ((i > 0) && (output[i] == '.')) { - output.erase(i-1, 1); - i--; + } else if (line.compare(0, 7, "mheavy4") == 0) { + if (barstyle.find(":|:") != string::npos) { + gm->setStyle(MeasureStyle::RepeatBoth); + } else if (barstyle.find("|: :|") != string::npos) { + // Vivaldi op. 1, no. 1, mvmt. 1, m. 10: mheavy4 |: :| + gm->setStyle(MeasureStyle::RepeatBoth); } + } else if (line.compare(0, 7, "mdouble") == 0) { + gm->setStyle(MeasureStyle::Double); } - - fillMusicQueryInterleaved(query, output, true); - } - ////////////////////////////// // -// Tool_msearch::convertPitchesToIntervals -- +// Tool_musedata2hum::convertLine -- // -string Tool_msearch::convertPitchesToIntervals(const string& input) { - if (input.empty()) { - return ""; +void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { + int part = m_part; + int staff = 0; + int maxstaff = m_maxstaff; + int layer = mr.getLayer(); + if (layer > 0) { + // convert to an index: + layer = layer - 1; } - for (int i=0; i<(int)input.size(); i++) { - if (isdigit(input[i])) { - return input; - } - if (tolower(input[i] == 'r')) { - // not allowing rests for now - return input; - } + + if (mr.isAnyNoteOrRest()) { + m_figureOffset = 0; } - vector pitches; - for (int i=0; i<(int)input.size(); i++) { - char ch = tolower(input[i]); - if (ch >= 'a' && ch <= 'g') { - string val; - val += ch; - pitches.push_back(val); - if (i > 0) { - if (input[i-1] == '^') { - pitches.back().insert(0, "^"); - } - if (input[i-1] == 'v') { - pitches.back().insert(0, "v"); - } - } - continue; - } - if (!pitches.empty()) { - if (ch == 'n') { - pitches.back() += 'n'; - } else if (ch == '-') { - pitches.back() += '-'; - } else if (ch == '#') { - pitches.back() += '#'; + if (mr.isDirection()) { + return; + } + + HumNum timestamp = mr.getAbsBeat(); + // cerr << "CONVERTING LINE " << timestamp << "\t" << mr << endl; + string tok; + GridSlice* slice = NULL; + + if (mr.isBarline()) { + // barline handled elsewhere + // tok = mr.getKernMeasure(); + } else if (mr.isAttributes()) { + map attributes; + mr.getAttributeMap(attributes); + + string mtempo = cleanString(attributes["D"]); + if (!mtempo.empty()) { + if (timestamp != 0) { + string value = "!!!OMD: " + mtempo; + gm->addGlobalComment(value, timestamp); + } else { + setInitialOmd(mtempo); } } - } - if (pitches.size() <= 1) { - return ""; - } + if (!attributes["Q"].empty()) { + m_quarterDivisions = std::stoi(attributes["Q"]); + } - vector chromatic(pitches.size(), false); - for (int i=0; i<(int)pitches.size(); i++) { - for (int j=(int)pitches[i].size()-1; j>0; j--) { - int ch = pitches[i][j]; - if ((ch == 'n') || (ch == '-') || (ch == '#')) { - chromatic[i] = true; - break; + string mclef = attributes["C"]; + if (!mclef.empty()) { + string kclef = Convert::museClefToKernClef(mclef); + if (!kclef.empty()) { + gm->addClefToken(kclef, timestamp, part, staff, layer, maxstaff); } } - } - string output; - int p1; - int p2; - int base40; - int base7; - int sign; - for (int i=0; i<(int)pitches.size() - 1; i++) { - if (chromatic[i] && chromatic[i+1]) { - p1 = Convert::kernToBase40(pitches[i]); - p2 = Convert::kernToBase40(pitches[i+1]); - base40 = p2 - p1; - sign = base40 < 0 ? -1 : +1; - if (sign < 0) { - base40 = -base40; + string mkeysig = attributes["K"]; + if (!mkeysig.empty()) { + string kkeysig = Convert::museKeySigToKernKeySig(mkeysig); + gm->addKeySigToken(kkeysig, timestamp, part, staff, layer, maxstaff); + } + + string mtimesig = attributes["T"]; + if (!mtimesig.empty()) { + string ktimesig = Convert::museTimeSigToKernTimeSig(mtimesig); + slice = gm->addTimeSigToken(ktimesig, timestamp, part, staff, layer, maxstaff); + setTimeSigDurInfo(ktimesig); + string kmeter = Convert::museMeterSigToKernMeterSig(mtimesig); + if (!kmeter.empty()) { + slice = gm->addMeterSigToken(kmeter, timestamp, part, staff, layer, maxstaff); + } + if (m_tempo > 0.00) { + int value = (int)(m_tempo + 0.5); + string tempotok = "*MM" + to_string(value); + slice = gm->addTempoToken(tempotok, timestamp, part, staff, layer, maxstaff); } - string value = ""; - if (sign < 0) { - value += "-"; + } + } else if (mr.isRegularNote()) { + tok = mr.getKernNoteStyle(1, 1); + string other = mr.getKernNoteOtherNotations(); + if (!needsAboveBelowKernRdf()) { + if (other.find("<") != string::npos) { + addAboveBelowKernRdf(); + } else if (other.find(">") != string::npos) { + addAboveBelowKernRdf(); } - value += Convert::base40ToIntervalAbbr(base40); - output += value; - output += " "; - } else { - p1 = Convert::kernToBase7(pitches[i]); - p2 = Convert::kernToBase7(pitches[i+1]); - base7 = p2 - p1; - sign = base7 < 0 ? -1 : +1; - if (sign < 0) { - base7 = -base7; + } + if (!other.empty()) { + tok += other; + } + slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff); + if (slice) { + mr.setVoice(slice->at(part)->at(staff)->at(layer)); + string gr = mr.getLayoutVis(); + if (gr.size() > 0) { + // Visual and performance durations are not equal: + HTp token = slice->at(part)->at(staff)->at(layer)->getToken(); + string text = "!LO:N:vis=" + gr; + token->setValue("auto", "LO", text); } - string value = ""; - if (sign < 0) { - value += "-"; + } + m_lastnote = slice->at(part)->at(staff)->at(layer)->getToken(); + addNoteDynamics(slice, part, mr); + addDirectionDynamics(slice, part, mr); + addLyrics(slice, part, staff, mr); + } else if (mr.isFiguredHarmony()) { + addFiguredHarmony(mr, gm, timestamp, part, maxstaff); + } else if (mr.isChordNote()) { + tok = mr.getKernNoteStyle(1, 1); + if (m_lastnote) { + string text = m_lastnote->getText(); + text += " "; + text += tok; + m_lastnote->setText(text); + } else { + cerr << "Warning: found chord note with no regular note to attach to" << endl; + } + } else if (mr.isCueNote()) { + cerr << "PROCESS CUE NOTE HERE: " << mr << endl; + } else if (mr.isGraceNote()) { + cerr << "PROCESS GRACE NOTE HERE: " << mr << endl; + } else if (mr.isChordGraceNote()) { + cerr << "PROCESS GRACE CHORD NOTE HERE: " << mr << endl; + } else if (mr.isAnyRest()) { + tok = mr.getKernRestStyle(); + slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff); + if (slice) { + mr.setVoice(slice->at(part)->at(staff)->at(layer)); + string gr = mr.getLayoutVis(); + if (gr.size() > 0) { + cerr << "GRAPHIC VERSION OF NOTEB " << gr << endl; } - value += to_string(base7 + 1); - output += value; - output += " "; } - } - - if (output.size() > 0) { - if (output.back() == ' ') { - output.resize((int)output.size() - 1); + } else if (mr.isDirection()) { + if (mr.isTextDirection()) { + addTextDirection(gm, part, staff, mr, timestamp); } } - - return output; } - ////////////////////////////// // -// Tool_msearch::fillMusicQueryInterval -- +// Tool_musedata2hum::addDirectionDynamics -- search for a dynamic +// marking before the current line and after any previous note +// or similar line. These lines are store in "musical directions" +// which start the line with a "*" character. +// +// Example for "p" dyamic, with print suggesting. +// 1 2 +// 12345678901234567890123456789 +// * G p +// P C17:Y57 // -void Tool_msearch::fillMusicQueryInterval(vector& query, - const string& input) { - - string newinput = convertPitchesToIntervals(input); - - char ch; - int counter = 0; - MSearchQueryToken temp; - MSearchQueryToken *active = &temp; - - if (query.size() > 0) { - active = &query.at(counter); - } else { - // what is this for? +void Tool_musedata2hum::addDirectionDynamics(GridSlice* slice, int part, MuseRecord& mr) { + MuseRecord* direction = mr.getMusicalDirection(); + if (direction == NULL) { + return; } - int sign = 1; - string alteration; - for (int i=0; i<(int)newinput.size(); i++) { - ch = newinput[i]; - if (ch == ' ') { - // skip over spaces - continue; - } - if ((ch == 'P') || (ch == 'p')) { - alteration = "P"; - continue; - } - if ((ch == 'd') || (ch == 'D')) { - if ((!alteration.empty()) && (alteration[0] == 'd')) { - alteration += "d"; - } else { - alteration = "d"; - } - continue; - } - if ((ch == 'A') || (ch == 'a')) { - if ((!alteration.empty()) && (alteration[0] == 'A')) { - alteration += "A"; - } else { - alteration = "A"; + if (direction->isDynamic()) { + string dynamicText = direction->getDynamicText(); + if (!dynamicText.empty()) { + slice->at(part)->setDynamics(dynamicText); + HumGrid* grid = slice->getOwner(); + if (grid) { + grid->setDynamicsPresent(part); } - continue; - } - if ((ch == 'M') || (ch == 'm')) { - alteration = ch; - continue; - } - if (ch == '-') { - sign = -1; - continue; - } - if (ch == '+') { - sign = +1; - continue; - } - ch = tolower(ch); - - if (!isdigit(ch)) { - // skip over non-digits (sign of interval - // will be read retroactively). - continue; } + } +} - // check for intervals. Intervals will trigger a - // new element in the query list - - active->anything = false; - active->anyinterval = false; - // active->direction = 1; - - if (alteration.empty()) { - // store a diatonic interval - active->dinterval = (ch - '0') - 1; // zero-indexed interval - active->dinterval *= sign; - } else { - active->cinterval = makeBase40Interval((ch - '0') - 1, alteration); - active->cinterval *= sign; - } - sign = 1; - alteration.clear(); - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); - } else { - active = &temp; - } - } - // The last element in the interval search is set to - // any pitch, because the interval was already checked - // to the next note, and this value is needed to highlight - // the next note of the interval. - active->anything = true; - active->anyinterval = true; - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } +////////////////////////////// +// +// Tool_musedata2hum::addAboveBelowKernRdf -- Save for later that +// !!!RDF**kern: > = above +// !!!RDF**kern: < = below +// in the output Humdrum data file. +// +void Tool_musedata2hum::addAboveBelowKernRdf(void) { + m_aboveBelowKernRdf = true; } ////////////////////////////// // -// Tool_msearch::makeBase40Interval -- +// Tool_musedata2hum::needsAboveBelowKernRdf -- Function name says it all. // -int Tool_msearch::makeBase40Interval(int diatonic, const string& alteration) { - int sign = 1; - if (diatonic < 0) { - sign = -1; - diatonic = -diatonic; - } - bool perfectQ = false; - int base40 = 0; - switch (diatonic) { - case 0: // unison - base40 = 0; - perfectQ = true; - break; - case 1: // second - base40 = 6; - perfectQ = false; - break; - case 2: // third - base40 = 12; - perfectQ = false; - break; - case 3: // fourth - base40 = 17; - perfectQ = true; - break; - case 4: // fifth - base40 = 23; - perfectQ = true; - break; - case 5: // sixth - base40 = 29; - perfectQ = false; - break; - case 6: // seventh - base40 = 35; - perfectQ = false; - break; - case 7: // octave - base40 = 40; - perfectQ = true; - break; - case 8: // ninth - base40 = 46; - perfectQ = false; - break; - case 9: // tenth - base40 = 52; - perfectQ = false; - break; - default: - cerr << "cannot handle this interval yet. Setting to unison" << endl; - base40 = 0; - perfectQ = 1; - } - - if (perfectQ) { - if (alteration == "P") { - // do nothing since the interval is already perfect - } else if ((!alteration.empty()) && (alteration[0] == 'd')) { - if (alteration.size() <= 2) { - base40 -= (int)alteration.size(); - } else { - cerr << "TOO MUCH DIMINISHED, IGNORING" << endl; - } - } else if ((!alteration.empty()) && (alteration[0] == 'A')) { - if (alteration.size() <= 2) { - base40 += (int)alteration.size(); - } else { - cerr << "TOO MUCH AUGMENTED, IGNORING" << endl; - } - } - } else { - if (alteration == "M") { - // do nothing since the interval is already major - } else if (alteration == "m") { - base40--; - } else if ((!alteration.empty()) && (alteration[0] == 'd')) { - if (alteration.size() <= 2) { - base40 -= (int)alteration.size() + 1; - } else { - cerr << "TOO MUCH DIMINISHED, IGNORING" << endl; - } - } else if ((!alteration.empty()) && (alteration[0] == 'A')) { - if (alteration.size() <= 2) { - base40 += (int)alteration.size(); - } else { - cerr << "TOO MUCH AUGMENTED, IGNORING" << endl; - } - } - } - base40 *= sign; - return base40; +bool Tool_musedata2hum::needsAboveBelowKernRdf(void) { + return m_aboveBelowKernRdf; } ////////////////////////////// // -// Tool_msearch::fillMusicQueryInterleaved -- +// Tool_musedata2hum::addTextDirection -- // -void Tool_msearch::fillMusicQueryInterleaved(vector& query, - const string& input, bool rhythmQ) { - - string newinput = input; - char ch; - int counter = 0; - MSearchQueryToken temp; - MSearchQueryToken *active = &temp; - string paren; +void Tool_musedata2hum::addTextDirection(GridMeasure* gm, int part, int staff, + MuseRecord& mr, HumNum timestamp) { - if (query.size() > 0) { - active = &query.at(counter); - } else { - // what is this for? + if (!mr.isTextDirection()) { + return; + } + string text = mr.getTextDirection(); + if (text == "") { + // no text direction to process + return; } + HumRegex hre; + hre.replaceDestructive(text, ":", ":", "g"); + string output = "!LO:TX"; + output += ":b"; // text below (figure out above cases) + output += ":t="; + output += text; - for (int i=0; i<(int)newinput.size(); i++) { - paren.clear(); - ch = tolower(newinput[i]); - if (ch == '(') { - paren += ch; - newinput[i] = ' '; - // A harmonic search initiated - int j = i; - bool keepQ = true; - bool diatonicQ = false; - for (j=i+1; j<(int)newinput.size(); j++) { - char ch2 = tolower(newinput[j]); - if (ch2 == ')') { - paren += ch2; - newinput[j] = ' '; - break; - } - if (ch2 >= 'a' && ch2 <= 'g') { - if (diatonicQ) { - keepQ = false; - } else { - diatonicQ = true; - } - } - if (keepQ) { - paren += newinput[j]; - continue; - } else { - paren += newinput[j]; - newinput[j] = ' '; - } - } - if (!paren.empty()) { - active->harmonic = paren; - paren.clear(); - } - continue; - } + // add staff index later + gm->addLayoutParameter(NULL, part, output); - if (ch == '=') { - continue; - } - if (ch == ' ') { - // skip over multiple spaces - if (i > 0) { - if (newinput[i-1] == ' ') { - continue; - } - } - } - if (ch == '^') { - active->anything = false; - active->anyinterval = false; - active->direction = -1; - continue; - } - if (ch == 'v') { - active->anything = false; - active->anyinterval = false; - active->direction = 1; - continue; - } +} - // process rhythm. This must go first then intervals then pitches - if (isdigit(ch) || (ch == '.')) { - active->anything = false; - active->anyrhythm = false; - active->rhythm += ch; - if (i < (int)newinput.size() - 1) { - if (newinput[i+1] == ' ') { - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); - } else { - active = &temp; - } - continue; - } - } else { - // this is the last charcter in the input string - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); - } else { - active = &temp; - } - } + +////////////////////////////// +// +// Tool_musedata2hum::addFiguredHarmony -- +// + +void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm, + HumNum timestamp, int part, int maxstaff) { + string fh = mr.getFigureString(); + int figureDuration = mr.getFigureDuration(); + fh = Convert::museFiguredBassToKernFiguredBass(fh); + if (m_figureOffset > 0) { + if (m_quarterDivisions > 0) { + HumNum offset(m_figureOffset, m_quarterDivisions); + timestamp + offset; } + } + if (fh.find(":") == string::npos) { + HTp fhtok = new HumdrumToken(fh); + m_lastfigure = fhtok; + gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; + return; + } - // check for intervals. Intervals will trigger a - // new element in the query list - // A new type ^ or v will not increment the query list - // (and they will expect a pitch after them). - if (ch == '/') { - active->anything = false; - active->anyinterval = false; - active->direction = 1; - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); - } else { - active = &temp; - } - continue; - } else if (ch == '\\') { - active->anything = false; - active->anyinterval = false; - active->direction = -1; - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); - } else { - active = &temp; - } - continue; - } else if (ch == '=') { - active->anything = false; - active->anyinterval = false; - active->direction = 0; - if (active == &temp) { - query.push_back(temp); - temp.clear(); + if (!m_lastfigure) { + HTp fhtok = new HumdrumToken(fh); + m_lastfigure = fhtok; + gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; + return; + } + + // For now assuming only one line extension needs to be transferred. + + // Has a line extension that should be moved to the previous token: + int position = 0; + int colpos = -1; + if (fh[0] == ':') { + colpos = 0; + } else { + for (int i=1; i<(int)fh.size(); i++) { + if (isspace(fh[i]) && !isspace(fh[i-1])) { + position++; } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); - } else { - active = &temp; + if (fh[i] == ':') { + colpos = i; + break; } - continue; } + } - // check for actual pitches - if ((ch >= 'a' && ch <= 'g')) { - active->anything = false; - active->anypitch = false; - active->base = 7; - active->pc = (ch - 'a' + 5) % 7; - if (active == &temp) { - query.push_back(temp); - temp.clear(); - } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); + string lastfh = m_lastfigure->getText(); + vector pieces; + int state = 0; + for (int i=0; i<(int)lastfh.size(); i++) { + if (state) { + if (isspace(lastfh[i])) { + state = 0; } else { - active = &temp; - } - continue; - } else if (ch == 'r') { - active->anything = false; - active->anypitch = false; - active->base = 7; - active->pc = GRIDREST; - if (active == &temp) { - query.push_back(temp); - temp.clear(); + pieces.back() += lastfh[i]; } - counter++; - if ((int)query.size() > counter) { - active = &query.at(counter); + } else { + if (isspace(lastfh[i])) { + // do nothing } else { - active = &temp; + pieces.resize(pieces.size()+1); + pieces.back() += lastfh[i]; + state = 1; } - continue; } + } - // accidentals: - if ((!query.empty()) && (ch == 'n') && (!Convert::isNaN(query.back().pc))) { - query.back().base = 40; - query.back().pc = Convert::base7ToBase40((int)query.back().pc + 70) % 40; - } else if ((!query.empty()) && (ch == '#') && (!Convert::isNaN(query.back().pc))) { - query.back().base = 40; - query.back().pc = (Convert::base7ToBase40((int)query.back().pc + 70) + 1) % 40; - } else if ((!query.empty()) && (ch == '-') && (!Convert::isNaN(query.back().pc))) { - query.back().base = 40; - query.back().pc = (Convert::base7ToBase40((int)query.back().pc + 70) - 1) % 40; - } - // deal with double sharps and double flats here + if (pieces.empty() || (position >= (int)pieces.size())) { + HTp fhtok = new HumdrumToken(fh); + m_lastfigure = fhtok; + gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; + return; } - // Convert rhythms to durations - for (int i=0; i<(int)query.size(); i++) { - if (query[i].anyrhythm) { - continue; - } - if (query[i].rhythm.empty()) { - continue; + pieces[position] += ':'; + string oldtok; + for (int i=0; i<(int)pieces.size(); i++) { + oldtok += pieces[i]; + if (i<(int)pieces.size() - 1) { + oldtok += ' '; } - query[i].duration = Convert::recipToDuration(query[i].rhythm); } - // what is this for (end condition)? - //if ((!query.empty()) && (query[0].base <= 0)) { - // temp.clear(); - // temp.anything = true; - // query.insert(query.begin(), temp); - //} + m_lastfigure->setText(oldtok); + + fh.erase(colpos, 1); + HTp newtok = new HumdrumToken(fh); + m_lastfigure = newtok; + gm->addFiguredBass(newtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; } ////////////////////////////// // -// checkVerticalOnly -- +// Tool_musedata2hum::addLyrics -- // -bool Tool_msearch::checkVerticalOnly(const string& input) { - if (input.empty()) { - return false; - } - if (input.size() < 2) { - return false; +void Tool_musedata2hum::addLyrics(GridSlice* slice, int part, int staff, MuseRecord& mr) { + int versecount = mr.getVerseCount(); + if (versecount == 0) { + return; } - if (input[0] != '(') { - return false; + for (int i=0; iat(part)->at(staff)->setVerse(i, verse); } - if (input.back() != ')') { - return false; + slice->reportVerseCount(part, staff, versecount); +} + + + +////////////////////////////// +// +// Tool_musedata2hum::addNoteDynamics -- only one contiguous dynamic allowed +// + +void Tool_musedata2hum::addNoteDynamics(GridSlice* slice, int part, + MuseRecord& mr) { + string notations = mr.getAdditionalNotationsField(); + vector dynamics(1); + vector column(1, -1); + int state = 0; + for (int i=0; i<(int)notations.size(); i++) { + if (state) { + switch (notations[i]) { + case 'p': + case 'm': + case 'f': + dynamics.back() += notations[i]; + break; + default: + state = 0; + dynamics.resize(dynamics.size() + 1); + } + } else { + switch (notations[i]) { + case 'p': + case 'm': + case 'f': + state = 1; + dynamics.back() = notations[i]; + column.back() = i; + break; + } + } } - for (int i=1; i<(int)input.size()-1; i++) { - // Maybe allow internal () if there is nothing outside of them. - if (input[i] == '(') { - return false; + + bool setdynamics = false; + vector ps; + HumRegex hre; + for (int i=0; i<(int)dynamics.size(); i++) { + if (dynamics[i].empty()) { + continue; } - if (input[i] == ')') { - return false; + mr.getPrintSuggestions(ps, column[i]+32); + if (ps.size() > 0) { + cerr << "\tPRINT SUGGESTION: " << ps[0] << endl; + // only checking the first entry (first parameter): + if (hre.search(ps[0], "Y(-?\\d+)")) { + int y = hre.getMatchInt(1); + cerr << "Y = " << y << endl; + } + } + + slice->at(part)->setDynamics(dynamics[i]); + setdynamics = true; + break; // only one dynamic allowed (at least for now) + } + + if (setdynamics) { + HumGrid* grid = slice->getOwner(); + if (grid) { + grid->setDynamicsPresent(part); } } - return true; } ////////////////////////////// // -// Tool_msearch::storeMatch -- Store a search result for later printing -// in the input file footer. +// Tool_musedata2hum::setTimeSigDurInfo -- // -void Tool_msearch::storeMatch(vector& match) { - m_matches.resize(m_matches.size() + 1); - m_matches.back().resize(match.size()); - for (int i=0; i<(int)match.size(); i++) { - m_matches.back().at(i) = match.at(i); +void Tool_musedata2hum::setTimeSigDurInfo(const string& ktimesig) { + HumRegex hre; + if (hre.search(ktimesig, "(\\d+)/(\\d+)")) { + int top = hre.getMatchInt(1); + int bot = hre.getMatchInt(2); + HumNum value = 1; + value /= bot; + value *= top; + value.invert(); + value *= 4; // convert from whole notes to quarter notes + m_timesigdur = value; } } @@ -102730,1414 +106351,1943 @@ void Tool_msearch::storeMatch(vector& match) { ////////////////////////////// // -// operator<< -- print MSearchQueryToken item. +// Tool_musedata2hum::getMeasure -- Could be imporoved by NlogN search. // -ostream& operator<<(ostream& out, MSearchQueryToken& item) { - out << "ITEM: " << endl; - out << "\tANYTHING:\t" << item.anything << endl; - out << "\tANYPITCH:\t" << item.anypitch << endl; - out << "\tANYINTERVAL:\t" << item.anyinterval << endl; - out << "\tANYRHYTHM:\t" << item.anyrhythm << endl; - out << "\tPC:\t\t" << item.pc << endl; - out << "\tBASE:\t\t" << item.base << endl; - out << "\tDIRECTION:\t" << item.direction << endl; - out << "\tDINTERVAL:\t" << item.dinterval << endl; - out << "\tCINTERVAL:\t" << item.cinterval << endl; - out << "\tRHYTHM:\t\t" << item.rhythm << endl; - out << "\tDURATION:\t" << item.duration << endl; - if (!item.harmonic.empty()) { - out << "\tHARMONIC:\t" << item.harmonic << endl; +GridMeasure* Tool_musedata2hum::getMeasure(HumGrid& outdata, HumNum starttime) { + for (int i=0; i<(int)outdata.size(); i++) { + if (outdata[i]->getTimestamp() == starttime) { + return outdata[i]; + } + } + // Did not find measure in data, so append to end of list. + // Assuming that unknown measures are at a later timestamp + // than those in current list, but should fix this later perhaps. + GridMeasure* gm = new GridMeasure(&outdata); + outdata.push_back(gm); + return gm; +} + + + +////////////////////////////// +// +// Tool_musedata2hum::setInitialOmd -- +// + +void Tool_musedata2hum::setInitialOmd(const string& omd) { + m_omd = omd; +} + + + +////////////////////////////// +// +// Tool_musedata2hum::cleanString -- +// + +string Tool_musedata2hum::cleanString(const string& input) { + return MuseData::cleanString(input); +} + + + + +////////////////////////////// +// +// Tool_musicxml2hum::Tool_musicxml2hum -- +// + +Tool_musicxml2hum::Tool_musicxml2hum(void) { + // Options& options = m_options; + // options.define("k|kern=b","display corresponding **kern data"); + + define("r|recip=b", "output **recip spine"); + define("s|stems=b", "include stems in output"); + + VoiceDebugQ = false; + DebugQ = false; +} + + + +////////////////////////////// +// +// Tool_musicxml2hum::convert -- Convert a MusicXML file into +// Humdrum content. +// + +bool Tool_musicxml2hum::convertFile(ostream& out, const char* filename) { + xml_document doc; + auto result = doc.load_file(filename); + if (!result) { + cerr << "\nXML file [" << filename << "] has syntax errors\n"; + cerr << "Error description:\t" << result.description() << "\n"; + cerr << "Error offset:\t" << result.offset << "\n\n"; + exit(1); + } + + return convert(out, doc); +} + + +bool Tool_musicxml2hum::convert(ostream& out, istream& input) { + string s(istreambuf_iterator(input), {}); + return convert(out, s.c_str()); +} + + +bool Tool_musicxml2hum::convert(ostream& out, const char* input) { + xml_document doc; + auto result = doc.load_string(input); + if (!result) { + cout << "\nXML content has syntax errors\n"; + cout << "Error description:\t" << result.description() << "\n"; + cout << "Error offset:\t" << result.offset << "\n\n"; + exit(1); + } + + return convert(out, doc); +} + + + +bool Tool_musicxml2hum::convert(ostream& out, xml_document& doc) { + initialize(); + + bool status = true; // for keeping track of problems in conversion process. + + setSoftwareInfo(doc); + vector partids; // list of part IDs + map partinfo; // mapping if IDs to score-part elements + map partcontent; // mapping of IDs to part elements + + getPartInfo(partinfo, partids, doc); + m_used_hairpins.resize(partinfo.size()); + + m_current_dynamic.resize(partids.size()); + m_current_brackets.resize(partids.size()); + m_current_figured_bass.resize(partids.size()); + m_stop_char.resize(partids.size(), "["); + + getPartContent(partcontent, partids, doc); + vector partdata; + partdata.resize(partids.size()); + m_last_ottava_direction.resize(partids.size()); + + fillPartData(partdata, partids, partinfo, partcontent); + + // for debugging: + //printPartInfo(partids, partinfo, partcontent, partdata); + + m_maxstaff = 0; + // check the voice info + for (int i=0; i<(int)partdata.size(); i++) { + partdata[i].prepareVoiceMapping(); + m_maxstaff += partdata[i].getStaffCount(); + // for debugging: + if (VoiceDebugQ) { + partdata[i].printStaffVoiceInfo(); + } + } + + // re-index voices to disallow empty intermediate voices. + reindexVoices(partdata); + + HumGrid outdata; + status &= stitchParts(outdata, partids, partinfo, partcontent, partdata); + + if (outdata.size() > 2) { + if (outdata.at(0)->getDuration() == 0) { + while (!outdata.at(0)->empty()) { + outdata.at(1)->push_front(outdata.at(0)->back()); + outdata.at(0)->pop_back(); + } + outdata.deleteMeasure(0); + } + } + + for (int i=0; i<(int)partdata.size(); i++) { + m_hasOrnamentsQ |= partdata[i].hasOrnaments(); + } + + outdata.removeRedundantClefChanges(); + outdata.removeSibeliusIncipit(); + m_systemDecoration = getSystemDecoration(doc, outdata, partids); + + // tranfer verse counts from parts/staves to HumGrid: + // should also do part verse counts here (-1 staffindex). + int versecount; + for (int p=0; p<(int)partdata.size(); p++) { + for (int s=0; s argv; + argv.push_back("autobeam"); // name of program (placeholder) + argv.push_back("-g"); // beam adjacent grace notes + gracebeam.process(argv); + // Need to force a reparsing of the files contents to + // analyze strands. For now just create a temporary + // Humdrum file to force the analysis of the strands. + stringstream sstream; + sstream << outfile; + HumdrumFile outfile2; + outfile2.readString(sstream.str()); + gracebeam.run(outfile2); + outfile = outfile2; + } + + if (m_hasTransposition) { + Tool_transpose transpose; + vector argv; + argv.push_back("transpose"); // name of program (placeholder) + argv.push_back("-C"); // transpose to concert pitch + transpose.process(argv); + stringstream sstream; + sstream << outfile; + HumdrumFile outfile2; + outfile2.readString(sstream.str()); + transpose.run(outfile2); + if (transpose.hasHumdrumText()) { + stringstream ss; + transpose.getHumdrumText(ss); + outfile.readString(ss.str()); + printResult(out, outfile); + } + } else { + for (int i=0; i = above" << endl; + } + if (m_slurbelow || m_staffbelow) { + out << "!!!RDF**kern: < = below" << endl; } - return out; + + for (int i=0; i<(int)partdata.size(); i++) { + if (partdata[i].hasEditorialAccidental()) { + out << "!!!RDF**kern: i = editorial accidental" << endl; + break; + } + } + + // put the above code in here some time: + prepareRdfs(partdata); + printRdfs(out); + + return status; } ////////////////////////////// // -// Tool_musedata2hum::Tool_musedata2hum -- +// Tool_musicxml2hum::addMeasureOneNumber -- For the first measure if it occurs before +// the first data, change = to =1. Maybe check next measure for a number and +// addd one less than that number instead of 1. // -Tool_musedata2hum::Tool_musedata2hum(void) { - // Options& options = m_options; - // options.define("k|kern=b","display corresponding **kern data"); +void Tool_musicxml2hum::addMeasureOneNumber(HumdrumFile& infile) { + for (int i=0; isetText(newvalue); + + // Add "1" to other spines here: + for (int j=1; jsetText(newvalue); + } + break; + } } ////////////////////////////// // -// initialize -- +// Tool_musicxml2hum::printResult -- filter out +// some item if not necessary: +// +// MuseScore calls everything "Piano" by default, so suppress +// this instrument name if there is only one **kern spine in +// the file. // -void Tool_musedata2hum::initialize(void) { - m_stemsQ = getBoolean("stems"); - m_recipQ = getBoolean("recip"); - m_group = getString("group"); - m_noOmvQ = getBoolean("no-omv"); +void Tool_musicxml2hum::printResult(ostream& out, HumdrumFile& outfile) { + vector kernspines = outfile.getKernSpineStartList(); + if (kernspines.size() > 1) { + out << outfile; + } else { + for (int i=0; i& argvlist) { - m_options.process(argvlist); +void Tool_musicxml2hum::printRdfs(ostream& out) { + if (!m_caesura_rdf.empty()) { + out << m_caesura_rdf << "\n"; + } } ////////////////////////////// // -// Tool_musedata2hum::getOptionDefinitions -- Used to avoid -// duplicating the definitions in the test main() function. +// Tool_muisicxml2hum::setSoftwareInfo -- Store which software program generated the +// MusicXML data to handle locale variants. There can be more than one +// entry, so desired information is not necessarily in the first one. // -Options Tool_musedata2hum::getOptionDefinitions(void) { - return m_options; +void Tool_musicxml2hum::setSoftwareInfo(xml_document& doc) { + string xpath = "/score-partwise/identification/encoding/software"; + string software = doc.select_node(xpath.c_str()).node().child_value(); + HumRegex hre; + if (hre.search(software, "sibelius", "i")) { + m_software = "sibelius"; + } } ////////////////////////////// // -// Tool_musedata2hum::convert -- Convert a MusicXML file into -// Humdrum content. +// Tool_musicxml2hum::cleanSpaces -- Converts newlines and tabs to spaces, and removes +// trailing spaces from the string. Does not remove leading spaces, but this could +// be added. Another variation would be to use \n to encode newlines if they need +// to be preserved, but for now converting them to spaces. // -bool Tool_musedata2hum::convertFile(ostream& out, const string& filename) { - MuseDataSet mds; - int result = mds.readFile(filename); - if (!result) { - cerr << "\nMuseData file [" << filename << "] has syntax errors\n"; - cerr << "Error description:\t" << mds.getError() << "\n"; - exit(1); +string& Tool_musicxml2hum::cleanSpaces(string& input) { + for (int i=0; i<(int)input.size(); i++) { + if (std::isspace(input[i])) { + input[i] = ' '; + } } - return convert(out, mds); + while ((!input.empty()) && std::isspace(input.back())) { + input.resize(input.size() - 1); + } + return input; } -bool Tool_musedata2hum::convert(ostream& out, istream& input) { - MuseDataSet mds; - mds.read(input); - return convert(out, mds); -} +////////////////////////////// +// +// Tool_musicxml2hum::cleanSpacesAndColons -- Converts newlines and +// tabs to spaces, and removes leading and trailing spaces from the +// string. Another variation would be to use \n to encode newlines +// if they need to be preserved, but for now converting them to spaces. +// Colons (:) are also converted to :. -bool Tool_musedata2hum::convertString(ostream& out, const string& input) { - MuseDataSet mds; - int result = mds.readString(input); - if (!result) { - cout << "\nXML content has syntax errors\n"; - cout << "Error description:\t" << mds.getError() << "\n"; - exit(1); +string Tool_musicxml2hum::cleanSpacesAndColons(const string& input) { + string output; + bool foundnonspace = false; + for (int i=0; i<(int)input.size(); i++) { + if (std::isspace(input[i])) { + if (!foundnonspace) { + output += ' '; + } + } + if (input[i] == ':') { + foundnonspace = true; + output += ":"; + } else { + output += input[i]; + foundnonspace = true; + } } - return convert(out, mds); -} - - -bool Tool_musedata2hum::convert(ostream& out, MuseDataSet& mds) { - int partcount = mds.getFileCount(); - if (partcount == 0) { - cerr << "Error: No parts found in data:" << endl; - cerr << mds << endl; - return false; + while ((!output.empty()) && std::isspace(output.back())) { + output.resize(output.size() - 1); } - initialize(); + return output; +} - m_tempo = mds.getMidiTempo(); - vector groupMemberIndex = mds.getGroupIndexList(m_group); - if (groupMemberIndex.empty()) { - cerr << "Error: no files in the " << m_group << " membership." << endl; - return false; - } - HumGrid outdata; - bool status = true; - for (int i=0; i<(int)groupMemberIndex.size(); i++) { - status &= convertPart(outdata, mds, groupMemberIndex[i], i, (int)groupMemberIndex.size()); - } +////////////////////////////// +// +// Tool_musicxml2hum::addHeaderRecords -- Inserted in reverse order +// (last record inserted first). +// - HumdrumFile outfile; - outdata.transferTokens(outfile); - outfile.generateLinesFromTokens(); - stringstream sss; - sss << outfile; - outfile.readString(sss.str()); +void Tool_musicxml2hum::addHeaderRecords(HumdrumFile& outfile, xml_document& doc) { + string xpath; + HumRegex hre; - if (needsAboveBelowKernRdf()) { - outfile.appendLine("!!!RDF**kern: > = above"); - outfile.appendLine("!!!RDF**kern: < = above"); + if (!m_systemDecoration.empty()) { + // outfile.insertLine(0, "!!!system-decoration: " + m_systemDecoration); + if (m_systemDecoration != "s1") { + outfile.appendLine("!!!system-decoration: " + m_systemDecoration); + } } - outfile.createLinesFromTokens(); - - Tool_trillspell trillspell; - trillspell.run(outfile); - - // Convert comments in header of first part: - int ii = groupMemberIndex[0]; - bool ending = false; - HumRegex hre; - for (int i=0; i< mds[ii].getLineCount(); i++) { - if (mds[ii][i].isAnyNote()) { - break; + xpath = "/score-partwise/credit/credit-words"; + pugi::xpath_node_set credits = doc.select_nodes(xpath.c_str()); + map keys; + vector refs; + vector positions; // +1 = above, -1 = below; + for (auto it = credits.begin(); it != credits.end(); it++) { + string contents = cleanSpaces(it->node().child_value()); + if (contents.empty()) { + continue; } - if (mds[ii].getLine(i).compare(0, 2, "@@") == 0) { - string output = mds[ii].getLine(i); - if (output == "@@@") { - ending = true; - continue; - } - for (int j=0; j<(int)output.size(); j++) { - if (output[j] == '@') { - output[j] = '!'; - } else { - break; - } - } - if (hre.search(output, "!!!\\s*([^!:]+)\\s*:")) { - string key = hre.getMatch(1); - m_usedReferences[key] = true; - } - if (ending) { - m_postReferences.push_back(output); + if ((contents[0] != '@') && (contents[0] != '!')) { + continue; + } + + if (contents.size() >= 3) { + // If line starts with "@@" then place at end of score. + if ((contents[0] == '@') && (contents[1] == '@')) { + positions.push_back(-1); } else { - out << output << endl; + positions.push_back(1); } + } else { + positions.push_back(1); } - } - if (!m_usedReferences["COM"]) { - string composer = mds[ii].getComposer(); - if (!composer.empty()) { - out << "!!!COM: " << composer << endl; + if (hre.search(contents, "^[@!]+([^\\s]+):")) { + // reference record + string key = hre.getMatch(1); + keys[key] = 1; + hre.replaceDestructive(contents, "!!!", "^[!@]+"); + refs.push_back(contents); + } else { + // global comment + hre.replaceDestructive(contents, "!!", "^[!@]+"); + refs.push_back(contents); } } - if (!m_usedReferences["CDT"]) { - string cdate = mds[ii].getComposerDate(); - if (!cdate.empty()) { - out << "!!!CDT: " << cdate << endl; - } + // OTL: title ////////////////////////////////////////////////////////// + + // Sibelius method + xpath = "/score-partwise/work/work-title"; + string worktitle = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value()); + string otl_record; + string omv_record; + bool worktitleQ = false; + if ((worktitle != "") && (worktitle != "Title")) { + otl_record = "!!!OTL: "; + otl_record += worktitle; + worktitleQ = true; } - if (!m_usedReferences["OTL"]) { - string worktitle = mds[ii].getWorkTitle(); - if (!worktitle.empty()) { - out << "!!!OTL: " << worktitle << endl; + xpath = "/score-partwise/movement-title"; + string mtitle = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value()); + if (mtitle != "") { + if (worktitleQ) { + omv_record = "!!!OMV: "; + omv_record += mtitle; + } else { + otl_record = "!!!OTL: "; + otl_record += mtitle; } } - if (!m_noOmvQ) { - if (!m_usedReferences["OMV"]) { - string movementtitle = mds[ii].getMovementTitle(); - if (!movementtitle.empty()) { - out << "!!!OMV: " << movementtitle << endl; + // COM: composer ///////////////////////////////////////////////////////// + // CDT: composer's dates + xpath = "/score-partwise/identification/creator[@type='composer']"; + string composer = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value()); + string cdt_record; + if (composer != "") { + if (hre.search(composer, R"(\((.*?\d.*?)\))")) { + string dates = hre.getMatch(1); + // hre.replaceDestructive(composer, "", R"(\()" + dates + R"(\))"); + auto loc = composer.find(dates); + if (loc != std::string::npos) { + composer.replace(loc-1, dates.size()+2, ""); + } + hre.replaceDestructive(composer, "", R"(^\s+)"); + hre.replaceDestructive(composer, "", R"(\s+$)"); + if (hre.search(composer, R"(([^\s]+) +([^\s]+))")) { + composer = hre.getMatch(2) + ", " + hre.getMatch(1); + } + if (dates != "") { + if (hre.search(dates, R"(\b(\d{4})\?)")) { + string replacement = "~"; + replacement += hre.getMatch(1); + hre.replaceDestructive(dates, replacement, R"(\b\d{4}\?)"); + cdt_record = "!!!CDT: "; + cdt_record += dates; + } } } } - if (!m_usedReferences["OPS"]) { - string opus = mds[ii].getOpus(); - if (!opus.empty()) { - out << "!!!OPS: " << opus << endl; - } - } - if (!m_usedReferences["ONM"]) { - string number = mds[ii].getNumber(); - if (!number.empty()) { - out << "!!!ONM: " << number << endl; + for (int i=(int)refs.size()-1; i>=0; i--) { + if (positions.at(i) > 0) { + // place at start of file + outfile.insertLine(0, refs[i]); } } - if (!m_usedReferences["OMD"]) { - if (!m_omd.empty()) { - out << "!!!OMD: " << m_omd << endl; + for (int i=0; i<(int)refs.size(); i++) { + if (positions.at(i) < 0) { + // place at end of file + outfile.appendLine(refs[i]); } } - bool foundDataQ = false; - for (int i=0; i outputs; - for (int i=mds[lastone].getLineCount() - 1; i>=0; i--) { - if (mds[lastone][i].isAnyNote()) { - break; - } - if (mds[lastone].getLine(i).compare(0, 2, "@@") == 0) { - string output = mds[lastone].getLine(i); - for (int j=0; j<(int)output.size(); j++) { - if (output[j] == '@') { - output[j] = '!'; - } else { - break; - } - } - outputs.push_back(output); - } + if (validcopy) { + string yem_record = "!!!YEM: "; + yem_record += cleanSpaces(copy); + outfile.appendLine(yem_record); } - for (int i=(int)outputs.size() - 1; i>=0; i--) { - out << outputs[i] << endl; + // RDF: + if (m_hasEditorial) { + string rdf_record = "!!!RDF**kern: i = editorial accidental"; + outfile.appendLine(rdf_record); } +} + + + +////////////////////////////// +// +// initialize -- +// - return status; +void Tool_musicxml2hum::initialize(void) { + m_recipQ = getBoolean("recip"); + m_stemsQ = getBoolean("stems"); + m_hasOrnamentsQ = false; } ////////////////////////////// // -// Tool_musedata2hum::printLine -- Print line of Humdrum file -// contents. If there is any layout parameter in the line tokens, -// then print an extra line with these. Currently only checking for -// a single parameter. +// Tool_musicxml2hum::reindexVoices -- // -void Tool_musedata2hum::printLine(ostream& out, HumdrumLine& line) { - vector lo(line.getFieldCount()); - int count = 0; - for (int i=0; igetValue("auto", "LO"); - if (!value.empty()) { - lo.at(i) = value; - count++; - } - } - if (count > 0) { - for (int i=0; i<(int)lo.size(); i++) { - if (lo[i].empty()) { - out << "!"; - } else { - out << lo[i]; - } - if (i < (int)lo.size() - 1) { - out << "\t"; +void Tool_musicxml2hum::reindexVoices(vector& partdata) { + for (int p=0; p<(int)partdata.size(); p++) { + for (int m=0; m<(int)partdata[p].getMeasureCount(); m++) { + MxmlMeasure* measure = partdata[p].getMeasure(m); + if (!measure) { + continue; } + reindexMeasure(measure); } - out << endl; } - out << line << endl; } ////////////////////////////// // -// Tool_musedata2hum::convertPart -- +// Tool_musicxml2hum::prepareRdfs -- // -bool Tool_musedata2hum::convertPart(HumGrid& outdata, MuseDataSet& mds, int index, int partindex, int maxstaff) { - MuseData& part = mds[index]; - m_lastfigure = NULL; - m_lastnote = NULL; - m_lastbarnum = -1; - m_part = partindex; - // maybe maxpart? - m_maxstaff = maxstaff; - - bool status = true; - int i = 0; - while (i < part.getLineCount()) { - m_measureLineIndex = i; - i = convertMeasure(outdata, part, partindex, i); +void Tool_musicxml2hum::prepareRdfs(vector& partdata) { + string caesura; + for (int i=0; i<(int)partdata.size(); i++) { + caesura = partdata[i].getCaesura(); + if (!caesura.empty()) { + } } - storePartName(outdata, part, partindex); + if (!caesura.empty()) { + m_caesura_rdf = "!!!RDF**kern: " + caesura + " = caesura"; + } - return status; } -/////////////////////////////// +////////////////////////////// // -// Tool_musedata2hum::storePartName -- +// Tool_musicxml2hum::reindexMeasure -- // -void Tool_musedata2hum::storePartName(HumGrid& outdata, MuseData& part, int index) { - string name = part.getPartName(); - if (!name.empty()) { - outdata.setPartName(index, name); +void Tool_musicxml2hum::reindexMeasure(MxmlMeasure* measure) { + if (!measure) { + return; } -} - + vector > staffVoiceCounts; + vector& elist = measure->getEventList(); -////////////////////////////// -// -// Tool_musedata2hum::convertMeasure -- -// + for (int i=0; i<(int)elist.size(); i++) { + int staff = elist[i]->getStaffIndex(); + int voice = elist[i]->getVoiceIndex(); -int Tool_musedata2hum::convertMeasure(HumGrid& outdata, MuseData& part, int partindex, int startindex) { - if (part.getLineCount() == 0) { - return 1; - } - HumNum starttime = part[startindex].getAbsBeat(); - HumNum filedur = part.getFileDuration(); - HumNum diff = filedur - starttime; - if (diff == 0) { - // last barline in score, so ignore - return startindex + 1;; + if ((voice >= 0) && (staff >= 0)) { + if (staff >= (int)staffVoiceCounts.size()) { + int newsize = staff + 1; + staffVoiceCounts.resize(newsize); + } + if (voice >= (int)staffVoiceCounts[staff].size()) { + int oldsize = (int)staffVoiceCounts[staff].size(); + int newsize = voice + 1; + staffVoiceCounts[staff].resize(newsize); + for (int i=oldsize; i= part.getLineCount()) { - endtime = part[i-1].getAbsBeat(); - } else { - endtime = part[i].getAbsBeat(); + + if (!needreindexing) { + return; } - // set duration of measures (so it will be printed in conversion to Humdrum): - gm->setDuration(endtime - starttime); - gm->setTimestamp(starttime); - gm->setTimeSigDur(m_timesigdur); + vector > remapping; + remapping.resize(staffVoiceCounts.size()); + int reindex; + for (int i=0; i<(int)staffVoiceCounts.size(); i++) { + remapping[i].resize(staffVoiceCounts[i].size()); + reindex = 0; + for (int j=0; j<(int)remapping[i].size(); j++) { + if (remapping[i].size() == 1) { + remapping[i][j] = 0; + continue; + } + if (staffVoiceCounts[i][j]) { + remapping[i][j] = reindex++; + } else { + remapping[i][j] = -1; // invalidate voice + } + } + } - if ((i < part.getLineCount()) && part[i].isBarline()) { - if (partindex == 0) { - // For now setting the barline style from the - // lowest staff. This is mostly because - // MEI/verovio can handle only one style - // on a system barline. But also because - // GridMeasure objects only has a setting - // for a single barline style. - setMeasureStyle(outdata.back(), part[i]); - setMeasureNumber(outdata.back(), part[i]); - // gm->setBarStyle(MeasureStyle::Plain); + // Go back and remap the voice indexes of elements. + // Presuming that the staff does not need to be reindex. + for (int i=0; i<(int)elist.size(); i++) { + int oldvoice = elist[i]->getVoiceIndex(); + int staff = elist[i]->getStaffIndex(); + if (oldvoice < 0) { + continue; + } + int newvoice = remapping[staff][oldvoice]; + if (newvoice == oldvoice) { + continue; } + elist[i]->setVoiceIndex(newvoice); } - return i; } ////////////////////////////// // -// Tool_musedata2hum::setMeasureNumber -- +// Tool_musicxml2hum::setOptions -- // -void Tool_musedata2hum::setMeasureNumber(GridMeasure* gm, MuseRecord& mr) { - int pos = -1; - string line = mr.getLine(); - bool space = false; - for (int i=0; i<(int)line.size(); i++) { - if (isspace(line[i])) { - space = true; - continue; - } - if (!space) { - continue; - } - if (isdigit(line[i])) { - pos = i; - break; - } - } - if (pos < 0) { - gm->setMeasureNumber(-1); - return; - } - int num = stoi(line.substr(pos)); - if (m_lastbarnum >= 0) { - int temp = num; - num = m_lastbarnum; - m_lastbarnum = temp; - } - gm->setMeasureNumber(num); +void Tool_musicxml2hum::setOptions(int argc, char** argv) { + m_options.process(argc, argv); +} + + +void Tool_musicxml2hum::setOptions(const vector& argvlist) { + m_options.process(argvlist); } ////////////////////////////// // -// Tool_musedata2hum::setMeasureStyle -- +// Tool_musicxml2hum::getOptionDefinitions -- Used to avoid +// duplicating the definitions in the test main() function. // -void Tool_musedata2hum::setMeasureStyle(GridMeasure* gm, MuseRecord& mr) { - string line = mr.getLine(); - string barstyle = mr.getMeasureFlags(); - if (line.compare(0, 7, "mheavy2") == 0) { - if (barstyle.find(":|") != string::npos) { - gm->setStyle(MeasureStyle::RepeatBackward); - } else { - gm->setStyle(MeasureStyle::Final); - } - } else if (line.compare(0, 7, "mheavy3") == 0) { - if (barstyle.find("|:") != string::npos) { - gm->setStyle(MeasureStyle::RepeatForward); - } - } else if (line.compare(0, 7, "mheavy4") == 0) { - if (barstyle.find(":|:") != string::npos) { - gm->setStyle(MeasureStyle::RepeatBoth); - } else if (barstyle.find("|: :|") != string::npos) { - // Vivaldi op. 1, no. 1, mvmt. 1, m. 10: mheavy4 |: :| - gm->setStyle(MeasureStyle::RepeatBoth); - } - } else if (line.compare(0, 7, "mdouble") == 0) { - gm->setStyle(MeasureStyle::Double); - } +Options Tool_musicxml2hum::getOptionDefinitions(void) { + return m_options; } +/////////////////////////////////////////////////////////////////////////// + + ////////////////////////////// // -// Tool_musedata2hum::convertLine -- +// Tool_musicxml2hum::fillPartData -- // -void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) { - int part = m_part; - int staff = 0; - int maxstaff = m_maxstaff; - int layer = mr.getLayer(); - if (layer > 0) { - // convert to an index: - layer = layer - 1; - } +bool Tool_musicxml2hum::fillPartData(vector& partdata, + const vector& partids, map& partinfo, + map& partcontent) { - if (mr.isAnyNoteOrRest()) { - m_figureOffset = 0; + bool output = true; + for (int i=0; i<(int)partinfo.size(); i++) { + partdata[i].setPartNumber(i+1); + output &= fillPartData(partdata[i], partids[i], partinfo[partids[i]], + partcontent[partids[i]]); } + return output; +} - if (mr.isDirection()) { - return; - } - HumNum timestamp = mr.getAbsBeat(); - // cerr << "CONVERTING LINE " << timestamp << "\t" << mr << endl; - string tok; - GridSlice* slice = NULL; +bool Tool_musicxml2hum::fillPartData(MxmlPart& partdata, + const string& id, xml_node partdeclaration, xml_node partcontent) { + if (m_stemsQ) { + partdata.enableStems(); + } - if (mr.isBarline()) { - // barline handled elsewhere - // tok = mr.getKernMeasure(); - } else if (mr.isAttributes()) { - map attributes; - mr.getAttributeMap(attributes); + partdata.parsePartInfo(partdeclaration); + // m_last_ottava_direction.at(partdata.getPartIndex()).resize(partdata.getStaffCount()); + // staff count is incorrect at this point? Just assume 32 staves in the part, which should + // be 28-30 staffs too many. + m_last_ottava_direction.at(partdata.getPartIndex()).resize(32); - string mtempo = cleanString(attributes["D"]); - if (!mtempo.empty()) { - if (timestamp != 0) { - string value = "!!!OMD: " + mtempo; - gm->addGlobalComment(value, timestamp); - } else { - setInitialOmd(mtempo); + int count; + auto measures = partcontent.select_nodes("./measure"); + for (int i=0; i<(int)measures.size(); i++) { + partdata.addMeasure(measures[i].node()); + count = partdata.getMeasureCount(); + if (count > 1) { + HumNum dur = partdata.getMeasure(count-1)->getTimeSigDur(); + if (dur == 0) { + HumNum dur = partdata.getMeasure(count-2) + ->getTimeSigDur(); + if (dur > 0) { + partdata.getMeasure(count - 1)->setTimeSigDur(dur); + } } } - if (!attributes["Q"].empty()) { - m_quarterDivisions = std::stoi(attributes["Q"]); - } + } + return true; +} - string mclef = attributes["C"]; - if (!mclef.empty()) { - string kclef = Convert::museClefToKernClef(mclef); - if (!kclef.empty()) { - gm->addClefToken(kclef, timestamp, part, staff, layer, maxstaff); - } - } - string mkeysig = attributes["K"]; - if (!mkeysig.empty()) { - string kkeysig = Convert::museKeySigToKernKeySig(mkeysig); - gm->addKeySigToken(kkeysig, timestamp, part, staff, layer, maxstaff); + +////////////////////////////// +// +// Tool_musicxml2hum::printPartInfo -- Debug information. +// + +void Tool_musicxml2hum::printPartInfo(vector& partids, + map& partinfo, map& partcontent, + vector& partdata) { + cout << "\nPart information in the file:" << endl; + int maxmeasure = 0; + for (int i=0; i<(int)partids.size(); i++) { + cout << "\tPART " << i+1 << " id = " << partids[i] << endl; + cout << "\tMAXSTAFF " << partdata[i].getStaffCount() << endl; + cout << "\t\tpart name:\t" + << getChildElementText(partinfo[partids[i]], "part-name") << endl; + cout << "\t\tpart abbr:\t" + << getChildElementText(partinfo[partids[i]], "part-abbreviation") + << endl; + auto node = partcontent[partids[i]]; + auto measures = node.select_nodes("./measure"); + cout << "\t\tMeasure count:\t" << measures.size() << endl; + if (maxmeasure < (int)measures.size()) { + maxmeasure = (int)measures.size(); } + cout << "\t\tTotal duration:\t" << partdata[i].getDuration() << endl; + } - string mtimesig = attributes["T"]; - if (!mtimesig.empty()) { - string ktimesig = Convert::museTimeSigToKernTimeSig(mtimesig); - slice = gm->addTimeSigToken(ktimesig, timestamp, part, staff, layer, maxstaff); - setTimeSigDurInfo(ktimesig); - string kmeter = Convert::museMeterSigToKernMeterSig(mtimesig); - if (!kmeter.empty()) { - slice = gm->addMeterSigToken(kmeter, timestamp, part, staff, layer, maxstaff); + MxmlMeasure* measure; + for (int i=0; igetDuration(); } - if (m_tempo > 0.00) { - int value = (int)(m_tempo + 0.5); - string tempotok = "*MM" + to_string(value); - slice = gm->addTempoToken(tempotok, timestamp, part, staff, layer, maxstaff); + if (j < (int)partdata.size() - 1) { + cout << "\t"; } } - } else if (mr.isRegularNote()) { - tok = mr.getKernNoteStyle(1, 1); - string other = mr.getKernNoteOtherNotations(); - if (!needsAboveBelowKernRdf()) { - if (other.find("<") != string::npos) { - addAboveBelowKernRdf(); - } else if (other.find(">") != string::npos) { - addAboveBelowKernRdf(); - } + cout << endl; + } +} + + + +////////////////////////////// +// +// Tool_musicxml2hum::insertPartNames -- +// + +void Tool_musicxml2hum::insertPartNames(HumGrid& outdata, vector& partdata) { + + bool hasname = false; + bool hasabbr = false; + + for (int i=0; i<(int)partdata.size(); i++) { + string value; + value = partdata[i].getPartName(); + if (!value.empty()) { + hasname = true; + break; } - if (!other.empty()) { - tok += other; + } + + for (int i=0; i<(int)partdata.size(); i++) { + string value; + value = partdata[i].getPartAbbr(); + if (!value.empty()) { + hasabbr = true; + break; } - slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff); - if (slice) { - mr.setVoice(slice->at(part)->at(staff)->at(layer)); - string gr = mr.getLayoutVis(); - if (gr.size() > 0) { - // Visual and performance durations are not equal: - HTp token = slice->at(part)->at(staff)->at(layer)->getToken(); - string text = "!LO:N:vis=" + gr; - token->setValue("auto", "LO", text); + } + + if (!(hasabbr || hasname)) { + return; + } + + GridMeasure* gm; + if (outdata.empty()) { + gm = new GridMeasure(&outdata); + outdata.push_back(gm); + } else { + gm = outdata[0]; + } + + int maxstaff; + + if (hasabbr) { + for (int i=0; i<(int)partdata.size(); i++) { + string partabbr = partdata[i].getPartAbbr(); + if (partabbr.empty()) { + continue; } + string abbr = "*I'" + partabbr; + maxstaff = outdata.getStaffCount(i); + gm->addLabelAbbrToken(abbr, 0, i, maxstaff-1, 0, (int)partdata.size(), maxstaff); } - m_lastnote = slice->at(part)->at(staff)->at(layer)->getToken(); - addNoteDynamics(slice, part, mr); - addDirectionDynamics(slice, part, mr); - addLyrics(slice, part, staff, mr); - } else if (mr.isFiguredHarmony()) { - addFiguredHarmony(mr, gm, timestamp, part, maxstaff); - } else if (mr.isChordNote()) { - tok = mr.getKernNoteStyle(1, 1); - if (m_lastnote) { - string text = m_lastnote->getText(); - text += " "; - text += tok; - m_lastnote->setText(text); - } else { - cerr << "Warning: found chord note with no regular note to attach to" << endl; - } - } else if (mr.isCueNote()) { - cerr << "PROCESS CUE NOTE HERE: " << mr << endl; - } else if (mr.isGraceNote()) { - cerr << "PROCESS GRACE NOTE HERE: " << mr << endl; - } else if (mr.isChordGraceNote()) { - cerr << "PROCESS GRACE CHORD NOTE HERE: " << mr << endl; - } else if (mr.isAnyRest()) { - tok = mr.getKernRestStyle(); - slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff); - if (slice) { - mr.setVoice(slice->at(part)->at(staff)->at(layer)); - string gr = mr.getLayoutVis(); - if (gr.size() > 0) { - cerr << "GRAPHIC VERSION OF NOTEB " << gr << endl; + } + + if (hasname) { + for (int i=0; i<(int)partdata.size(); i++) { + string partname = partdata[i].getPartName(); + if (partname.empty()) { + continue; } - } - } else if (mr.isDirection()) { - if (mr.isTextDirection()) { - addTextDirection(gm, part, staff, mr, timestamp); + if (partname.find("MusicXML") != string::npos) { + // ignore Finale dummy part names + continue; + } + if (partname.find("Part_") != string::npos) { + // ignore SharpEye dummy part names + continue; + } + if (partname.find("Unnamed") != string::npos) { + // ignore Sibelius dummy part names + continue; + } + string name = "*I\"" + partname; + maxstaff = outdata.getStaffCount(i); + gm->addLabelToken(name, 0, i, maxstaff-1, 0, (int)partdata.size(), maxstaff); } } + } + ////////////////////////////// // -// Tool_musedata2hum::addDirectionDynamics -- search for a dynamic -// marking before the current line and after any previous note -// or similar line. These lines are store in "musical directions" -// which start the line with a "*" character. -// -// Example for "p" dyamic, with print suggesting. -// 1 2 -// 12345678901234567890123456789 -// * G p -// P C17:Y57 +// Tool_musicxml2hum::stitchParts -- Merge individual parts into a +// single score sequence. // -void Tool_musedata2hum::addDirectionDynamics(GridSlice* slice, int part, MuseRecord& mr) { - MuseRecord* direction = mr.getMusicalDirection(); - if (direction == NULL) { - return; +bool Tool_musicxml2hum::stitchParts(HumGrid& outdata, + vector& partids, map& partinfo, + map& partcontent, vector& partdata) { + if (partdata.size() == 0) { + return false; } - if (direction->isDynamic()) { - string dynamicText = direction->getDynamicText(); - if (!dynamicText.empty()) { - slice->at(part)->setDynamics(dynamicText); - HumGrid* grid = slice->getOwner(); - if (grid) { - grid->setDynamicsPresent(part); - } + int i; + int measurecount = partdata[0].getMeasureCount(); + // i used to start at 1 for some strange reason. + for (i=0; i<(int)partdata.size(); i++) { + if (measurecount != partdata[i].getMeasureCount()) { + cerr << "ERROR: cannot handle parts with different measure\n"; + cerr << "counts yet. Compare MM" << measurecount << " to MM"; + cerr << partdata[i].getMeasureCount() << endl; + exit(1); } } + + vector partstaves(partdata.size(), 0); + for (i=0; i<(int)partstaves.size(); i++) { + partstaves[i] = partdata[i].getStaffCount(); + } + + bool status = true; + int m; + for (m=0; m = above -// !!!RDF**kern: < = below -// in the output Humdrum data file. +// moveBreaksToEndOfPreviousMeasure -- // -void Tool_musedata2hum::addAboveBelowKernRdf(void) { - m_aboveBelowKernRdf = true; +void Tool_musicxml2hum::moveBreaksToEndOfPreviousMeasure(HumGrid& outdata) { + for (int i=1; i<(int)outdata.size(); i++) { + GridMeasure* gm = outdata[i]; + GridMeasure* gmlast = outdata[i-1]; + if (!gm || !gmlast) { + continue; + } + if (gm->begin() == gm->end()) { + // empty measure + return; + } + GridSlice *firstit = *(gm->begin()); + HumNum starttime = firstit->getTimestamp(); + for (auto it = gm->begin(); it != gm->end(); it++) { + HumNum time2 = (*it)->getTimestamp(); + if (time2 > starttime) { + break; + } + if (!(*it)->isGlobalComment()) { + continue; + } + HTp token = (*it)->at(0)->at(0)->at(0)->getToken(); + if (!token) { + continue; + } + if ((*token == "!!linebreak:original") || + (*token == "!!pagebreak:original")) { + GridSlice *swapper = *it; + gm->erase(it); + gmlast->push_back(swapper); + // there can be only one break, so quit the loop now. + break; + } + } + } } ////////////////////////////// // -// Tool_musedata2hum::needsAboveBelowKernRdf -- Function name says it all. +// Tool_musicxml2hum::cleanupMeasures -- +// Also add barlines here (keeping track of the +// duration of each measure). // -bool Tool_musedata2hum::needsAboveBelowKernRdf(void) { - return m_aboveBelowKernRdf; +void Tool_musicxml2hum::cleanupMeasures(HumdrumFile& outfile, + vector measures) { + + HumdrumToken* token; + for (int i=0; iappendToken(token); + line->createLineFromTokens(); + outfile.appendLine(line); +} - if (!mr.isTextDirection()) { - return; - } - string text = mr.getTextDirection(); - if (text == "") { - // no text direction to process - return; - } - HumRegex hre; - hre.replaceDestructive(text, ":", ":", "g"); - string output = "!LO:TX"; - output += ":b"; // text below (figure out above cases) - output += ":t="; - output += text; - // add staff index later - gm->addLayoutParameter(NULL, part, output); +////////////////////////////// +// +// Tool_musicxml2hum::insertAllToken -- +// + +void Tool_musicxml2hum::insertAllToken(HumdrumFile& outfile, + vector& partdata, const string& common) { + + HLp line = new HumdrumLine; + HumdrumToken* token; + int i, j; + for (i=0; i<(int)partdata.size(); i++) { + for (j=0; j<(int)partdata[i].getStaffCount(); j++) { + token = new HumdrumToken(common); + line->appendToken(token); + } + for (j=0; j<(int)partdata[i].getVerseCount(); j++) { + token = new HumdrumToken(common); + line->appendToken(token); + } + } + outfile.appendLine(line); } + ////////////////////////////// // -// Tool_musedata2hum::addFiguredHarmony -- +// Tool_musicxml2hum::insertMeasure -- // -void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm, - HumNum timestamp, int part, int maxstaff) { - string fh = mr.getFigureString(); - int figureDuration = mr.getFigureDuration(); - fh = Convert::museFiguredBassToKernFiguredBass(fh); - if (m_figureOffset > 0) { - if (m_quarterDivisions > 0) { - HumNum offset(m_figureOffset, m_quarterDivisions); - timestamp + offset; +bool Tool_musicxml2hum::insertMeasure(HumGrid& outdata, int mnum, + vector& partdata, vector partstaves) { + + GridMeasure* gm = outdata.addMeasureToBack(); + + MxmlMeasure* xmeasure; + vector measuredata; + vector* > sevents; + int i; + + for (i=0; i<(int)partdata.size(); i++) { + xmeasure = partdata[i].getMeasure(mnum); + measuredata.push_back(xmeasure); + if (i==0) { + gm->setDuration(partdata[i].getMeasure(mnum)->getDuration()); + gm->setTimestamp(partdata[i].getMeasure(mnum)->getTimestamp()); + gm->setTimeSigDur(partdata[i].getMeasure(mnum)->getTimeSigDur()); + } + checkForDummyRests(xmeasure); + sevents.push_back(xmeasure->getSortedEvents()); + if (i == 0) { + // only checking measure style of first barline + gm->setBarStyle(xmeasure->getBarStyle()); } - } - if (fh.find(":") == string::npos) { - HTp fhtok = new HumdrumToken(fh); - m_lastfigure = fhtok; - gm->addFiguredBass(fhtok, timestamp, part, maxstaff); - m_figureOffset += figureDuration; - return; } - if (!m_lastfigure) { - HTp fhtok = new HumdrumToken(fh); - m_lastfigure = fhtok; - gm->addFiguredBass(fhtok, timestamp, part, maxstaff); - m_figureOffset += figureDuration; - return; - } + vector curtime(partdata.size()); + vector measuredurs(partdata.size()); + vector curindex(partdata.size(), 0); // assuming data in a measure... + HumNum nexttime = -1; - // For now assuming only one line extension needs to be transferred. + vector> endingDirections(partdata.size()); - // Has a line extension that should be moved to the previous token: - int position = 0; - int colpos = -1; - if (fh[0] == ':') { - colpos = 0; - } else { - for (int i=1; i<(int)fh.size(); i++) { - if (isspace(fh[i]) && !isspace(fh[i-1])) { - position++; - } - if (fh[i] == ':') { - colpos = i; + HumNum tsdur; + for (i=0; i<(int)curtime.size(); i++) { + tsdur = measuredata[i]->getTimeSigDur(); + if ((tsdur == 0) && (i > 0)) { + tsdur = measuredata[i-1]->getTimeSigDur(); + measuredata[i]->setTimeSigDur(tsdur); + } + + // Keep track of hairpin endings that should be attached + // the the previous note (and doubling the ending marker + // to indicate that the timestamp of the ending is at the + // end rather than the start of the note. + vector& events = measuredata[i]->getEventList(); + xml_node hairpin = xml_node(NULL); + for (int j=(int)events.size() - 1; j >= 0; j--) { + if (events[j]->getElementName() == "note") { + if (hairpin) { + events[j]->setHairpinEnding(hairpin); + hairpin = xml_node(NULL); + } break; + } else if (events[j]->getElementName() == "direction") { + stringstream ss; + ss.str(""); + events[j]->getNode().print(ss); + if (ss.str().find("wedge") != string::npos) { + if (ss.str().find("stop") != string::npos) { + hairpin = events[j]->getNode(); + } + } } } - } - string lastfh = m_lastfigure->getText(); - vector pieces; - int state = 0; - for (int i=0; i<(int)lastfh.size(); i++) { - if (state) { - if (isspace(lastfh[i])) { - state = 0; - } else { - pieces.back() += lastfh[i]; + if (VoiceDebugQ) { + for (int j=0; j<(int)events.size(); j++) { + cerr << "!!ELEMENT: "; + cerr << "\tTIME: " << events[j]->getStartTime(); + cerr << "\tSTi: " << events[j]->getStaffIndex(); + cerr << "\tVi: " << events[j]->getVoiceIndex(); + cerr << "\tTS: " << events[j]->getStartTime(); + cerr << "\tDUR: " << events[j]->getDuration(); + cerr << "\tPITCH: " << events[j]->getKernPitch(); + cerr << "\tNAME: " << events[j]->getElementName(); + cerr << endl; } + cerr << "======================================" << endl; + } + if (!(*sevents[i]).empty()) { + curtime[i] = (*sevents[i])[curindex[i]].starttime; } else { - if (isspace(lastfh[i])) { - // do nothing - } else { - pieces.resize(pieces.size()+1); - pieces.back() += lastfh[i]; - state = 1; - } + curtime[i] = tsdur; } - } - - if (pieces.empty() || (position >= (int)pieces.size())) { - HTp fhtok = new HumdrumToken(fh); - m_lastfigure = fhtok; - gm->addFiguredBass(fhtok, timestamp, part, maxstaff); - m_figureOffset += figureDuration; - return; - } - - pieces[position] += ':'; - string oldtok; - for (int i=0; i<(int)pieces.size(); i++) { - oldtok += pieces[i]; - if (i<(int)pieces.size() - 1) { - oldtok += ' '; + if (nexttime < 0) { + nexttime = curtime[i]; + } else if (curtime[i] < nexttime) { + nexttime = curtime[i]; } + measuredurs[i] = measuredata[i]->getDuration(); } - m_lastfigure->setText(oldtok); + bool allend = false; + vector nowevents; + vector nowparts; + bool status = true; - fh.erase(colpos, 1); - HTp newtok = new HumdrumToken(fh); - m_lastfigure = newtok; - gm->addFiguredBass(newtok, timestamp, part, maxstaff); - m_figureOffset += figureDuration; -} + HumNum processtime = nexttime; + while (!allend) { + nowevents.resize(0); + nowparts.resize(0); + allend = true; + processtime = nexttime; + nexttime = -1; + for (i = (int)partdata.size()-1; i >= 0; i--) { + if (curindex[i] >= (int)(*sevents[i]).size()) { + continue; + } + if ((*sevents[i])[curindex[i]].starttime == processtime) { + SimultaneousEvents* thing = &(*sevents[i])[curindex[i]]; + nowevents.push_back(thing); + nowparts.push_back(i); + curindex[i]++; + } + if (curindex[i] < (int)(*sevents[i]).size()) { + allend = false; + if ((nexttime < 0) || + ((*sevents[i])[curindex[i]].starttime < nexttime)) { + nexttime = (*sevents[i])[curindex[i]].starttime; + } + } + } + status &= convertNowEvents(outdata.back(), + nowevents, nowparts, processtime, partdata, partstaves); -////////////////////////////// -// -// Tool_musedata2hum::addLyrics -- -// + // Remove all figured bass numbers for this nowtime so that they are not + // accidentally displayed in the next nowtime, which can currently + // happen if there are no nonzerodur events in the same part + for (int i=0; i<(int)m_current_figured_bass.size(); i++) { + m_current_figured_bass[i].clear(); + } + } -void Tool_musedata2hum::addLyrics(GridSlice* slice, int part, int staff, MuseRecord& mr) { - int versecount = mr.getVerseCount(); - if (versecount == 0) { - return; + if (offsetHarmony.size() > 0) { + insertOffsetHarmonyIntoMeasure(outdata.back()); } - for (int i=0; iat(part)->at(staff)->setVerse(i, verse); + if (m_offsetFiguredBass.size() > 0) { + insertOffsetFiguredBassIntoMeasure(outdata.back()); } - slice->reportVerseCount(part, staff, versecount); + return status; } ////////////////////////////// // -// Tool_musedata2hum::addNoteDynamics -- only one contiguous dynamic allowed +// Tool_musicxml2hum::insertOffsetFiguredBassIntoMeasure -- // -void Tool_musedata2hum::addNoteDynamics(GridSlice* slice, int part, - MuseRecord& mr) { - string notations = mr.getAdditionalNotationsField(); - vector dynamics(1); - vector column(1, -1); - int state = 0; - for (int i=0; i<(int)notations.size(); i++) { - if (state) { - switch (notations[i]) { - case 'p': - case 'm': - case 'f': - dynamics.back() += notations[i]; - break; - default: - state = 0; - dynamics.resize(dynamics.size() + 1); - } - } else { - switch (notations[i]) { - case 'p': - case 'm': - case 'f': - state = 1; - dynamics.back() = notations[i]; - column.back() = i; - break; - } - } +void Tool_musicxml2hum::insertOffsetFiguredBassIntoMeasure(GridMeasure* gm) { + if (m_offsetFiguredBass.empty()) { + return; } - bool setdynamics = false; - vector ps; - HumRegex hre; - for (int i=0; i<(int)dynamics.size(); i++) { - if (dynamics[i].empty()) { + bool beginQ = true; + for (auto it = gm->begin(); it != gm->end(); ++it) { + GridSlice* gs = *it; + if (!gs->isNoteSlice()) { + // Only attached harmony to data lines. continue; } - mr.getPrintSuggestions(ps, column[i]+32); - if (ps.size() > 0) { - cerr << "\tPRINT SUGGESTION: " << ps[0] << endl; - // only checking the first entry (first parameter): - if (hre.search(ps[0], "Y(-?\\d+)")) { - int y = hre.getMatchInt(1); - cerr << "Y = " << y << endl; + HumNum timestamp = gs->getTimestamp(); + for (int i=0; i<(int)m_offsetFiguredBass.size(); i++) { + if (m_offsetFiguredBass[i].token == NULL) { + continue; + } + if (m_offsetFiguredBass[i].timestamp == timestamp) { + // this is the slice to insert the harmony + gs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token); + m_offsetFiguredBass[i].token = NULL; + } else if (m_offsetFiguredBass[i].timestamp < timestamp) { + if (beginQ) { + cerr << "Error: Cannot insert harmony " << m_offsetFiguredBass[i].token + << " at timestamp " << m_offsetFiguredBass[i].timestamp + << " since first timestamp in measure is " << timestamp << endl; + } else { + m_forceRecipQ = true; + // go back to previous note line and insert + // new slice to store the harmony token + auto tempit = it; + tempit--; + while (tempit != gm->end()) { + if ((*tempit)->getTimestamp() == (*it)->getTimestamp()) { + tempit--; + continue; + } + int partcount = (int)(*tempit)->size(); + tempit++; + GridSlice* newgs = new GridSlice(gm, m_offsetFiguredBass[i].timestamp, + SliceType::Notes, partcount); + newgs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token); + gm->insert(tempit, newgs); + m_offsetFiguredBass[i].token = NULL; + break; + } + } } } - - slice->at(part)->setDynamics(dynamics[i]); - setdynamics = true; - break; // only one dynamic allowed (at least for now) - } - - if (setdynamics) { - HumGrid* grid = slice->getOwner(); - if (grid) { - grid->setDynamicsPresent(part); - } + beginQ = false; } -} - - - -////////////////////////////// -// -// Tool_musedata2hum::setTimeSigDurInfo -- -// - -void Tool_musedata2hum::setTimeSigDurInfo(const string& ktimesig) { - HumRegex hre; - if (hre.search(ktimesig, "(\\d+)/(\\d+)")) { - int top = hre.getMatchInt(1); - int bot = hre.getMatchInt(2); - HumNum value = 1; - value /= bot; - value *= top; - value.invert(); - value *= 4; // convert from whole notes to quarter notes - m_timesigdur = value; + // If there are still valid harmonies in the input list, apppend + // them to the end of the measure. + for (int i=0; i<(int)m_offsetFiguredBass.size(); i++) { + if (m_offsetFiguredBass[i].token == NULL) { + continue; + } + m_forceRecipQ = true; + int partcount = (int)gm->back()->size(); + GridSlice* newgs = new GridSlice(gm, m_offsetFiguredBass[i].timestamp, + SliceType::Notes, partcount); + newgs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token); + gm->insert(gm->end(), newgs); + m_offsetFiguredBass[i].token = NULL; } + m_offsetFiguredBass.clear(); } ////////////////////////////// // -// Tool_musedata2hum::getMeasure -- Could be imporoved by NlogN search. +// Tool_musicxml2hum::insertOffsetHarmonyIntoMeasure -- // -GridMeasure* Tool_musedata2hum::getMeasure(HumGrid& outdata, HumNum starttime) { - for (int i=0; i<(int)outdata.size(); i++) { - if (outdata[i]->getTimestamp() == starttime) { - return outdata[i]; +void Tool_musicxml2hum::insertOffsetHarmonyIntoMeasure(GridMeasure* gm) { + if (offsetHarmony.empty()) { + return; + } + // the offsetHarmony list should probably be time sorted first, and then + // iterate through the slices once. But there should not be many offset + bool beginQ = true; + for (auto it = gm->begin(); it != gm->end(); ++it) { + GridSlice* gs = *it; + if (!gs->isNoteSlice()) { + // Only attached harmony to data lines. + continue; + } + HumNum timestamp = gs->getTimestamp(); + for (int i=0; i<(int)offsetHarmony.size(); i++) { + if (offsetHarmony[i].token == NULL) { + continue; + } + if (offsetHarmony[i].timestamp == timestamp) { + // this is the slice to insert the harmony + gs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token); + offsetHarmony[i].token = NULL; + } else if (offsetHarmony[i].timestamp < timestamp) { + if (beginQ) { + cerr << "Error: Cannot insert harmony " << offsetHarmony[i].token + << " at timestamp " << offsetHarmony[i].timestamp + << " since first timestamp in measure is " << timestamp << endl; + } else { + m_forceRecipQ = true; + // go back to previous note line and insert + // new slice to store the harmony token + auto tempit = it; + tempit--; + while (tempit != gm->end()) { + if ((*tempit)->getTimestamp() == (*it)->getTimestamp()) { + tempit--; + continue; + } + int partcount = (int)(*tempit)->size(); + tempit++; + GridSlice* newgs = new GridSlice(gm, offsetHarmony[i].timestamp, + SliceType::Notes, partcount); + newgs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token); + gm->insert(tempit, newgs); + offsetHarmony[i].token = NULL; + break; + } + } + } } + beginQ = false; } - // Did not find measure in data, so append to end of list. - // Assuming that unknown measures are at a later timestamp - // than those in current list, but should fix this later perhaps. - GridMeasure* gm = new GridMeasure(&outdata); - outdata.push_back(gm); - return gm; + // If there are still valid harmonies in the input list, apppend + // them to the end of the measure. + for (int i=0; i<(int)offsetHarmony.size(); i++) { + if (offsetHarmony[i].token == NULL) { + continue; + } + m_forceRecipQ = true; + int partcount = (int)gm->back()->size(); + GridSlice* newgs = new GridSlice(gm, offsetHarmony[i].timestamp, + SliceType::Notes, partcount); + newgs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token); + gm->insert(gm->end(), newgs); + offsetHarmony[i].token = NULL; + } + offsetHarmony.clear(); } ////////////////////////////// // -// Tool_musedata2hum::setInitialOmd -- +// Tool_musicxml2hum::checkForDummyRests -- // -void Tool_musedata2hum::setInitialOmd(const string& omd) { - m_omd = omd; -} - - - -////////////////////////////// -// -// Tool_musedata2hum::cleanString -- -// +void Tool_musicxml2hum::checkForDummyRests(MxmlMeasure* measure) { + vector& events = measure->getEventList(); -string Tool_musedata2hum::cleanString(const string& input) { - return MuseData::cleanString(input); -} + MxmlPart* owner = measure->getOwner(); + int maxstaff = owner->getStaffCount(); + vector > itemcounts(maxstaff); + for (int i=0; i<(int)itemcounts.size(); i++) { + itemcounts[i].resize(1); + itemcounts[i][0] = 0; + } + for (int i=0; i<(int)events.size(); i++) { + if (!nodeType(events[i]->getNode(), "note")) { + // only counting notes/(rests) for now. may + // need to be counted. + continue; + } + int voiceindex = events[i]->getVoiceIndex(); + int staffindex = events[i]->getStaffIndex(); + if (voiceindex < 0) { + continue; + } + if (staffindex < 0) { + continue; + } + if (staffindex >= (int)itemcounts.size()) { + itemcounts.resize(staffindex+1); + } -////////////////////////////// -// -// Tool_musicxml2hum::Tool_musicxml2hum -- -// + if (voiceindex >= (int)itemcounts[staffindex].size()) { + int oldsize = (int)itemcounts[staffindex].size(); + int newsize = voiceindex + 1; + itemcounts[staffindex].resize(newsize); + for (int j=oldsize; jgetDuration(); + HumNum starttime = measure->getStartTime(); + measure->addDummyRest(starttime, mdur, i, j); + measure->forceLastInvisible(); + dummy = true; + } + } - define("r|recip=b", "output **recip spine"); - define("s|stems=b", "include stems in output"); + if (dummy) { + measure->sortEvents(); + } - VoiceDebugQ = false; - DebugQ = false; } ////////////////////////////// // -// Tool_musicxml2hum::convert -- Convert a MusicXML file into -// Humdrum content. +// Tool_musicxml2hum::convertNowEvents -- // -bool Tool_musicxml2hum::convertFile(ostream& out, const char* filename) { - xml_document doc; - auto result = doc.load_file(filename); - if (!result) { - cerr << "\nXML file [" << filename << "] has syntax errors\n"; - cerr << "Error description:\t" << result.description() << "\n"; - cerr << "Error offset:\t" << result.offset << "\n\n"; - exit(1); - } - - return convert(out, doc); -} - - -bool Tool_musicxml2hum::convert(ostream& out, istream& input) { - string s(istreambuf_iterator(input), {}); - return convert(out, s.c_str()); -} - +bool Tool_musicxml2hum::convertNowEvents(GridMeasure* outdata, + vector& nowevents, vector& nowparts, + HumNum nowtime, vector& partdata, vector& partstaves) { -bool Tool_musicxml2hum::convert(ostream& out, const char* input) { - xml_document doc; - auto result = doc.load_string(input); - if (!result) { - cout << "\nXML content has syntax errors\n"; - cout << "Error description:\t" << result.description() << "\n"; - cout << "Error offset:\t" << result.offset << "\n\n"; - exit(1); + if (nowevents.size() == 0) { + // cout << "NOW EVENTS ARE EMPTY" << endl; + return true; } - return convert(out, doc); -} - - + //if (0 && VoiceDebugQ) { + // for (int j=0; j<(int)nowevents.size(); j++) { + // vector nz = nowevents[j]->nonzerodur; + // for (int i=0; i<(int)nz.size(); i++) { + // cerr << "NOWEVENT NZ NAME: " << nz[i]->getElementName() + // << "<\t" << nz[i]->getKernPitch() << endl; + // } + // } + //} -bool Tool_musicxml2hum::convert(ostream& out, xml_document& doc) { - initialize(); + appendZeroEvents(outdata, nowevents, nowtime, partdata); - bool status = true; // for keeping track of problems in conversion process. + bool hasNonZeroDurElements = false; + for (const SimultaneousEvents* event : nowevents) { + if (event->nonzerodur.size() != 0) { + hasNonZeroDurElements = true; + break; + } + } + if (!hasNonZeroDurElements) { + // no duration events (should be a terminal barline) + // ignore and deal with in calling function. + return true; + } - setSoftwareInfo(doc); - vector partids; // list of part IDs - map partinfo; // mapping if IDs to score-part elements - map partcontent; // mapping of IDs to part elements + appendNonZeroEvents(outdata, nowevents, nowtime, partdata); - getPartInfo(partinfo, partids, doc); - m_used_hairpins.resize(partinfo.size()); + handleFiguredBassWithoutNonZeroEvent(nowevents, nowtime); - m_current_dynamic.resize(partids.size()); - m_current_brackets.resize(partids.size()); - m_current_figured_bass.resize(partids.size()); - m_stop_char.resize(partids.size(), "["); + return true; +} - getPartContent(partcontent, partids, doc); - vector partdata; - partdata.resize(partids.size()); - m_last_ottava_direction.resize(partids.size()); - fillPartData(partdata, partids, partinfo, partcontent); - // for debugging: - //printPartInfo(partids, partinfo, partcontent, partdata); +///////////////////////////// +// +// Tool_musicxml2hum::handleFiguredBassWithoutNonZeroEvent -- +// - m_maxstaff = 0; - // check the voice info - for (int i=0; i<(int)partdata.size(); i++) { - partdata[i].prepareVoiceMapping(); - m_maxstaff += partdata[i].getStaffCount(); - // for debugging: - if (VoiceDebugQ) { - partdata[i].printStaffVoiceInfo(); +void Tool_musicxml2hum::handleFiguredBassWithoutNonZeroEvent(vector& nowevents, HumNum nowtime) { + vector nonZeroParts; + vector floatingFiguredBass; + for (const SimultaneousEvents* sevent : nowevents) { + for (MxmlEvent* mxmlEvent : sevent->nonzerodur) { + nonZeroParts.push_back(mxmlEvent->getPartIndex()); } - } - - // re-index voices to disallow empty intermediate voices. - reindexVoices(partdata); - - HumGrid outdata; - status &= stitchParts(outdata, partids, partinfo, partcontent, partdata); - - if (outdata.size() > 2) { - if (outdata.at(0)->getDuration() == 0) { - while (!outdata.at(0)->empty()) { - outdata.at(1)->push_front(outdata.at(0)->back()); - outdata.at(0)->pop_back(); + for (MxmlEvent* mxmlEvent : sevent->zerodur) { + if ("figured-bass" == mxmlEvent->getElementName()) { + if (std::find(nonZeroParts.begin(), nonZeroParts.end(), mxmlEvent->getPartIndex()) == nonZeroParts.end()) { + // cerr << mxmlEvent->getNode() << "\n"; + string fstring = getFiguredBassString(mxmlEvent->getNode()); + HTp ftok = new HumdrumToken(fstring); + MusicXmlFiguredBassInfo finfo; + finfo.timestamp = nowtime; + finfo.partindex = mxmlEvent->getPartIndex(); + finfo.token = ftok; + m_offsetFiguredBass.push_back(finfo); + // cerr << "ADD FLOATING FB NUM " << fstring << " " << nowtime << "\n"; + } } - outdata.deleteMeasure(0); } } +} - for (int i=0; i<(int)partdata.size(); i++) { - m_hasOrnamentsQ |= partdata[i].hasOrnaments(); - } - outdata.removeRedundantClefChanges(); - outdata.removeSibeliusIncipit(); - m_systemDecoration = getSystemDecoration(doc, outdata, partids); - // tranfer verse counts from parts/staves to HumGrid: - // should also do part verse counts here (-1 staffindex). - int versecount; - for (int p=0; p<(int)partdata.size(); p++) { - for (int s=0; s& nowevents, HumNum nowtime, + vector& partdata) { - // transfer dynamics boolean for part to HumGrid - for (int p = 0; p<(int)partdata.size(); p++) { - bool dynstate = partdata[p].hasDynamics(); - if (dynstate) { - outdata.setDynamicsPresent(p); + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::Notes); + if (outdata->empty()) { + outdata->push_back(slice); + } else { + HumNum lasttime = outdata->back()->getTimestamp(); + if (nowtime >= lasttime) { + outdata->push_back(slice); + } else { + // travel backwards in the measure until the correct + // time position is found. + auto it = outdata->rbegin(); + while (it != outdata->rend()) { + lasttime = (*it)->getTimestamp(); + if (nowtime >= lasttime) { + outdata->insert(it.base(), slice); + break; + } + it++; + } } } + slice->initializePartStaves(partdata); - // transfer figured bass boolean for part to HumGrid - for (int p=0; p<(int)partdata.size(); p++) { - bool fbstate = partdata[p].hasFiguredBass(); - if (fbstate) { - outdata.setFiguredBassPresent(p); - // break; + for (int i=0; i<(int)nowevents.size(); i++) { + vector& events = nowevents[i]->nonzerodur; + for (int j=0; j<(int)events.size(); j++) { + addEvent(slice, outdata, events[j], nowtime); } } +} - if (m_recipQ || m_forceRecipQ) { - outdata.enableRecipSpine(); - } - - outdata.buildSingleList(); - outdata.expandLocalCommentLayers(); - - // set the duration of the last slice - HumdrumFile outfile; - outdata.transferTokens(outfile); - addHeaderRecords(outfile, doc); - addFooterRecords(outfile, doc); +////////////////////////////// +// +// Tool_musicxml2hum::addEvent -- Add a note or rest. +// - Tool_ruthfix ruthfix; - ruthfix.run(outfile); +void Tool_musicxml2hum::addEvent(GridSlice* slice, GridMeasure* outdata, MxmlEvent* event, + HumNum nowtime) { + int partindex; // which part the event occurs in + int staffindex; // which staff the event occurs in (need to fix) + int voiceindex; // which voice the event occurs in (use for staff) - addMeasureOneNumber(outfile); + partindex = event->getPartIndex(); + staffindex = event->getStaffIndex(); + voiceindex = event->getVoiceIndex(); - // Maybe implement barnum tool and apply here based on options. + string recip; + string pitch; + string prefix; + string postfix; + bool invisible = false; + bool primarynote = true; + vector slurdirs; - Tool_chord chord; - chord.run(outfile); + if (!event->isFloating()) { + recip = event->getRecip(); + HumRegex hre; + if (hre.search(recip, "(\\d+)%(\\d+)(\\.*)")) { + int first = hre.getMatchInt(1); + int second = hre.getMatchInt(2); + string dots = hre.getMatch(3); + if (dots.empty()) { + if ((first == 1) && (second == 2)) { + hre.replaceDestructive(recip, "0", "1%2"); + } + if ((first == 1) && (second == 4)) { + hre.replaceDestructive(recip, "00", "1%4"); + } + if ((first == 1) && (second == 3)) { + hre.replaceDestructive(recip, "0.", "1%3"); + } + if ((first == 2) && (second == 3)) { + hre.replaceDestructive(recip, "1.", "2%3"); + } + } else { + if ((first == 1) && (second == 2)) { + string original = "1%2" + dots; + string replacement = "0" + dots; + hre.replaceDestructive(recip, replacement, original); + } + } + } - if (m_hasOrnamentsQ) { - Tool_trillspell trillspell; - trillspell.run(outfile); - } + pitch = event->getKernPitch(); + prefix = event->getPrefixNoteInfo(); + postfix = event->getPostfixNoteInfo(primarynote, recip); + if (postfix.find("@") != string::npos) { + m_hasTremoloQ = true; + } + bool grace = event->isGrace(); + int slurstarts = event->hasSlurStart(slurdirs); + int slurstops = event->hasSlurStop(); - if (m_hasTremoloQ) { - Tool_tremolo tremolo; - tremolo.run(outfile); - } + if (pitch.find('r') != std::string::npos) { + string restpitch = event->getRestPitch(); + pitch += restpitch; + } - if (m_software == "sibelius") { - // Needed at least for Sibelius 19.5/Dolet 6.6 for Sibelius - // where grace note groups are not beamed in the MusicXML export. - Tool_autobeam gracebeam; - vector argv; - argv.push_back("autobeam"); // name of program (placeholder) - argv.push_back("-g"); // beam adjacent grace notes - gracebeam.process(argv); - // Need to force a reparsing of the files contents to - // analyze strands. For now just create a temporary - // Humdrum file to force the analysis of the strands. - stringstream sstream; - sstream << outfile; - HumdrumFile outfile2; - outfile2.readString(sstream.str()); - gracebeam.run(outfile2); - outfile = outfile2; - } + for (int i=0; i 0) { + // prefix.insert(1, ">"); + // m_slurabove++; + // } else if (slurdir < 0) { + // prefix.insert(1, "<"); + // m_slurbelow++; + // } + // } + // } + } + for (int i=0; i argv; - argv.push_back("transpose"); // name of program (placeholder) - argv.push_back("-C"); // transpose to concert pitch - transpose.process(argv); - stringstream sstream; - sstream << outfile; - HumdrumFile outfile2; - outfile2.readString(sstream.str()); - transpose.run(outfile2); - if (transpose.hasHumdrumText()) { - stringstream ss; - transpose.getHumdrumText(ss); - outfile.readString(ss.str()); - printResult(out, outfile); + invisible = isInvisible(event); + if (event->isInvisible()) { + invisible = true; } - } else { - for (int i=0; igetEmbeddedDuration(modification, event->getNode()) / 4; + if (dur.getNumerator() == 1) { + recip = to_string(dur.getDenominator()) + "q"; + } else { + recip = "q"; + } + if (!event->hasGraceSlash()) { + recip += "q"; + } } - printResult(out, outfile); } - // add RDFs - if (m_slurabove || m_staffabove) { - out << "!!!RDF**kern: > = above" << endl; - } - if (m_slurbelow || m_staffbelow) { - out << "!!!RDF**kern: < = below" << endl; + if (event->getCrossStaffOffset() > 0) { + m_staffbelow = true; + } else if (event->getCrossStaffOffset() < 0) { + m_staffabove = true; } - for (int i=0; i<(int)partdata.size(); i++) { - if (partdata[i].hasEditorialAccidental()) { - out << "!!!RDF**kern: i = editorial accidental" << endl; - break; + stringstream ss; + if (event->isFloating()) { + ss << "."; + HTp token = new HumdrumToken(ss.str()); + slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token, + event->getDuration()); + } else { + ss << prefix << recip << pitch << postfix; + if (invisible) { + ss << "yy"; } - } - - // put the above code in here some time: - prepareRdfs(partdata); - printRdfs(out); - return status; -} + // check for chord notes. + HTp token; + if (event->isChord()) { + addSecondaryChordNotes(ss, event, recip); + token = new HumdrumToken(ss.str()); + slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token, + event->getDuration()); + } else { + token = new HumdrumToken(ss.str()); + slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token, + event->getDuration()); + } + } + if (DebugQ) { + cerr << "!!TOKEN: " << ss.str(); + cerr << "\tTS: " << event->getStartTime(); + cerr << "\tDUR: " << event->getDuration(); + cerr << "\tSTn: " << event->getStaffNumber(); + cerr << "\tVn: " << event->getVoiceNumber(); + cerr << "\tSTi: " << event->getStaffIndex(); + cerr << "\tVi: " << event->getVoiceIndex(); + cerr << "\teNAME: " << event->getElementName(); + cerr << endl; + } + int vcount = addLyrics(slice->at(partindex)->at(staffindex), event); -////////////////////////////// -// -// Tool_musicxml2hum::addMeasureOneNumber -- For the first measure if it occurs before -// the first data, change = to =1. Maybe check next measure for a number and -// addd one less than that number instead of 1. -// + if (vcount > 0) { + event->reportVerseCountToOwner(staffindex, vcount); + } -void Tool_musicxml2hum::addMeasureOneNumber(HumdrumFile& infile) { - for (int i=0; iat(partindex), event, nowtime, partindex); + if (hcount > 0) { + event->reportHarmonyCountToOwner(hcount); + } - string newvalue = "="; - if (value.size() < 2) { - newvalue += "1"; - } else if (value[1] != '=') { - newvalue += "1"; - newvalue += value.substr(1); - } - token->setText(newvalue); + int fcount = addFiguredBass(slice->at(partindex), event, nowtime, partindex); + if (fcount > 0) { + event->reportFiguredBassToOwner(); + } - // Add "1" to other spines here: - for (int j=1; jsetText(newvalue); + if (m_current_brackets[partindex].size() > 0) { + for (int i=0; i<(int)m_current_brackets[partindex].size(); i++) { + event->setBracket(m_current_brackets[partindex].at(i)); } - break; + m_current_brackets[partindex].clear(); + addBrackets(slice, outdata, event, nowtime, partindex); } -} + if (m_current_text.size() > 0) { + event->setTexts(m_current_text); + m_current_text.clear(); + addTexts(slice, outdata, event->getPartIndex(), staffindex, voiceindex, event); + } + if (m_current_tempo.size() > 0) { + event->setTempos(m_current_tempo); + m_current_tempo.clear(); + addTempos(slice, outdata, event->getPartIndex(), staffindex, voiceindex, event); + } -////////////////////////////// -// -// Tool_musicxml2hum::printResult -- filter out -// some item if not necessary: -// -// MuseScore calls everything "Piano" by default, so suppress -// this instrument name if there is only one **kern spine in -// the file. -// + if (m_current_dynamic[partindex].size()) { + // only processing the first dynamic at the current time point for now. + // Fix later so that multiple dynamics are handleded in the part at the + // same time. The LO parameters for multiple dynamics will need to be + // qualified with "n=#". + for (int i=0; i<(int)m_current_dynamic[partindex].size(); i++) { + event->setDynamics(m_current_dynamic[partindex][i]); + string dparam = getDynamicsParameters(m_current_dynamic[partindex][i]); -void Tool_musicxml2hum::printResult(ostream& out, HumdrumFile& outfile) { - vector kernspines = outfile.getKernSpineStartList(); - if (kernspines.size() > 1) { - out << outfile; - } else { - for (int i=0; ireportDynamicToOwner(); + addDynamic(slice->at(partindex), event, partindex); + if (dparam != "") { + // deal with multiple layout entries here... + GridMeasure *gm = slice->getMeasure(); + string fullparam = "!LO:DY" + dparam; + if (gm) { + gm->addDynamicsLayoutParameters(slice, partindex, fullparam); } } - if (isPianoLabel || isPianoAbbr || isStaffNum || isPartNum) { - continue; - } - out << outfile[i] << "\n"; } + m_current_dynamic[partindex].clear(); } -} + // see if a hairpin ending needs to be added before end of measure: + xml_node enode = event->getHairpinEnding(); + if (enode) { + event->reportDynamicToOwner(); // shouldn't be necessary + addHairpinEnding(slice->at(partindex), event, partindex); + // shouldn't need dynamics layout parameter + } + if (m_post_note_text.empty()) { + return; + } -////////////////////////////// -// -// Tool_musicxml2hum::printRdfs -- -// + // check the text buffer for text which needs to be moved + // after the current note. + string index; + index = to_string(partindex); + index += ' '; + index += to_string(staffindex); + index += ' '; + index += to_string(voiceindex); -void Tool_musicxml2hum::printRdfs(ostream& out) { - if (!m_caesura_rdf.empty()) { - out << m_caesura_rdf << "\n"; + auto it = m_post_note_text.find(index); + if (it == m_post_note_text.end()) { + // There is text waiting, but not for this note + // (for some strange reason). + return; + } + vector& tnodes = it->second; + for (int i=0; i<(int)tnodes.size(); i++) { + addText(slice, outdata, partindex, staffindex, voiceindex, tnodes[i], true); } + m_post_note_text.erase(it); } ////////////////////////////// // -// Tool_muisicxml2hum::setSoftwareInfo -- Store which software program generated the -// MusicXML data to handle locale variants. There can be more than one -// entry, so desired information is not necessarily in the first one. +// Tool_musicxml2hum::addTexts -- Add all text direction for a note. // -void Tool_musicxml2hum::setSoftwareInfo(xml_document& doc) { - string xpath = "/score-partwise/identification/encoding/software"; - string software = doc.select_node(xpath.c_str()).node().child_value(); - HumRegex hre; - if (hre.search(software, "sibelius", "i")) { - m_software = "sibelius"; +void Tool_musicxml2hum::addTexts(GridSlice* slice, GridMeasure* measure, int partindex, + int staffindex, int voiceindex, MxmlEvent* event) { + vector>& nodes = event->getTexts(); + for (auto item : nodes) { + int newpartindex = item.first; + int newstaffindex = 0; // Not allowing addressing text by layer (could be changed). + addText(slice, measure, newpartindex, newstaffindex, voiceindex, item.second, false); } } @@ -104145,239 +108295,349 @@ void Tool_musicxml2hum::setSoftwareInfo(xml_document& doc) { ////////////////////////////// // -// Tool_musicxml2hum::cleanSpaces -- Converts newlines and tabs to spaces, and removes -// trailing spaces from the string. Does not remove leading spaces, but this could -// be added. Another variation would be to use \n to encode newlines if they need -// to be preserved, but for now converting them to spaces. +// Tool_musicxml2hum::addTempos -- Add all text direction for a note. // -string& Tool_musicxml2hum::cleanSpaces(string& input) { - for (int i=0; i<(int)input.size(); i++) { - if (std::isspace(input[i])) { - input[i] = ' '; - } - } - while ((!input.empty()) && std::isspace(input.back())) { - input.resize(input.size() - 1); +void Tool_musicxml2hum::addTempos(GridSlice* slice, GridMeasure* measure, int partindex, + int staffindex, int voiceindex, MxmlEvent* event) { + vector>& nodes = event->getTempos(); + for (auto item : nodes) { + int newpartindex = item.first; + int newstaffindex = 0; // Not allowing addressing text by layer (could be changed). + addTempo(slice, measure, newpartindex, newstaffindex, voiceindex, item.second); } - return input; } ////////////////////////////// // -// Tool_musicxml2hum::cleanSpacesAndColons -- Converts newlines and -// tabs to spaces, and removes leading and trailing spaces from the -// string. Another variation would be to use \n to encode newlines -// if they need to be preserved, but for now converting them to spaces. -// Colons (:) are also converted to :. +// Tool_musicxml2hum::addBrackets -- +// +// +// +// +// +// +// 4 +// +// +// +// +// +// +// 5 +// +// -string Tool_musicxml2hum::cleanSpacesAndColons(const string& input) { - string output; - bool foundnonspace = false; - for (int i=0; i<(int)input.size(); i++) { - if (std::isspace(input[i])) { - if (!foundnonspace) { - output += ' '; - } +void Tool_musicxml2hum::addBrackets(GridSlice* slice, GridMeasure* measure, MxmlEvent* event, + HumNum nowtime, int partindex) { + int staffindex = 0; + int voiceindex = 0; + string token; + HumNum timestamp; + vector brackets = event->getBrackets(); + for (int i=0; i<(int)brackets.size(); i++) { + xml_node bracket = brackets[i].child("direction-type").child("bracket"); + if (!bracket) { + continue; } - if (input[i] == ':') { - foundnonspace = true; - output += ":"; + string linetype = bracket.attribute("line-type").as_string(); + string endtype = bracket.attribute("type").as_string(); + int number = bracket.attribute("number").as_int(); + if (endtype == "stop") { + linetype = m_bracket_type_buffer[number]; } else { - output += input[i]; - foundnonspace = true; + m_bracket_type_buffer[number] = linetype; + } + + if (linetype == "solid") { + if (endtype == "start") { + token = "*lig"; + measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, token); + } else if (endtype == "stop") { + token = "*Xlig"; + timestamp = nowtime + event->getDuration(); + measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, token, timestamp); + } + } else if (linetype == "dashed") { + if (endtype == "start") { + token = "*col"; + measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, token); + } else if (endtype == "stop") { + token = "*Xcol"; + timestamp = nowtime + event->getDuration(); + measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, token, timestamp); + } } } - while ((!output.empty()) && std::isspace(output.back())) { - output.resize(output.size() - 1); - } - return output; } ////////////////////////////// // -// Tool_musicxml2hum::addHeaderRecords -- Inserted in reverse order -// (last record inserted first). +// Tool_musicxml2hum::addText -- Add a text direction to the grid. +// +// +// +// Some Text +// +// +// +// Multi-line example: +// +// +// +// note +// with newline +// +// 2 +// // -void Tool_musicxml2hum::addHeaderRecords(HumdrumFile& outfile, xml_document& doc) { - string xpath; - HumRegex hre; - - if (!m_systemDecoration.empty()) { - // outfile.insertLine(0, "!!!system-decoration: " + m_systemDecoration); - if (m_systemDecoration != "s1") { - outfile.appendLine("!!!system-decoration: " + m_systemDecoration); +void Tool_musicxml2hum::addText(GridSlice* slice, GridMeasure* measure, int partindex, + int staffindex, int voiceindex, xml_node node, bool force) { + string placementstring; + xml_attribute placement = node.attribute("placement"); + if (placement) { + string value = placement.value(); + if (value == "above") { + placementstring = ":a"; + } else if (value == "below") { + placementstring = ":b"; } } - xpath = "/score-partwise/credit/credit-words"; - pugi::xpath_node_set credits = doc.select_nodes(xpath.c_str()); - map keys; - vector refs; - vector positions; // +1 = above, -1 = below; - for (auto it = credits.begin(); it != credits.end(); it++) { - string contents = cleanSpaces(it->node().child_value()); - if (contents.empty()) { - continue; - } - if ((contents[0] != '@') && (contents[0] != '!')) { - continue; - } + xml_node child = node.first_child(); + if (!child) { + return; + } + if (!nodeType(child, "direction-type")) { + return; + } - if (contents.size() >= 3) { - // If line starts with "@@" then place at end of score. - if ((contents[0] == '@') && (contents[1] == '@')) { - positions.push_back(-1); - } else { - positions.push_back(1); - } - } else { - positions.push_back(1); - } + xml_node grandchild = child.first_child(); + if (!grandchild) { + return; + } - if (hre.search(contents, "^[@!]+([^\\s]+):")) { - // reference record - string key = hre.getMatch(1); - keys[key] = 1; - hre.replaceDestructive(contents, "!!!", "^[!@]+"); - refs.push_back(contents); - } else { - // global comment - hre.replaceDestructive(contents, "!!", "^[!@]+"); - refs.push_back(contents); + xml_node sibling = grandchild; + + bool dyQ = false; + xml_attribute defaulty; + + string text; + while (sibling) { + if (nodeType(sibling, "words")) { + text += sibling.child_value(); + if (!dyQ) { + defaulty = sibling.attribute("default-y"); + if (defaulty) { + dyQ = true; + double number = std::stod(defaulty.value()); + if (number >= 0.0) { + placementstring = ":a"; + } else if (number < 0.0) { + placementstring = ":b"; + } + } + } } + sibling = sibling.next_sibling(); } - // OTL: title ////////////////////////////////////////////////////////// - - // Sibelius method - xpath = "/score-partwise/work/work-title"; - string worktitle = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value()); - string otl_record; - string omv_record; - bool worktitleQ = false; - if ((worktitle != "") && (worktitle != "Title")) { - otl_record = "!!!OTL: "; - otl_record += worktitle; - worktitleQ = true; + if (text == "") { + // Don't insert an empty text + return; } - xpath = "/score-partwise/movement-title"; - string mtitle = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value()); - if (mtitle != "") { - if (worktitleQ) { - omv_record = "!!!OMV: "; - omv_record += mtitle; - } else { - otl_record = "!!!OTL: "; - otl_record += mtitle; + // Mapping \n (0x0a) to newline (ignoring \r, (0x0d)) + string newtext; + for (int i=0; i<(int)text.size(); i++) { + switch (text[i]) { + case 0x0a: + newtext += "\\n"; + case 0x0d: + break; + default: + newtext += text[i]; } } + text = newtext; - // COM: composer ///////////////////////////////////////////////////////// - // CDT: composer's dates - xpath = "/score-partwise/identification/creator[@type='composer']"; - string composer = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value()); - string cdt_record; - if (composer != "") { - if (hre.search(composer, R"(\((.*?\d.*?)\))")) { - string dates = hre.getMatch(1); - // hre.replaceDestructive(composer, "", R"(\()" + dates + R"(\))"); - auto loc = composer.find(dates); - if (loc != std::string::npos) { - composer.replace(loc-1, dates.size()+2, ""); - } - hre.replaceDestructive(composer, "", R"(^\s+)"); - hre.replaceDestructive(composer, "", R"(\s+$)"); - if (hre.search(composer, R"(([^\s]+) +([^\s]+))")) { - composer = hre.getMatch(2) + ", " + hre.getMatch(1); - } - if (dates != "") { - if (hre.search(dates, R"(\b(\d{4})\?)")) { - string replacement = "~"; - replacement += hre.getMatch(1); - hre.replaceDestructive(dates, replacement, R"(\b\d{4}\?)"); - cdt_record = "!!!CDT: "; - cdt_record += dates; - } - } - } + // Remove newlines encodings at end of text. + HumRegex hre; + hre.replaceDestructive(text, "", "(\\\\n)+\\s*$"); + + /* Problem: these are also possibly signs for figured bass + if (text == "#") { + // interpret as an editorial sharp marker + setEditorialAccidental(+1, slice, partindex, staffindex, voiceindex); + return; + } else if (text == "b") { + // interpret as an editorial flat marker + setEditorialAccidental(-1, slice, partindex, staffindex, voiceindex); + return; + // } else if (text == u8"§") { + } else if (text == "\xc2\xa7") { + // interpret as an editorial natural marker + setEditorialAccidental(0, slice, partindex, staffindex, voiceindex); + return; } + */ + // + // The following code should be merged into the loop to apply + // font changes within the text. Internal formatting code for + // the string would need to be developed if so. For now, just + // the first word's style will be processed. + // - for (int i=(int)refs.size()-1; i>=0; i--) { - if (positions.at(i) > 0) { - // place at start of file - outfile.insertLine(0, refs[i]); - } - } + string stylestring; + bool italic = false; + bool bold = false; - for (int i=0; i<(int)refs.size(); i++) { - if (positions.at(i) < 0) { - // place at end of file - outfile.appendLine(refs[i]); + xml_attribute fontstyle = grandchild.attribute("font-style"); + if (fontstyle) { + string value = fontstyle.value(); + if (value == "italic") { + italic = true; } } - if ((!omv_record.empty()) && (!keys["OMV"])) { - outfile.insertLine(0, omv_record); - } - - if ((!otl_record.empty()) && (!keys["OTL"])) { - outfile.insertLine(0, otl_record); + xml_attribute fontweight = grandchild.attribute("font-weight"); + if (fontweight) { + string value = fontweight.value(); + if (value == "bold") { + bold = true; + } } - if ((!cdt_record.empty()) && (!keys["CDT"])) { - outfile.insertLine(0, cdt_record); + if (italic && bold) { + stylestring = ":Bi"; + } else if (italic) { + stylestring = ":i"; + } else if (bold) { + stylestring = ":B"; } - if ((!composer.empty()) && (!keys["COM"])) { - // Don't print composer name if it is "Composer". - if (composer != "Composer") { - string com_record = "!!!COM: " + composer; - outfile.insertLine(0, com_record); + bool interpQ = false; + bool specialQ = false; + bool globalQ = false; + bool afterQ = false; + string output; + if (text == "!") { + // null local comment + output = text; + specialQ = true; + } else if (text == "*") { + // null interpretation + output = text; + specialQ = true; + interpQ = true; + } else if ((text.size() > 1) && (text[0] == '*') && (text[1] != '*')) { + // regular tandem interpretation, but disallow manipulators: + if (text == "*^") { + specialQ = false; + } else if (text == "*+") { + specialQ = false; + } else if (text == "*-") { + specialQ = false; + } else if (text == "*v") { + specialQ = false; + } else { + specialQ = true; + interpQ = true; + output = text; + } + } else if ((text.size() > 2) && (text[0] == '*') && (text[1] == '*')) { + hre.replaceDestructive(text, "*", "^\\*+"); + output = text; + specialQ = true; + afterQ = true; + interpQ = true; + if (force == false) { + // store text for later processing after the next note in the data. + string index; + index += to_string(partindex); + index += ' '; + index += to_string(staffindex); + index += ' '; + index += to_string(voiceindex); + m_post_note_text[index].push_back(node); + return; } + } else if ((text.size() > 1) && (text[0] == '!') && (text[1] != '!')) { + // embedding a local comment + output = text; + specialQ = true; + } else if ((text.size() >= 2) && (text[0] == '!') && (text[1] == '!')) { + // embedding a global comment (or bibliographic record, etc.). + output = text; + globalQ = true; + specialQ = true; + } else if (hre.search(text, "\\s*problem\\s*:\\s*(.*)\\s*$")) { + specialQ = true; + output = "!LO:TX:t=P:problem:"; + output += hre.getMatch(1); + hre.replaceDestructive(output, "\\n", "\n", "g"); + hre.replaceDestructive(output, " ", "\t", "g"); } -} - - - -////////////////////////////// -// -// Tool_musicxml2hum::addFooterRecords -- -// + if (!specialQ) { + text = cleanSpacesAndColons(text); + if (text.empty()) { + // no text to display after removing whitespace + return; + } -void Tool_musicxml2hum::addFooterRecords(HumdrumFile& outfile, xml_document& doc) { + if (placementstring.empty()) { + // force above if no placement specified + placementstring = ":a"; + } - // YEM: copyright - string copy = doc.select_node("/score-partwise/identification/rights").node().child_value(); - bool validcopy = true; - if (copy == "") { - validcopy = false; - } - if ((copy.length() == 2) && ((unsigned char)copy[0] == 0xc2) && ((unsigned char)copy[1] == 0xa9)) { - validcopy = false; - } - if ((copy.find("opyright") != std::string::npos) && (copy.size() < 15)) { - validcopy = false; + output = "!LO:TX"; + output += placementstring; + output += stylestring; + output += ":t="; + output += text; } - if (validcopy) { - string yem_record = "!!!YEM: "; - yem_record += cleanSpaces(copy); - outfile.appendLine(yem_record); - } + // The text direction needs to be added before the last line + // in the measure object. If there is already an empty layout + // slice before the current one (with no spine manipulators + // in between), then insert onto the existing layout slice; + // otherwise create a new layout slice. - // RDF: - if (m_hasEditorial) { - string rdf_record = "!!!RDF**kern: i = editorial accidental"; - outfile.appendLine(rdf_record); + if (interpQ) { + if (afterQ) { + int voicecount = (int)slice->at(partindex)->at(staffindex)->size(); + if (voiceindex >= voicecount) { + // Adding voices in the new slice. It might be + // better to first check for a previous text line + // at the current timestamp that is empty (because there + // is text at the same time in another spine). + GridStaff* gs = slice->at(partindex)->at(staffindex); + gs->resize(voiceindex+1); + string null = slice->getNullTokenForSlice(); + for (int m=voicecount; mat(m) = new GridVoice(null, 0); + } + } + HTp token = slice->at(partindex)->at(staffindex)->at(voiceindex)->getToken(); + HumNum tokdur = Convert::recipToDuration(token); + HumNum timestamp = slice->getTimestamp() + tokdur; + measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, output, timestamp); + } else { + measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, output); + } + } else if (globalQ) { + HumNum timestamp = slice->getTimestamp(); + measure->addGlobalComment(text, timestamp); + } else { + // adding local comment that is not a layout parameter also goes here: + measure->addLayoutParameter(slice, partindex, output); } } @@ -104385,273 +108645,252 @@ void Tool_musicxml2hum::addFooterRecords(HumdrumFile& outfile, xml_document& doc ////////////////////////////// // -// initialize -- +// Tool_musicxml2hum::addTempo -- Add a tempo direction to the grid. // - -void Tool_musicxml2hum::initialize(void) { - m_recipQ = getBoolean("recip"); - m_stemsQ = getBoolean("stems"); - m_hasOrnamentsQ = false; -} - - - -////////////////////////////// +// +// +// +// half +// 80 +// +// +// +// // -// Tool_musicxml2hum::reindexVoices -- +// Dotted tempo example: // - -void Tool_musicxml2hum::reindexVoices(vector& partdata) { - for (int p=0; p<(int)partdata.size(); p++) { - for (int m=0; m<(int)partdata[p].getMeasureCount(); m++) { - MxmlMeasure* measure = partdata[p].getMeasure(m); - if (!measure) { - continue; - } - reindexMeasure(measure); - } - } -} - - - -////////////////////////////// +// +// +// +// quarter +// +// 80 +// +// +// +// // -// Tool_musicxml2hum::prepareRdfs -- // -void Tool_musicxml2hum::prepareRdfs(vector& partdata) { - string caesura; - for (int i=0; i<(int)partdata.size(); i++) { - caesura = partdata[i].getCaesura(); - if (!caesura.empty()) { +void Tool_musicxml2hum::addTempo(GridSlice* slice, GridMeasure* measure, int partindex, + int staffindex, int voiceindex, xml_node node) { + string placementstring; + xml_attribute placement = node.attribute("placement"); + if (placement) { + string value = placement.value(); + if (value == "above") { + placementstring = ":a"; + } else if (value == "below") { + placementstring = ":b"; + } else { + // force above if no explicit placement: + placementstring = ":a"; } } - if (!caesura.empty()) { - m_caesura_rdf = "!!!RDF**kern: " + caesura + " = caesura"; + xml_node child = node.first_child(); + if (!child) { + return; + } + if (!nodeType(child, "direction-type")) { + return; } -} - - + xml_node sound(NULL); + xml_node sibling = child; + while (sibling) { + if (nodeType(sibling, "sound")) { + sound = sibling; + break; + } + sibling = sibling.next_sibling(); + } -////////////////////////////// -// -// Tool_musicxml2hum::reindexMeasure -- -// + // grandchild should be (containing textual display) + // and which gives *MM data. + xml_node metronome(NULL); -void Tool_musicxml2hum::reindexMeasure(MxmlMeasure* measure) { - if (!measure) { + xml_node grandchild = child.first_child(); + if (!grandchild) { return; } + sibling = grandchild; - vector > staffVoiceCounts; - vector& elist = measure->getEventList(); - - for (int i=0; i<(int)elist.size(); i++) { - int staff = elist[i]->getStaffIndex(); - int voice = elist[i]->getVoiceIndex(); - - if ((voice >= 0) && (staff >= 0)) { - if (staff >= (int)staffVoiceCounts.size()) { - int newsize = staff + 1; - staffVoiceCounts.resize(newsize); - } - if (voice >= (int)staffVoiceCounts[staff].size()) { - int oldsize = (int)staffVoiceCounts[staff].size(); - int newsize = voice + 1; - staffVoiceCounts[staff].resize(newsize); - for (int i=oldsize; i > remapping; - remapping.resize(staffVoiceCounts.size()); - int reindex; - for (int i=0; i<(int)staffVoiceCounts.size(); i++) { - remapping[i].resize(staffVoiceCounts[i].size()); - reindex = 0; - for (int j=0; j<(int)remapping[i].size(); j++) { - if (remapping[i].size() == 1) { - remapping[i][j] = 0; - continue; - } - if (staffVoiceCounts[i][j]) { - remapping[i][j] = reindex++; - } else { - remapping[i][j] = -1; // invalidate voice - } - } + if (!beatunit) { + cerr << "Warning: missing beat-unit in tempo setting" << endl; + return; } - - // Go back and remap the voice indexes of elements. - // Presuming that the staff does not need to be reindex. - for (int i=0; i<(int)elist.size(); i++) { - int oldvoice = elist[i]->getVoiceIndex(); - int staff = elist[i]->getStaffIndex(); - if (oldvoice < 0) { - continue; - } - int newvoice = remapping[staff][oldvoice]; - if (newvoice == oldvoice) { - continue; - } - elist[i]->setVoiceIndex(newvoice); + if (!perminute) { + cerr << "Warning: missing per-minute in tempo setting" << endl; + return; } -} - + int staff = 0; + int voice = 0; + if (sound) { + string mmtok = "*MM"; + double mmv = stod(mmvalue); + double mmi = int(mmv + 0.001); + if (fabs(mmv - mmi) < 0.01) { + stringstream sstream; + sstream << mmi; + mmtok += sstream.str(); + } else { + mmtok += mmvalue; + } + HumNum timestamp = slice->getTimestamp(); + measure->addTempoToken(mmtok, timestamp, partindex, staff, voice, m_maxstaff); + } -////////////////////////////// -// -// Tool_musicxml2hum::setOptions -- -// + string butext = beatunit.child_value(); + string pmtext = perminute.child_value(); + string stylestring; -void Tool_musicxml2hum::setOptions(int argc, char** argv) { - m_options.process(argc, argv); -} + // create textual tempo marking + string text; + text = "["; + text += butext; + if (beatunitdot) { + text += "-dot"; + } + text += "]"; + text += "="; + text += pmtext; + string output = "!LO:TX"; + output += placementstring; + output += stylestring; + output += ":t="; + output += text; -void Tool_musicxml2hum::setOptions(const vector& argvlist) { - m_options.process(argvlist); + // The text direction needs to be added before the last line in the measure object. + // If there is already an empty layout slice before the current one (with no spine manipulators + // in between), then insert onto the existing layout slice; otherwise create a new layout slice. + measure->addTempoToken(slice, partindex, output); } ////////////////////////////// // -// Tool_musicxml2hum::getOptionDefinitions -- Used to avoid -// duplicating the definitions in the test main() function. +// setEditorialAccidental -- // -Options Tool_musicxml2hum::getOptionDefinitions(void) { - return m_options; -} - - -/////////////////////////////////////////////////////////////////////////// - - -////////////////////////////// -// -// Tool_musicxml2hum::fillPartData -- -// +void Tool_musicxml2hum::setEditorialAccidental(int accidental, GridSlice* slice, + int partindex, int staffindex, int voiceindex) { -bool Tool_musicxml2hum::fillPartData(vector& partdata, - const vector& partids, map& partinfo, - map& partcontent) { + HTp tok = slice->at(partindex)->at(staffindex)->at(voiceindex)->getToken(); - bool output = true; - for (int i=0; i<(int)partinfo.size(); i++) { - partdata[i].setPartNumber(i+1); - output &= fillPartData(partdata[i], partids[i], partinfo[partids[i]], - partcontent[partids[i]]); + if ((accidental < 0) && (tok->find("-") == string::npos)) { + cerr << "Editorial error for " << tok << ": no flat to mark" << endl; + return; } - return output; -} - - -bool Tool_musicxml2hum::fillPartData(MxmlPart& partdata, - const string& id, xml_node partdeclaration, xml_node partcontent) { - if (m_stemsQ) { - partdata.enableStems(); + if ((accidental > 0) && (tok->find("#") == string::npos)) { + cerr << "Editorial error for " << tok << ": no sharp to mark" << endl; + return; + } + if ((accidental == 0) && + ((tok->find("#") != string::npos) || (tok->find("-") != string::npos))) { + cerr << "Editorial error for " << tok << ": requesting a natural accidental" << endl; + return; } - partdata.parsePartInfo(partdeclaration); - // m_last_ottava_direction.at(partdata.getPartIndex()).resize(partdata.getStaffCount()); - // staff count is incorrect at this point? Just assume 32 staves in the part, which should - // be 28-30 staffs too many. - m_last_ottava_direction.at(partdata.getPartIndex()).resize(32); + string newtok = *tok; - int count; - auto measures = partcontent.select_nodes("./measure"); - for (int i=0; i<(int)measures.size(); i++) { - partdata.addMeasure(measures[i].node()); - count = partdata.getMeasureCount(); - if (count > 1) { - HumNum dur = partdata.getMeasure(count-1)->getTimeSigDur(); - if (dur == 0) { - HumNum dur = partdata.getMeasure(count-2) - ->getTimeSigDur(); - if (dur > 0) { - partdata.getMeasure(count - 1)->setTimeSigDur(dur); - } + if (accidental == -1) { + auto loc = newtok.find("-"); + if (loc < newtok.size()) { + if (newtok[loc+1] == 'X') { + // replace explicit accidental with editorial accidental + newtok[loc+1] = 'i'; + tok->setText(newtok); + m_hasEditorial = 'i'; + } else { + // append i after -: + newtok.insert(loc+1, "i"); + tok->setText(newtok); + m_hasEditorial = 'i'; } } - + return; } - return true; -} - - -////////////////////////////// -// -// Tool_musicxml2hum::printPartInfo -- Debug information. -// - -void Tool_musicxml2hum::printPartInfo(vector& partids, - map& partinfo, map& partcontent, - vector& partdata) { - cout << "\nPart information in the file:" << endl; - int maxmeasure = 0; - for (int i=0; i<(int)partids.size(); i++) { - cout << "\tPART " << i+1 << " id = " << partids[i] << endl; - cout << "\tMAXSTAFF " << partdata[i].getStaffCount() << endl; - cout << "\t\tpart name:\t" - << getChildElementText(partinfo[partids[i]], "part-name") << endl; - cout << "\t\tpart abbr:\t" - << getChildElementText(partinfo[partids[i]], "part-abbreviation") - << endl; - auto node = partcontent[partids[i]]; - auto measures = node.select_nodes("./measure"); - cout << "\t\tMeasure count:\t" << measures.size() << endl; - if (maxmeasure < (int)measures.size()) { - maxmeasure = (int)measures.size(); + if (accidental == +1) { + auto loc = newtok.find("#"); + if (loc < newtok.size()) { + if (newtok[loc+1] == 'X') { + // replace explicit accidental with editorial accidental + newtok[loc+1] = 'i'; + tok->setText(newtok); + m_hasEditorial = 'i'; + } else { + // append i after -: + newtok.insert(loc+1, "i"); + tok->setText(newtok); + m_hasEditorial = 'i'; + } } - cout << "\t\tTotal duration:\t" << partdata[i].getDuration() << endl; + return; } - MxmlMeasure* measure; - for (int i=0; igetDuration(); - } - if (j < (int)partdata.size() - 1) { - cout << "\t"; + if (accidental == 0) { + auto loc = newtok.find("n"); + if (loc < newtok.size()) { + if (newtok[loc+1] == 'X') { + // replace explicit accidental with editorial accidental + newtok[loc+1] = 'i'; + tok->setText(newtok); + m_hasEditorial = 'i'; + } else { + // append i after -: + newtok.insert(loc+1, "i"); + tok->setText(newtok); + m_hasEditorial = 'i'; } + } else { + // no natural sign, so add it after any pitch classes. + HumRegex hre; + hre.search(newtok, R"(([a-gA-G]+))"); + string diatonic = hre.getMatch(1); + string newacc = diatonic + "i"; + hre.replaceDestructive(newtok, newacc, diatonic); + tok->setText(newtok); + m_hasEditorial = 'i'; } - cout << endl; + return; } } @@ -104659,172 +108898,203 @@ void Tool_musicxml2hum::printPartInfo(vector& partids, ////////////////////////////// // -// Tool_musicxml2hum::insertPartNames -- +// Tool_musicxml2hum::addDynamic -- extract any dynamics for the event +// +// Such as: +// +// +// +// +// +// +// +// +// +// Hairpins: +// +// +// +// +// +// +// +// +// +// +// // -void Tool_musicxml2hum::insertPartNames(HumGrid& outdata, vector& partdata) { +void Tool_musicxml2hum::addDynamic(GridPart* part, MxmlEvent* event, int partindex) { + vector directions = event->getDynamics(); + if (directions.empty()) { + return; + } - bool hasname = false; - bool hasabbr = false; + HTp tok = NULL; - for (int i=0; i<(int)partdata.size(); i++) { - string value; - value = partdata[i].getPartName(); - if (!value.empty()) { - hasname = true; - break; + for (int i=0; i<(int)directions.size(); i++) { + xml_node direction = directions[i]; + xml_attribute placement = direction.attribute("placement"); + bool above = false; + if (placement) { + string value = placement.value(); + if (value == "above") { + above = true; + } } - } - - for (int i=0; i<(int)partdata.size(); i++) { - string value; - value = partdata[i].getPartAbbr(); - if (!value.empty()) { - hasabbr = true; - break; + xml_node child = direction.first_child(); + if (!child) { + continue; + } + if (!nodeType(child, "direction-type")) { + continue; + } + xml_node grandchild = child.first_child(); + if (!grandchild) { + continue; } - } - - if (!(hasabbr || hasname)) { - return; - } - - GridMeasure* gm; - if (outdata.empty()) { - gm = new GridMeasure(&outdata); - outdata.push_back(gm); - } else { - gm = outdata[0]; - } - int maxstaff; + if (!(nodeType(grandchild, "dynamics") || nodeType(grandchild, "wedge"))) { + continue; + } - if (hasabbr) { - for (int i=0; i<(int)partdata.size(); i++) { - string partabbr = partdata[i].getPartAbbr(); - if (partabbr.empty()) { + if (nodeType(grandchild, "dynamics")) { + xml_node dynamic = grandchild.first_child(); + if (!dynamic) { continue; } - string abbr = "*I'" + partabbr; - maxstaff = outdata.getStaffCount(i); - gm->addLabelAbbrToken(abbr, 0, i, maxstaff-1, 0, (int)partdata.size(), maxstaff); - } - } + string dstring = getDynamicString(dynamic); + if (!tok) { + tok = new HumdrumToken(dstring); + } else { + string oldtext = tok->getText(); + string newtext = oldtext + " " + dstring; + tok->setText(newtext); + } + } else if ( nodeType(grandchild, "wedge")) { + xml_node hairpin = grandchild; - if (hasname) { - for (int i=0; i<(int)partdata.size(); i++) { - string partname = partdata[i].getPartName(); - if (partname.empty()) { + if (isUsedHairpin(hairpin, partindex)) { + // need to suppress wedge ending if already used in [[ or ]] continue; } - if (partname.find("MusicXML") != string::npos) { - // ignore Finale dummy part names + if (!hairpin) { + cerr << "Warning: Expecting a hairpin, but found nothing" << endl; continue; } - if (partname.find("Part_") != string::npos) { - // ignore SharpEye dummy part names - continue; + string hstring = getHairpinString(hairpin, partindex); + if (!tok) { + tok = new HumdrumToken(hstring); + } else { + string oldtext = tok->getText(); + string newtext = oldtext + " " + hstring; + tok->setText(newtext); } - if (partname.find("Unnamed") != string::npos) { - // ignore Sibelius dummy part names - continue; + + // Deal here with adding an index if there is more than one hairpin. + if ((hstring != "[") && (hstring != "]") && above) { + tok->setValue("LO", "HP", "a", "true"); } - string name = "*I\"" + partname; - maxstaff = outdata.getStaffCount(i); - gm->addLabelToken(name, 0, i, maxstaff-1, 0, (int)partdata.size(), maxstaff); } } - + if (tok) { + part->setDynamics(tok); + } } ////////////////////////////// // -// Tool_musicxml2hum::stitchParts -- Merge individual parts into a -// single score sequence. +// Tool_musicxml::isUsedHairpin -- Needed to avoid double-insertion +// of hairpins which were stored before a barline so that they +// are not also repeated on the first beat of the next barline. +// This fuction will remove the hairpin from the used array +// when it is checked. The used array is only for storing +// hairpins that end on measures, so in theory there should not +// be too many, and they will be removed fairly quickly. // -bool Tool_musicxml2hum::stitchParts(HumGrid& outdata, - vector& partids, map& partinfo, - map& partcontent, vector& partdata) { - if (partdata.size() == 0) { - return false; - } - - int i; - int measurecount = partdata[0].getMeasureCount(); - // i used to start at 1 for some strange reason. - for (i=0; i<(int)partdata.size(); i++) { - if (measurecount != partdata[i].getMeasureCount()) { - cerr << "ERROR: cannot handle parts with different measure\n"; - cerr << "counts yet. Compare MM" << measurecount << " to MM"; - cerr << partdata[i].getMeasureCount() << endl; - exit(1); +bool Tool_musicxml2hum::isUsedHairpin(xml_node hairpin, int partindex) { + for (int i=0; i<(int)m_used_hairpins.at(partindex).size(); i++) { + if (hairpin == m_used_hairpins.at(partindex).at(i)) { + // Cannot delete yet: the hairpin endings are being double accessed somewhere. + //m_used_hairpins[partindex].erase(m_used_hairpins[partindex].begin() + i); + return true; } } - - vector partstaves(partdata.size(), 0); - for (i=0; i<(int)partstaves.size(); i++) { - partstaves[i] = partdata[i].getStaffCount(); - } - - bool status = true; - int m; - for (m=0; m +// +// +// +// // -void Tool_musicxml2hum::moveBreaksToEndOfPreviousMeasure(HumGrid& outdata) { - for (int i=1; i<(int)outdata.size(); i++) { - GridMeasure* gm = outdata[i]; - GridMeasure* gmlast = outdata[i-1]; - if (!gm || !gmlast) { - continue; - } - if (gm->begin() == gm->end()) { - // empty measure +void Tool_musicxml2hum::addHairpinEnding(GridPart* part, MxmlEvent* event, int partindex) { + + xml_node direction = event->getHairpinEnding(); + if (!direction) { + return; + } + + xml_node child = direction.first_child(); + if (!child) { + return; + } + if (!nodeType(child, "direction-type")) { + return; + } + xml_node grandchild = child.first_child(); + if (!grandchild) { + return; + } + + if (!nodeType(grandchild, "wedge")) { + return; + } + + if (nodeType(grandchild, "wedge")) { + xml_node hairpin = grandchild; + if (!hairpin) { return; } - GridSlice *firstit = *(gm->begin()); - HumNum starttime = firstit->getTimestamp(); - for (auto it = gm->begin(); it != gm->end(); it++) { - HumNum time2 = (*it)->getTimestamp(); - if (time2 > starttime) { - break; - } - if (!(*it)->isGlobalComment()) { - continue; - } - HTp token = (*it)->at(0)->at(0)->at(0)->getToken(); - if (!token) { - continue; - } - if ((*token == "!!linebreak:original") || - (*token == "!!pagebreak:original")) { - GridSlice *swapper = *it; - gm->erase(it); - gmlast->push_back(swapper); - // there can be only one break, so quit the loop now. - break; + string hstring = getHairpinString(hairpin, partindex); + if (hstring == "[") { + hstring = "[["; + } else if (hstring == "]") { + hstring = "]]"; + } + m_used_hairpins.at(partindex).push_back(hairpin); + HTp current = part->getDynamics(); + if (!current) { + HTp htok = new HumdrumToken(hstring); + part->setDynamics(htok); + } else { + string text = current->getText(); + text += " "; + text += hstring; + // Set single-note crescendos + if (text == "< [[") { + text = "<["; + } else if (text == "> ]]") { + text = ">]"; + } else if (text == "< [") { + text = "<["; + } else if (text == "> ]") { + text = ">]"; } + current->setText(text); } } } @@ -104833,1432 +109103,1188 @@ void Tool_musicxml2hum::moveBreaksToEndOfPreviousMeasure(HumGrid& outdata) { ////////////////////////////// // -// Tool_musicxml2hum::cleanupMeasures -- -// Also add barlines here (keeping track of the -// duration of each measure). +// Tool_musicxml2hum::convertFiguredBassNumber -- // -void Tool_musicxml2hum::cleanupMeasures(HumdrumFile& outfile, - vector measures) { +string Tool_musicxml2hum::convertFiguredBassNumber(const xml_node& figure) { + string output; + xml_node fnum = figure.select_node("figure-number").node(); + // assuming one each of prefix/suffix: + xml_node prefixelement = figure.select_node("prefix").node(); + xml_node suffixelement = figure.select_node("suffix").node(); - HumdrumToken* token; - for (int i=0; iappendToken(token); - } - for (j=0; j<(int)partdata[i].getVerseCount(); j++) { - token = new HumdrumToken(common); - line->appendToken(token); - } + xml_attribute placement = element.attribute("placement"); + if (!placement) { + return output; + } + string value = placement.value(); + if (value == "above") { + output = ":a"; + } + xml_node child = element.first_child(); + if (!child) { + return output; + } + if (!nodeType(child, "direction-type")) { + return output; + } + xml_node grandchild = child.first_child(); + if (!grandchild) { + return output; + } + if (!nodeType(grandchild, "wedge")) { + return output; } - outfile.appendLine(line); + + xml_attribute wtype = grandchild.attribute("type"); + if (!wtype) { + return output; + } + string value2 = wtype.value(); + if (value2 == "stop") { + // don't apply parameters to ends of hairpins. + output = ""; + } + + return output; } ////////////////////////////// // -// Tool_musicxml2hum::insertMeasure -- +// Tool_musicxml2hum::getFiguredBassParameters -- Already presumed to be +// figured bass. // -bool Tool_musicxml2hum::insertMeasure(HumGrid& outdata, int mnum, - vector& partdata, vector partstaves) { - - GridMeasure* gm = outdata.addMeasureToBack(); - - MxmlMeasure* xmeasure; - vector measuredata; - vector* > sevents; - int i; - - for (i=0; i<(int)partdata.size(); i++) { - xmeasure = partdata[i].getMeasure(mnum); - measuredata.push_back(xmeasure); - if (i==0) { - gm->setDuration(partdata[i].getMeasure(mnum)->getDuration()); - gm->setTimestamp(partdata[i].getMeasure(mnum)->getTimestamp()); - gm->setTimeSigDur(partdata[i].getMeasure(mnum)->getTimeSigDur()); - } - checkForDummyRests(xmeasure); - sevents.push_back(xmeasure->getSortedEvents()); - if (i == 0) { - // only checking measure style of first barline - gm->setBarStyle(xmeasure->getBarStyle()); - } +string Tool_musicxml2hum::getFiguredBassParameters(xml_node element) { + string output; + if (!nodeType(element, "figured-bass")) { + return output; } + return output; +} - vector curtime(partdata.size()); - vector measuredurs(partdata.size()); - vector curindex(partdata.size(), 0); // assuming data in a measure... - HumNum nexttime = -1; - - vector> endingDirections(partdata.size()); - HumNum tsdur; - for (i=0; i<(int)curtime.size(); i++) { - tsdur = measuredata[i]->getTimeSigDur(); - if ((tsdur == 0) && (i > 0)) { - tsdur = measuredata[i-1]->getTimeSigDur(); - measuredata[i]->setTimeSigDur(tsdur); - } - // Keep track of hairpin endings that should be attached - // the the previous note (and doubling the ending marker - // to indicate that the timestamp of the ending is at the - // end rather than the start of the note. - vector& events = measuredata[i]->getEventList(); - xml_node hairpin = xml_node(NULL); - for (int j=(int)events.size() - 1; j >= 0; j--) { - if (events[j]->getElementName() == "note") { - if (hairpin) { - events[j]->setHairpinEnding(hairpin); - hairpin = xml_node(NULL); - } - break; - } else if (events[j]->getElementName() == "direction") { - stringstream ss; - ss.str(""); - events[j]->getNode().print(ss); - if (ss.str().find("wedge") != string::npos) { - if (ss.str().find("stop") != string::npos) { - hairpin = events[j]->getNode(); - } - } - } - } +////////////////////////////// +// +// Tool_musicxml2hum::getHairpinString -- +// +// Hairpins: +// +// +// +// +// +// +// +// +// +// +// +// - if (VoiceDebugQ) { - for (int j=0; j<(int)events.size(); j++) { - cerr << "!!ELEMENT: "; - cerr << "\tTIME: " << events[j]->getStartTime(); - cerr << "\tSTi: " << events[j]->getStaffIndex(); - cerr << "\tVi: " << events[j]->getVoiceIndex(); - cerr << "\tTS: " << events[j]->getStartTime(); - cerr << "\tDUR: " << events[j]->getDuration(); - cerr << "\tPITCH: " << events[j]->getKernPitch(); - cerr << "\tNAME: " << events[j]->getElementName(); - cerr << endl; - } - cerr << "======================================" << endl; +string Tool_musicxml2hum::getHairpinString(xml_node element, int partindex) { + if (nodeType(element, "wedge")) { + xml_attribute wtype = element.attribute("type"); + if (!wtype) { + return "???"; } - if (!(*sevents[i]).empty()) { - curtime[i] = (*sevents[i])[curindex[i]].starttime; + string output; + string wstring = wtype.value(); + if (wstring == "diminuendo") { + m_stop_char.at(partindex) = "]"; + output = ">"; + } else if (wstring == "crescendo") { + m_stop_char.at(partindex) = "["; + output = "<"; + } else if (wstring == "stop") { + output = m_stop_char.at(partindex); } else { - curtime[i] = tsdur; - } - if (nexttime < 0) { - nexttime = curtime[i]; - } else if (curtime[i] < nexttime) { - nexttime = curtime[i]; + output = "???"; } - measuredurs[i] = measuredata[i]->getDuration(); + return output; } - bool allend = false; - vector nowevents; - vector nowparts; - bool status = true; + return "???"; +} - HumNum processtime = nexttime; - while (!allend) { - nowevents.resize(0); - nowparts.resize(0); - allend = true; - processtime = nexttime; - nexttime = -1; - for (i = (int)partdata.size()-1; i >= 0; i--) { - if (curindex[i] >= (int)(*sevents[i]).size()) { - continue; - } - if ((*sevents[i])[curindex[i]].starttime == processtime) { - SimultaneousEvents* thing = &(*sevents[i])[curindex[i]]; - nowevents.push_back(thing); - nowparts.push_back(i); - curindex[i]++; - } - if (curindex[i] < (int)(*sevents[i]).size()) { - allend = false; - if ((nexttime < 0) || - ((*sevents[i])[curindex[i]].starttime < nexttime)) { - nexttime = (*sevents[i])[curindex[i]].starttime; - } - } - } - status &= convertNowEvents(outdata.back(), - nowevents, nowparts, processtime, partdata, partstaves); +////////////////////////////// +// +// Tool_musicxml2hum::getDynamicString -- +// - // Remove all figured bass numbers for this nowtime so that they are not - // accidentally displayed in the next nowtime, which can currently - // happen if there are no nonzerodur events in the same part - for (int i=0; i<(int)m_current_figured_bass.size(); i++) { - m_current_figured_bass[i].clear(); - } - } +string Tool_musicxml2hum::getDynamicString(xml_node element) { - if (offsetHarmony.size() > 0) { - insertOffsetHarmonyIntoMeasure(outdata.back()); - } - if (m_offsetFiguredBass.size() > 0) { - insertOffsetFiguredBassIntoMeasure(outdata.back()); + if (nodeType(element, "f")) { + return "f"; + } else if (nodeType(element, "p")) { + return "p"; + } else if (nodeType(element, "mf")) { + return "mf"; + } else if (nodeType(element, "mp")) { + return "mp"; + } else if (nodeType(element, "ff")) { + return "ff"; + } else if (nodeType(element, "pp")) { + return "pp"; + } else if (nodeType(element, "sf")) { + return "sf"; + } else if (nodeType(element, "sfp")) { + return "sfp"; + } else if (nodeType(element, "sfpp")) { + return "sfpp"; + } else if (nodeType(element, "fp")) { + return "fp"; + } else if (nodeType(element, "rf")) { + return "rfz"; + } else if (nodeType(element, "rfz")) { + return "rfz"; + } else if (nodeType(element, "sfz")) { + return "sfz"; + } else if (nodeType(element, "sffz")) { + return "sffz"; + } else if (nodeType(element, "fz")) { + return "fz"; + } else if (nodeType(element, "fff")) { + return "fff"; + } else if (nodeType(element, "ppp")) { + return "ppp"; + } else if (nodeType(element, "ffff")) { + return "ffff"; + } else if (nodeType(element, "pppp")) { + return "pppp"; + } else { + return "???"; } - return status; } - ////////////////////////////// // -// Tool_musicxml2hum::insertOffsetFiguredBassIntoMeasure -- +// Tool_musicxml2hum::addFiguredBass -- +// +// Such as: +// +// +//
+// 0 +//
+//
+// or: +// +//
+// 5 +// backslash +//
+//
+// 2 +// cross +//
+//
+// +// +//
+// flat +//
+//
+// +// Case where there is more than one figure attached to a note: +// (notice element) +// +// +//
+// 6 +// +//
+// 2 +// +// // -void Tool_musicxml2hum::insertOffsetFiguredBassIntoMeasure(GridMeasure* gm) { - if (m_offsetFiguredBass.empty()) { - return; +int Tool_musicxml2hum::addFiguredBass(GridPart* part, MxmlEvent* event, HumNum nowtime, int partindex) { + if (m_current_figured_bass[partindex].empty()) { + return 0; } - bool beginQ = true; - for (auto it = gm->begin(); it != gm->end(); ++it) { - GridSlice* gs = *it; - if (!gs->isNoteSlice()) { - // Only attached harmony to data lines. + int dursum = 0; + for (int i=0; i<(int)m_current_figured_bass[partindex].size(); i++) { + xml_node fnode = m_current_figured_bass[partindex].at(i); + if (!fnode) { + // strange problem continue; } - HumNum timestamp = gs->getTimestamp(); - for (int i=0; i<(int)m_offsetFiguredBass.size(); i++) { - if (m_offsetFiguredBass[i].token == NULL) { - continue; - } - if (m_offsetFiguredBass[i].timestamp == timestamp) { - // this is the slice to insert the harmony - gs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token); - m_offsetFiguredBass[i].token = NULL; - } else if (m_offsetFiguredBass[i].timestamp < timestamp) { - if (beginQ) { - cerr << "Error: Cannot insert harmony " << m_offsetFiguredBass[i].token - << " at timestamp " << m_offsetFiguredBass[i].timestamp - << " since first timestamp in measure is " << timestamp << endl; - } else { - m_forceRecipQ = true; - // go back to previous note line and insert - // new slice to store the harmony token - auto tempit = it; - tempit--; - while (tempit != gm->end()) { - if ((*tempit)->getTimestamp() == (*it)->getTimestamp()) { - tempit--; - continue; - } - int partcount = (int)(*tempit)->size(); - tempit++; - GridSlice* newgs = new GridSlice(gm, m_offsetFiguredBass[i].timestamp, - SliceType::Notes, partcount); - newgs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token); - gm->insert(tempit, newgs); - m_offsetFiguredBass[i].token = NULL; - break; - } - } - } + string fstring = getFiguredBassString(fnode); + + HTp ftok = new HumdrumToken(fstring); + if (i == 0) { + part->setFiguredBass(ftok); + } else { + // store the figured bass for later handling at end of + // measure processing. + MusicXmlFiguredBassInfo finfo; + finfo.timestamp = dursum; + finfo.timestamp /= (int)event->getQTicks(); + finfo.timestamp += nowtime; + finfo.partindex = partindex; + finfo.token = ftok; + m_offsetFiguredBass.push_back(finfo); + } + if (i < (int)m_current_figured_bass[partindex].size() - 1) { + dursum += getFiguredBassDuration(fnode); } - beginQ = false; - } - // If there are still valid harmonies in the input list, apppend - // them to the end of the measure. - for (int i=0; i<(int)m_offsetFiguredBass.size(); i++) { - if (m_offsetFiguredBass[i].token == NULL) { - continue; - } - m_forceRecipQ = true; - int partcount = (int)gm->back()->size(); - GridSlice* newgs = new GridSlice(gm, m_offsetFiguredBass[i].timestamp, - SliceType::Notes, partcount); - newgs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token); - gm->insert(gm->end(), newgs); - m_offsetFiguredBass[i].token = NULL; } - m_offsetFiguredBass.clear(); -} - - + m_current_figured_bass[partindex].clear(); -////////////////////////////// -// -// Tool_musicxml2hum::insertOffsetHarmonyIntoMeasure -- -// + return 1; -void Tool_musicxml2hum::insertOffsetHarmonyIntoMeasure(GridMeasure* gm) { - if (offsetHarmony.empty()) { - return; - } - // the offsetHarmony list should probably be time sorted first, and then - // iterate through the slices once. But there should not be many offset - bool beginQ = true; - for (auto it = gm->begin(); it != gm->end(); ++it) { - GridSlice* gs = *it; - if (!gs->isNoteSlice()) { - // Only attached harmony to data lines. - continue; - } - HumNum timestamp = gs->getTimestamp(); - for (int i=0; i<(int)offsetHarmony.size(); i++) { - if (offsetHarmony[i].token == NULL) { - continue; - } - if (offsetHarmony[i].timestamp == timestamp) { - // this is the slice to insert the harmony - gs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token); - offsetHarmony[i].token = NULL; - } else if (offsetHarmony[i].timestamp < timestamp) { - if (beginQ) { - cerr << "Error: Cannot insert harmony " << offsetHarmony[i].token - << " at timestamp " << offsetHarmony[i].timestamp - << " since first timestamp in measure is " << timestamp << endl; - } else { - m_forceRecipQ = true; - // go back to previous note line and insert - // new slice to store the harmony token - auto tempit = it; - tempit--; - while (tempit != gm->end()) { - if ((*tempit)->getTimestamp() == (*it)->getTimestamp()) { - tempit--; - continue; - } - int partcount = (int)(*tempit)->size(); - tempit++; - GridSlice* newgs = new GridSlice(gm, offsetHarmony[i].timestamp, - SliceType::Notes, partcount); - newgs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token); - gm->insert(tempit, newgs); - offsetHarmony[i].token = NULL; - break; - } +/* deal with figured bass layout parameters?: + string fparam = getFiguredBassParameters(fnode); + if (fparam != "") { + GridMeasure *gm = slice->getMeasure(); + string fullparam = "!LO:FB" + fparam; + if (gm) { + gm->addFiguredBassLayoutParameters(slice, partindex, fullparam); } } } - beginQ = false; - } - // If there are still valid harmonies in the input list, apppend - // them to the end of the measure. - for (int i=0; i<(int)offsetHarmony.size(); i++) { - if (offsetHarmony[i].token == NULL) { - continue; - } - m_forceRecipQ = true; - int partcount = (int)gm->back()->size(); - GridSlice* newgs = new GridSlice(gm, offsetHarmony[i].timestamp, - SliceType::Notes, partcount); - newgs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token); - gm->insert(gm->end(), newgs); - offsetHarmony[i].token = NULL; - } - offsetHarmony.clear(); +*/ + } ////////////////////////////// // -// Tool_musicxml2hum::checkForDummyRests -- +// Tool_musicxml2hum::getFiguredBassString -- extract any figured bass string +// from XML node. // -void Tool_musicxml2hum::checkForDummyRests(MxmlMeasure* measure) { - vector& events = measure->getEventList(); - - MxmlPart* owner = measure->getOwner(); - int maxstaff = owner->getStaffCount(); - vector > itemcounts(maxstaff); - for (int i=0; i<(int)itemcounts.size(); i++) { - itemcounts[i].resize(1); - itemcounts[i][0] = 0; - } - - for (int i=0; i<(int)events.size(); i++) { - if (!nodeType(events[i]->getNode(), "note")) { - // only counting notes/(rests) for now. may - // need to be counted. - continue; - } - int voiceindex = events[i]->getVoiceIndex(); - int staffindex = events[i]->getStaffIndex(); - - if (voiceindex < 0) { - continue; - } - if (staffindex < 0) { - continue; - } +string Tool_musicxml2hum::getFiguredBassString(xml_node fnode) { + string output; - if (staffindex >= (int)itemcounts.size()) { - itemcounts.resize(staffindex+1); + // Parentheses can only enclose an entire figure stack, not + // individual numbers or accidentals on numbers in MusicXML, + // so apply an editorial mark for parentheses. + string editorial; + xml_attribute pattr = fnode.attribute("parentheses"); + if (pattr) { + string pval = pattr.value(); + if (pval == "yes") { + editorial = "i"; } + } + // There is no bracket for FB in musicxml (3.0). - if (voiceindex >= (int)itemcounts[staffindex].size()) { - int oldsize = (int)itemcounts[staffindex].size(); - int newsize = voiceindex + 1; - itemcounts[staffindex].resize(newsize); - for (int j=oldsize; jgetDuration(); - HumNum starttime = measure->getStartTime(); - measure->addDummyRest(starttime, mdur, i, j); - measure->forceLastInvisible(); - dummy = true; + HumRegex hre; + hre.replaceDestructive(output, "", R"(^\s+|\s+$)"); + + if (output.empty()) { + if (children.size()) { + cerr << "WARNING: figured bass string is empty but has " + << children.size() << " figure elements as children. " + << "The output has been replaced with \".\"" << endl; } + output = "."; } - if (dummy) { - measure->sortEvents(); - } + return output; + // HTp fbtok = new HumdrumToken(fbstring); + // part->setFiguredBass(fbtok); } ////////////////////////////// // -// Tool_musicxml2hum::convertNowEvents -- +// Tool_musicxml2hum::addHarmony -- // -bool Tool_musicxml2hum::convertNowEvents(GridMeasure* outdata, - vector& nowevents, vector& nowparts, - HumNum nowtime, vector& partdata, vector& partstaves) { - - if (nowevents.size() == 0) { - // cout << "NOW EVENTS ARE EMPTY" << endl; - return true; +int Tool_musicxml2hum::addHarmony(GridPart* part, MxmlEvent* event, HumNum nowtime, + int partindex) { + xml_node hnode = event->getHNode(); + if (!hnode) { + return 0; } - //if (0 && VoiceDebugQ) { - // for (int j=0; j<(int)nowevents.size(); j++) { - // vector nz = nowevents[j]->nonzerodur; - // for (int i=0; i<(int)nz.size(); i++) { - // cerr << "NOWEVENT NZ NAME: " << nz[i]->getElementName() - // << "<\t" << nz[i]->getKernPitch() << endl; - // } - // } - //} - - appendZeroEvents(outdata, nowevents, nowtime, partdata); - - bool hasNonZeroDurElements = false; - for (const SimultaneousEvents* event : nowevents) { - if (event->nonzerodur.size() != 0) { - hasNonZeroDurElements = true; - break; - } - } - if (!hasNonZeroDurElements) { - // no duration events (should be a terminal barline) - // ignore and deal with in calling function. - return true; + // fill in X with the harmony values from the node + string hstring = getHarmonyString(hnode); + int offset = getHarmonyOffset(hnode); + HTp htok = new HumdrumToken(hstring); + if (offset == 0) { + part->setHarmony(htok); + } else { + MusicXmlHarmonyInfo hinfo; + hinfo.timestamp = offset; + hinfo.timestamp /= (int)event->getQTicks(); + hinfo.timestamp += nowtime; + hinfo.partindex = partindex; + hinfo.token = htok; + offsetHarmony.push_back(hinfo); } - appendNonZeroEvents(outdata, nowevents, nowtime, partdata); - - handleFiguredBassWithoutNonZeroEvent(nowevents, nowtime); - - return true; + return 1; } -///////////////////////////// +////////////////////////////// // -// Tool_musicxml2hum::handleFiguredBassWithoutNonZeroEvent -- +// Tool_musicxml2hum::getHarmonyOffset -- +// +// +// C +// +// major-ninth +// +// E +// +// -8 +// // -void Tool_musicxml2hum::handleFiguredBassWithoutNonZeroEvent(vector& nowevents, HumNum nowtime) { - vector nonZeroParts; - vector floatingFiguredBass; - for (const SimultaneousEvents* sevent : nowevents) { - for (MxmlEvent* mxmlEvent : sevent->nonzerodur) { - nonZeroParts.push_back(mxmlEvent->getPartIndex()); - } - for (MxmlEvent* mxmlEvent : sevent->zerodur) { - if ("figured-bass" == mxmlEvent->getElementName()) { - if (std::find(nonZeroParts.begin(), nonZeroParts.end(), mxmlEvent->getPartIndex()) == nonZeroParts.end()) { - // cerr << mxmlEvent->getNode() << "\n"; - string fstring = getFiguredBassString(mxmlEvent->getNode()); - HTp ftok = new HumdrumToken(fstring); - MusicXmlFiguredBassInfo finfo; - finfo.timestamp = nowtime; - finfo.partindex = mxmlEvent->getPartIndex(); - finfo.token = ftok; - m_offsetFiguredBass.push_back(finfo); - // cerr << "ADD FLOATING FB NUM " << fstring << " " << nowtime << "\n"; - } - } +int Tool_musicxml2hum::getHarmonyOffset(xml_node hnode) { + if (!hnode) { + return 0; + } + xml_node child = hnode.first_child(); + if (!child) { + return 0; + } + while (child) { + if (nodeType(child, "offset")) { + return atoi(child.child_value()); } + child = child.next_sibling(); } + + return 0; } -///////////////////////////// +////////////////////////////// // -// Tool_musicxml2hum::appendNonZeroEvents -- +// Tool_musicxml2hum::getFiguredBaseDuration -- Needed for cases where there is more +// than one figure attached to a note. Return value is the integer of the duration +// element. If will need to be converted to quarter notes later. +// +// +//
+// 5 +//
+//
+// 3 +//
+// 2 <-- get this field if it exists. +//
// -void Tool_musicxml2hum::appendNonZeroEvents(GridMeasure* outdata, - vector& nowevents, HumNum nowtime, - vector& partdata) { - - GridSlice* slice = new GridSlice(outdata, nowtime, - SliceType::Notes); - if (outdata->empty()) { - outdata->push_back(slice); - } else { - HumNum lasttime = outdata->back()->getTimestamp(); - if (nowtime >= lasttime) { - outdata->push_back(slice); - } else { - // travel backwards in the measure until the correct - // time position is found. - auto it = outdata->rbegin(); - while (it != outdata->rend()) { - lasttime = (*it)->getTimestamp(); - if (nowtime >= lasttime) { - outdata->insert(it.base(), slice); - break; - } - it++; - } - } +int Tool_musicxml2hum::getFiguredBassDuration(xml_node fnode) { + if (!fnode) { + return 0; } - slice->initializePartStaves(partdata); - - for (int i=0; i<(int)nowevents.size(); i++) { - vector& events = nowevents[i]->nonzerodur; - for (int j=0; j<(int)events.size(); j++) { - addEvent(slice, outdata, events[j], nowtime); + xml_node child = fnode.first_child(); + if (!child) { + return 0; + } + while (child) { + if (nodeType(child, "duration")) { + return atoi(child.child_value()); } + child = child.next_sibling(); } + + return 0; } ////////////////////////////// // -// Tool_musicxml2hum::addEvent -- Add a note or rest. +// Tool_musicxml2hum::getHarmonyString -- +// +// +// C +// +// major-ninth +// +// E +// +// -8 +// +// +// For harmony labels from Musescore: +// +// +// +// C +// +// none +// +// +// Converts to: "V43" ignoring the root-step and kind contents +// if they are both "C" and "none". // -void Tool_musicxml2hum::addEvent(GridSlice* slice, GridMeasure* outdata, MxmlEvent* event, - HumNum nowtime) { - int partindex; // which part the event occurs in - int staffindex; // which staff the event occurs in (need to fix) - int voiceindex; // which voice the event occurs in (use for staff) - - partindex = event->getPartIndex(); - staffindex = event->getStaffIndex(); - voiceindex = event->getVoiceIndex(); - - string recip; - string pitch; - string prefix; - string postfix; - bool invisible = false; - bool primarynote = true; - vector slurdirs; - - if (!event->isFloating()) { - recip = event->getRecip(); - HumRegex hre; - if (hre.search(recip, "(\\d+)%(\\d+)(\\.*)")) { - int first = hre.getMatchInt(1); - int second = hre.getMatchInt(2); - string dots = hre.getMatch(3); - if (dots.empty()) { - if ((first == 1) && (second == 2)) { - hre.replaceDestructive(recip, "0", "1%2"); - } - if ((first == 1) && (second == 4)) { - hre.replaceDestructive(recip, "00", "1%4"); - } - if ((first == 1) && (second == 3)) { - hre.replaceDestructive(recip, "0.", "1%3"); - } - if ((first == 2) && (second == 3)) { - hre.replaceDestructive(recip, "1.", "2%3"); +string Tool_musicxml2hum::getHarmonyString(xml_node hnode) { + if (!hnode) { + return ""; + } + xml_node child = hnode.first_child(); + if (!child) { + return ""; + } + string root; + string kind; + string kindtext; + string bass; + int rootalter = 0; + int bassalter = 0; + xml_node grandchild; + while (child) { + if (nodeType(child, "root")) { + grandchild = child.first_child(); + while (grandchild) { + if (nodeType(grandchild, "root-step")) { + root = grandchild.child_value(); + } if (nodeType(grandchild, "root-alter")) { + rootalter = atoi(grandchild.child_value()); } - } else { - if ((first == 1) && (second == 2)) { - string original = "1%2" + dots; - string replacement = "0" + dots; - hre.replaceDestructive(recip, replacement, original); + grandchild = grandchild.next_sibling(); + } + } else if (nodeType(child, "kind")) { + kindtext = getAttributeValue(child, "text"); + kind = child.child_value(); + if (kind == "") { + kind = child.attribute("text").value(); + transform(kind.begin(), kind.end(), kind.begin(), ::tolower); + } + } else if (nodeType(child, "bass")) { + grandchild = child.first_child(); + while (grandchild) { + if (nodeType(grandchild, "bass-step")) { + bass = grandchild.child_value(); + } if (nodeType(grandchild, "bass-alter")) { + bassalter = atoi(grandchild.child_value()); } + grandchild = grandchild.next_sibling(); } } + child = child.next_sibling(); + } + stringstream ss; - pitch = event->getKernPitch(); - prefix = event->getPrefixNoteInfo(); - postfix = event->getPostfixNoteInfo(primarynote, recip); - if (postfix.find("@") != string::npos) { - m_hasTremoloQ = true; - } - bool grace = event->isGrace(); - int slurstarts = event->hasSlurStart(slurdirs); - int slurstops = event->hasSlurStop(); - - if (pitch.find('r') != std::string::npos) { - string restpitch = event->getRestPitch(); - pitch += restpitch; - } + if ((kind == "none") && (root == "C") && !kindtext.empty()) { + ss << kindtext; + string output = cleanSpaces(ss.str()); + return output; + } - for (int i=0; i 0) { - // prefix.insert(1, ">"); - // m_slurabove++; - // } else if (slurdir < 0) { - // prefix.insert(1, "<"); - // m_slurbelow++; - // } - // } - // } - } - for (int i=0; iisInvisible()) { - invisible = true; + if (rootalter > 0) { + for (int i=0; igetEmbeddedDuration(modification, event->getNode()) / 4; - if (dur.getNumerator() == 1) { - recip = to_string(dur.getDenominator()) + "q"; - } else { - recip = "q"; - } - if (!event->hasGraceSlash()) { - recip += "q"; - } + } else if (rootalter < 0) { + for (int i=0; i<-rootalter; i++) { + ss << "-"; } } - if (event->getCrossStaffOffset() > 0) { - m_staffbelow = true; - } else if (event->getCrossStaffOffset() < 0) { - m_staffabove = true; + if (root.size() && kind.size()) { + ss << " "; + } + ss << kind; + if (bass.size()) { + ss << "/"; } + ss << bass; - stringstream ss; - if (event->isFloating()) { - ss << "."; - HTp token = new HumdrumToken(ss.str()); - slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token, - event->getDuration()); - } else { - ss << prefix << recip << pitch << postfix; - if (invisible) { - ss << "yy"; + if (bassalter > 0) { + for (int i=0; iisChord()) { - addSecondaryChordNotes(ss, event, recip); - token = new HumdrumToken(ss.str()); - slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token, - event->getDuration()); - } else { - token = new HumdrumToken(ss.str()); - slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token, - event->getDuration()); + } else if (bassalter < 0) { + for (int i=0; i<-bassalter; i++) { + ss << "-"; } } - if (DebugQ) { - cerr << "!!TOKEN: " << ss.str(); - cerr << "\tTS: " << event->getStartTime(); - cerr << "\tDUR: " << event->getDuration(); - cerr << "\tSTn: " << event->getStaffNumber(); - cerr << "\tVn: " << event->getVoiceNumber(); - cerr << "\tSTi: " << event->getStaffIndex(); - cerr << "\tVi: " << event->getVoiceIndex(); - cerr << "\teNAME: " << event->getElementName(); - cerr << endl; - } + string output = cleanSpaces(ss.str()); + return output; +} - int vcount = addLyrics(slice->at(partindex)->at(staffindex), event); - if (vcount > 0) { - event->reportVerseCountToOwner(staffindex, vcount); - } - int hcount = addHarmony(slice->at(partindex), event, nowtime, partindex); - if (hcount > 0) { - event->reportHarmonyCountToOwner(hcount); - } +////////////////////////////// +// +// Tool_musicxml2hum::addLyrics -- +// - int fcount = addFiguredBass(slice->at(partindex), event, nowtime, partindex); - if (fcount > 0) { - event->reportFiguredBassToOwner(); +int Tool_musicxml2hum::addLyrics(GridStaff* staff, MxmlEvent* event) { + xml_node node = event->getNode(); + if (!node) { + return 0; } - - if (m_current_brackets[partindex].size() > 0) { - for (int i=0; i<(int)m_current_brackets[partindex].size(); i++) { - event->setBracket(m_current_brackets[partindex].at(i)); + HumRegex hre; + xml_node child = node.first_child(); + xml_node grandchild; + // int max; + int number = 0; + vector verses; + string syllabic; + string text; + while (child) { + if (!nodeType(child, "lyric")) { + child = child.next_sibling(); + continue; } - m_current_brackets[partindex].clear(); - addBrackets(slice, outdata, event, nowtime, partindex); - } - - if (m_current_text.size() > 0) { - event->setTexts(m_current_text); - m_current_text.clear(); - addTexts(slice, outdata, event->getPartIndex(), staffindex, voiceindex, event); - } - - if (m_current_tempo.size() > 0) { - event->setTempos(m_current_tempo); - m_current_tempo.clear(); - addTempos(slice, outdata, event->getPartIndex(), staffindex, voiceindex, event); + string value = child.attribute("number").value(); + if (hre.search(value, R"(verse(\d+))")) { + // Fix for Sibelius which uses number="part8verse5" format. + number = stoi(hre.getMatch(1)); + } else { + number = atoi(child.attribute("number").value()); + } + if (number > 100) { + cerr << "Error: verse number is too large: number" << endl; + return 0; + } + if (number == (int)verses.size() + 1) { + verses.push_back(child); + } else if ((number > 0) && (number < (int)verses.size())) { + // replace a verse for some reason. + verses[number-1] = child; + } else if (number > 0) { + int oldsize = (int)verses.size(); + int newsize = number; + verses.resize(newsize); + for (int i=oldsize; isetDynamics(m_current_dynamic[partindex][i]); - string dparam = getDynamicsParameters(m_current_dynamic[partindex][i]); - - event->reportDynamicToOwner(); - addDynamic(slice->at(partindex), event, partindex); - if (dparam != "") { - // deal with multiple layout entries here... - GridMeasure *gm = slice->getMeasure(); - string fullparam = "!LO:DY" + dparam; - if (gm) { - gm->addDynamicsLayoutParameters(slice, partindex, fullparam); + string finaltext; + string fontstyle; + HTp token; + for (int i=0; i<(int)verses.size(); i++) { + if (!verses[i]) { + // no verse so doing an empty slot. + } else { + child = verses[i].first_child(); + finaltext = ""; + while (child) { + if (nodeType(child, "syllabic")) { + syllabic = child.child_value(); + child = child.next_sibling(); + continue; + } else if (nodeType(child, "text")) { + fontstyle = child.attribute("font-style").value(); + text = cleanSpaces(child.child_value()); + if (fontstyle == "italic") { + text = "" + text + ""; + } + } else if (nodeType(child, "elision")) { + finaltext += " "; + child = child.next_sibling(); + continue; + } else { + // such as + child = child.next_sibling(); + continue; + } + // escape text which would otherwise be reinterpreated + // as Humdrum syntax. + if (!text.empty()) { + if (text[0] == '!') { + text.insert(0, 1, '\\'); + } else if (text[0] == '*') { + text.insert(0, 1, '\\'); + } + } + child = child.next_sibling(); + if (syllabic == "middle" ) { + finaltext += "-"; + finaltext += text; + finaltext += "-"; + } else if (syllabic == "end") { + finaltext += "-"; + finaltext += text; + } else if (syllabic == "begin") { + finaltext += text; + finaltext += "-"; + } else { + finaltext += text; } + syllabic.clear(); } } - m_current_dynamic[partindex].clear(); - } - // see if a hairpin ending needs to be added before end of measure: - xml_node enode = event->getHairpinEnding(); - if (enode) { - event->reportDynamicToOwner(); // shouldn't be necessary - addHairpinEnding(slice->at(partindex), event, partindex); - // shouldn't need dynamics layout parameter - } + if (finaltext.empty()) { + continue; + } + if (m_software == "sibelius") { + hre.replaceDestructive(finaltext, " ", "_", "g"); + } - if (m_post_note_text.empty()) { - return; + if (verses[i]) { + token = new HumdrumToken(finaltext); + staff->setVerse(i,token); + } else { + token = new HumdrumToken("."); + staff->setVerse(i,token); + } } - // check the text buffer for text which needs to be moved - // after the current note. - string index; - index = to_string(partindex); - index += ' '; - index += to_string(staffindex); - index += ' '; - index += to_string(voiceindex); - - auto it = m_post_note_text.find(index); - if (it == m_post_note_text.end()) { - // There is text waiting, but not for this note - // (for some strange reason). - return; - } - vector& tnodes = it->second; - for (int i=0; i<(int)tnodes.size(); i++) { - addText(slice, outdata, partindex, staffindex, voiceindex, tnodes[i], true); - } - m_post_note_text.erase(it); + return (int)staff->getVerseCount(); } ////////////////////////////// // -// Tool_musicxml2hum::addTexts -- Add all text direction for a note. +// cleanSpaces -- remove trailing and leading spaces from text. +// Also removed doubled spaces, and converts tabs and newlines +// into spaces. // -void Tool_musicxml2hum::addTexts(GridSlice* slice, GridMeasure* measure, int partindex, - int staffindex, int voiceindex, MxmlEvent* event) { - vector>& nodes = event->getTexts(); - for (auto item : nodes) { - int newpartindex = item.first; - int newstaffindex = 0; // Not allowing addressing text by layer (could be changed). - addText(slice, measure, newpartindex, newstaffindex, voiceindex, item.second, false); +string Tool_musicxml2hum::cleanSpaces(const string& input) { + int endi = (int)input.size() - 1; + while (endi >= 0) { + if (isspace(input[endi])) { + endi--; + continue; + } + break; + } + int starti = 0; + while (starti <= endi) { + if (isspace(input[starti])) { + starti++; + continue; + } + break; + + } + string output; + for (int i=starti; i<=endi; i++) { + if (!isspace(input[i])) { + output += input[i]; + continue; + } + output += " "; + i++; + while ((i < endi) && isspace(input[i])) { + i++; + } + i--; + } + if ((output.size() == 3) && ((unsigned char)output[0] == 0xee) && + ((unsigned char)output[1] == 0x95) && ((unsigned char)output[2] == 0x91)) { + // MuseScore elision character: + // + output = " "; } + + return output; } ////////////////////////////// // -// Tool_musicxml2hum::addTempos -- Add all text direction for a note. +// Tool_musicxml2hum::isInvisible -- // -void Tool_musicxml2hum::addTempos(GridSlice* slice, GridMeasure* measure, int partindex, - int staffindex, int voiceindex, MxmlEvent* event) { - vector>& nodes = event->getTempos(); - for (auto item : nodes) { - int newpartindex = item.first; - int newstaffindex = 0; // Not allowing addressing text by layer (could be changed). - addTempo(slice, measure, newpartindex, newstaffindex, voiceindex, item.second); +bool Tool_musicxml2hum::isInvisible(MxmlEvent* event) { + xml_node node = event->getNode(); + if (!node) { + return false; + } + if (strcmp(node.attribute("print-object").value(), "no") == 0) { + return true; } + + return false; } ////////////////////////////// // -// Tool_musicxml2hum::addBrackets -- -// -// -// -// -// -// -// 4 -// -// -// -// -// -// -// 5 -// +// Tool_musicxml2hum::addSecondaryChordNotes -- // -void Tool_musicxml2hum::addBrackets(GridSlice* slice, GridMeasure* measure, MxmlEvent* event, - HumNum nowtime, int partindex) { - int staffindex = 0; - int voiceindex = 0; - string token; - HumNum timestamp; - vector brackets = event->getBrackets(); - for (int i=0; i<(int)brackets.size(); i++) { - xml_node bracket = brackets[i].child("direction-type").child("bracket"); - if (!bracket) { - continue; - } - string linetype = bracket.attribute("line-type").as_string(); - string endtype = bracket.attribute("type").as_string(); - int number = bracket.attribute("number").as_int(); - if (endtype == "stop") { - linetype = m_bracket_type_buffer[number]; - } else { - m_bracket_type_buffer[number] = linetype; - } +void Tool_musicxml2hum::addSecondaryChordNotes(ostream& output, + MxmlEvent* head, const string& recip) { + vector links = head->getLinkedNotes(); + MxmlEvent* note; + string pitch; + string prefix; + string postfix; + int slurstarts = 0; + int slurstops = 0; + vector slurdirs; - if (linetype == "solid") { - if (endtype == "start") { - token = "*lig"; - measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, token); - } else if (endtype == "stop") { - token = "*Xlig"; - timestamp = nowtime + event->getDuration(); - measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, token, timestamp); - } - } else if (linetype == "dashed") { - if (endtype == "start") { - token = "*col"; - measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, token); - } else if (endtype == "stop") { - token = "*Xcol"; - timestamp = nowtime + event->getDuration(); - measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, token, timestamp); + bool primarynote = false; + for (int i=0; i<(int)links.size(); i++) { + note = links.at(i); + pitch = note->getKernPitch(); + prefix = note->getPrefixNoteInfo(); + postfix = note->getPostfixNoteInfo(primarynote, recip); + slurstarts = note->hasSlurStart(slurdirs); + slurstops = note->hasSlurStop(); + + // or maybe walk backwards in the following loop? + for (int i=0; i 0) { + prefix.insert(1, ">"); + m_slurabove++; + } else if (slurdirs[i] < 0) { + prefix.insert(1, "<"); + m_slurbelow++; } } + for (int i=0; i 0) { + // prefix.insert(1, ">"); + // m_slurabove++; + // } else if (slurdir < 0) { + // prefix.insert(1, "<"); + // m_slurbelow++; + // } + // } + //} + //if (slurstop) { + // postfix.push_back(')'); + //} + + output << " " << prefix << recip << pitch << postfix; } } -////////////////////////////// -// -// Tool_musicxml2hum::addText -- Add a text direction to the grid. -// -// -// -// Some Text -// -// -// -// Multi-line example: +///////////////////////////// // -// -// -// note -// with newline -// -// 2 -// +// Tool_musicxml2hum::appendZeroEvents -- // -void Tool_musicxml2hum::addText(GridSlice* slice, GridMeasure* measure, int partindex, - int staffindex, int voiceindex, xml_node node, bool force) { - string placementstring; - xml_attribute placement = node.attribute("placement"); - if (placement) { - string value = placement.value(); - if (value == "above") { - placementstring = ":a"; - } else if (value == "below") { - placementstring = ":b"; - } - } - - xml_node child = node.first_child(); - if (!child) { - return; - } - if (!nodeType(child, "direction-type")) { - return; - } - - xml_node grandchild = child.first_child(); - if (!grandchild) { - return; - } - - xml_node sibling = grandchild; - - bool dyQ = false; - xml_attribute defaulty; - - string text; - while (sibling) { - if (nodeType(sibling, "words")) { - text += sibling.child_value(); - if (!dyQ) { - defaulty = sibling.attribute("default-y"); - if (defaulty) { - dyQ = true; - double number = std::stod(defaulty.value()); - if (number >= 0.0) { - placementstring = ":a"; - } else if (number < 0.0) { - placementstring = ":b"; - } - } - } - } - sibling = sibling.next_sibling(); - } +void Tool_musicxml2hum::appendZeroEvents(GridMeasure* outdata, + vector& nowevents, HumNum nowtime, + vector& partdata) { - if (text == "") { - // Don't insert an empty text - return; - } + bool hasclef = false; + bool haskeysig = false; + bool haskeydesignation = false; + bool hastransposition = false; + bool hastimesig = false; + bool hasottava = false; + bool hasstafflines = false; - // Mapping \n (0x0a) to newline (ignoring \r, (0x0d)) - string newtext; - for (int i=0; i<(int)text.size(); i++) { - switch (text[i]) { - case 0x0a: - newtext += "\\n"; - case 0x0d: - break; - default: - newtext += text[i]; - } - } - text = newtext; + vector> clefs(partdata.size()); + vector> keysigs(partdata.size()); + vector> transpositions(partdata.size()); + vector> timesigs(partdata.size()); + vector>> ottavas(partdata.size()); + vector> hairpins(partdata.size()); + vector> stafflines(partdata.size()); - // Remove newlines encodings at end of text. - HumRegex hre; - hre.replaceDestructive(text, "", "(\\\\n)+\\s*$"); + vector>>> gracebefore(partdata.size()); + vector>>> graceafter(partdata.size()); + bool foundnongrace = false; - /* Problem: these are also possibly signs for figured bass - if (text == "#") { - // interpret as an editorial sharp marker - setEditorialAccidental(+1, slice, partindex, staffindex, voiceindex); - return; - } else if (text == "b") { - // interpret as an editorial flat marker - setEditorialAccidental(-1, slice, partindex, staffindex, voiceindex); - return; - // } else if (text == u8"§") { - } else if (text == "\xc2\xa7") { - // interpret as an editorial natural marker - setEditorialAccidental(0, slice, partindex, staffindex, voiceindex); - return; - } - */ + int pindex = 0; + xml_node child; + xml_node grandchild; - // - // The following code should be merged into the loop to apply - // font changes within the text. Internal formatting code for - // the string would need to be developed if so. For now, just - // the first word's style will be processed. - // + for (int i=0; i<(int)nowevents.size(); i++) { + for (int j=0; j<(int)nowevents[i]->zerodur.size(); j++) { + xml_node element = nowevents[i]->zerodur[j]->getNode(); + pindex = nowevents[i]->zerodur[j]->getPartIndex(); - string stylestring; - bool italic = false; - bool bold = false; + if (nodeType(element, "attributes")) { + child = element.first_child(); + while (child) { + if (nodeType(child, "clef")) { + clefs[pindex].push_back(child); + hasclef = true; + foundnongrace = true; + } - xml_attribute fontstyle = grandchild.attribute("font-style"); - if (fontstyle) { - string value = fontstyle.value(); - if (value == "italic") { - italic = true; - } - } + if (nodeType(child, "key")) { + keysigs[pindex].push_back(child); + haskeysig = true; + string xpath = "mode"; + string mode = child.select_node(xpath.c_str()).node().child_value(); + if (mode != "") { + haskeydesignation = true; + } + foundnongrace = true; + } - xml_attribute fontweight = grandchild.attribute("font-weight"); - if (fontweight) { - string value = fontweight.value(); - if (value == "bold") { - bold = true; - } - } + if (nodeType(child, "transpose")) { + transpositions[pindex].push_back(child); + hastransposition = true; + foundnongrace = true; + } - if (italic && bold) { - stylestring = ":Bi"; - } else if (italic) { - stylestring = ":i"; - } else if (bold) { - stylestring = ":B"; - } + if (nodeType(child, "staff-details")) { + grandchild = child.first_child(); + while (grandchild) { + if (nodeType(grandchild, "staff-lines")) { + stafflines[pindex].push_back(grandchild); + hasstafflines = true; + } + grandchild = grandchild.next_sibling(); + } + } - bool interpQ = false; - bool specialQ = false; - bool globalQ = false; - bool afterQ = false; - string output; - if (text == "!") { - // null local comment - output = text; - specialQ = true; - } else if (text == "*") { - // null interpretation - output = text; - specialQ = true; - interpQ = true; - } else if ((text.size() > 1) && (text[0] == '*') && (text[1] != '*')) { - // regular tandem interpretation, but disallow manipulators: - if (text == "*^") { - specialQ = false; - } else if (text == "*+") { - specialQ = false; - } else if (text == "*-") { - specialQ = false; - } else if (text == "*v") { - specialQ = false; - } else { - specialQ = true; - interpQ = true; - output = text; - } - } else if ((text.size() > 2) && (text[0] == '*') && (text[1] == '*')) { - hre.replaceDestructive(text, "*", "^\\*+"); - output = text; - specialQ = true; - afterQ = true; - interpQ = true; - if (force == false) { - // store text for later processing after the next note in the data. - string index; - index += to_string(partindex); - index += ' '; - index += to_string(staffindex); - index += ' '; - index += to_string(voiceindex); - m_post_note_text[index].push_back(node); - return; + if (nodeType(child, "time")) { + timesigs[pindex].push_back(child); + hastimesig = true; + foundnongrace = true; + } + child = child.next_sibling(); + } + } else if (nodeType(element, "direction")) { + // direction -> direction-type -> words + // direction -> direction-type -> dynamics + // direction -> direction-type -> octave-shift + child = element.first_child(); + if (nodeType(child, "direction-type")) { + grandchild = child.first_child(); + if (nodeType(grandchild, "words")) { + m_current_text.emplace_back(std::make_pair(pindex, element)); + } else if (nodeType(grandchild, "metronome")) { + m_current_tempo.emplace_back(std::make_pair(pindex, element)); + } else if (nodeType(grandchild, "dynamics")) { + m_current_dynamic[pindex].push_back(element); + } else if (nodeType(grandchild, "octave-shift")) { + storeOttava(pindex, grandchild, element, ottavas); + hasottava = true; + } else if (nodeType(grandchild, "wedge")) { + m_current_dynamic[pindex].push_back(element); + } else if (nodeType(grandchild, "bracket")) { + m_current_brackets[pindex].push_back(element); + } + } + } else if (nodeType(element, "figured-bass")) { + m_current_figured_bass[pindex].push_back(element); + } else if (nodeType(element, "note")) { + if (foundnongrace) { + addEventToList(graceafter, nowevents[i]->zerodur[j]); + } else { + addEventToList(gracebefore, nowevents[i]->zerodur[j]); + } + } else if (nodeType(element, "print")) { + processPrintElement(outdata, element, nowtime); + } } - } else if ((text.size() > 1) && (text[0] == '!') && (text[1] != '!')) { - // embedding a local comment - output = text; - specialQ = true; - } else if ((text.size() >= 2) && (text[0] == '!') && (text[1] == '!')) { - // embedding a global comment (or bibliographic record, etc.). - output = text; - globalQ = true; - specialQ = true; - } else if (hre.search(text, "\\s*problem\\s*:\\s*(.*)\\s*$")) { - specialQ = true; - output = "!LO:TX:t=P:problem:"; - output += hre.getMatch(1); - hre.replaceDestructive(output, "\\n", "\n", "g"); - hre.replaceDestructive(output, " ", "\t", "g"); } - if (!specialQ) { - text = cleanSpacesAndColons(text); - if (text.empty()) { - // no text to display after removing whitespace - return; - } + addGraceLines(outdata, gracebefore, partdata, nowtime); - if (placementstring.empty()) { - // force above if no placement specified - placementstring = ":a"; - } + if (hasstafflines) { + addStriaLine(outdata, stafflines, partdata, nowtime); + } - output = "!LO:TX"; - output += placementstring; - output += stylestring; - output += ":t="; - output += text; + if (hasclef) { + addClefLine(outdata, clefs, partdata, nowtime); } - // The text direction needs to be added before the last line - // in the measure object. If there is already an empty layout - // slice before the current one (with no spine manipulators - // in between), then insert onto the existing layout slice; - // otherwise create a new layout slice. + if (hastransposition) { + addTranspositionLine(outdata, transpositions, partdata, nowtime); + } - if (interpQ) { - if (afterQ) { - int voicecount = (int)slice->at(partindex)->at(staffindex)->size(); - if (voiceindex >= voicecount) { - // Adding voices in the new slice. It might be - // better to first check for a previous text line - // at the current timestamp that is empty (because there - // is text at the same time in another spine). - GridStaff* gs = slice->at(partindex)->at(staffindex); - gs->resize(voiceindex+1); - string null = slice->getNullTokenForSlice(); - for (int m=voicecount; mat(m) = new GridVoice(null, 0); - } - } - HTp token = slice->at(partindex)->at(staffindex)->at(voiceindex)->getToken(); - HumNum tokdur = Convert::recipToDuration(token); - HumNum timestamp = slice->getTimestamp() + tokdur; - measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, output, timestamp); - } else { - measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, output); - } - } else if (globalQ) { - HumNum timestamp = slice->getTimestamp(); - measure->addGlobalComment(text, timestamp); - } else { - // adding local comment that is not a layout parameter also goes here: - measure->addLayoutParameter(slice, partindex, output); + if (haskeysig) { + addKeySigLine(outdata, keysigs, partdata, nowtime); + } + + if (haskeydesignation) { + addKeyDesignationLine(outdata, keysigs, partdata, nowtime); + } + + if (hastimesig) { + addTimeSigLine(outdata, timesigs, partdata, nowtime); + } + + if (hasottava) { + addOttavaLine(outdata, ottavas, partdata, nowtime); } + + addGraceLines(outdata, graceafter, partdata, nowtime); } ////////////////////////////// // -// Tool_musicxml2hum::addTempo -- Add a tempo direction to the grid. -// -// -// -// -// half -// 80 -// -// -// -// +// Tool_musicxml2hum::storeOcttava -- store an ottava mark which has this structure: // -// Dotted tempo example: +// octaveShift: +// // -// -// -// -// quarter -// -// 80 -// -// -// -// +// For grand staff or multi-staff parts, the staff number needs to be extracted from an uncle element: +// +// +// +// +// 2 +// // +// ottavas array has three dimensions: (1) is the part, (2) is the staff, and (3) is the list of ottavas. // -void Tool_musicxml2hum::addTempo(GridSlice* slice, GridMeasure* measure, int partindex, - int staffindex, int voiceindex, xml_node node) { - string placementstring; - xml_attribute placement = node.attribute("placement"); - if (placement) { - string value = placement.value(); - if (value == "above") { - placementstring = ":a"; - } else if (value == "below") { - placementstring = ":b"; - } else { - // force above if no explicit placement: - placementstring = ":a"; - } - } - - xml_node child = node.first_child(); - if (!child) { - return; - } - if (!nodeType(child, "direction-type")) { - return; - } - - xml_node sound(NULL); - xml_node sibling = child; - while (sibling) { - if (nodeType(sibling, "sound")) { - sound = sibling; - break; +void Tool_musicxml2hum::storeOttava(int pindex, xml_node octaveShift, xml_node direction, + vector>>& ottavas) { + int staffindex = 0; + xml_node staffnode = direction.select_node("staff").node(); + if (staffnode && staffnode.text()) { + int staffnum = staffnode.text().as_int(); + if (staffnum > 0) { + staffindex = staffnum - 1; } - sibling = sibling.next_sibling(); } - - // grandchild should be (containing textual display) - // and which gives *MM data. - xml_node metronome(NULL); - - xml_node grandchild = child.first_child(); - if (!grandchild) { - return; + // ottavas presumed to be allocated by part, but not by staff. + if ((int)ottavas[pindex].size() <= staffindex) { + ottavas[pindex].resize(staffindex+1); } - sibling = grandchild; + ottavas[pindex][staffindex].push_back(octaveShift); +} - while (sibling) { - if (nodeType(sibling, "metronome")) { - metronome = sibling; - } - sibling = sibling.next_sibling(); - } - // get metronome parameters - xml_node beatunit(NULL); - xml_node beatunitdot(NULL); - xml_node perminute(NULL); +////////////////////////////// +// +// Tool_musicxml2hum::processPrintElement -- +// +// +// - if (metronome) { - sibling = metronome.first_child(); - while (sibling) { - if (nodeType(sibling, "beat-unit")) { - beatunit = sibling; - } else if (nodeType(sibling, "beat-unit-dot")) { - beatunitdot = sibling; - } else if (nodeType(sibling, "per-minute")) { - perminute = sibling; - } - sibling = sibling.next_sibling(); - } +void Tool_musicxml2hum::processPrintElement(GridMeasure* outdata, xml_node element, + HumNum timestamp) { + bool isPageBreak = false; + bool isSystemBreak = false; + string pageparam = element.attribute("new-page").value(); + string systemparam = element.attribute("new-system").value(); + if (pageparam == "yes") { + isPageBreak = true; } - - string mmvalue; - if (sound) { - mmvalue = getAttributeValue(sound, "tempo"); + if (systemparam == "yes") { + isSystemBreak = true; } - if (!beatunit) { - cerr << "Warning: missing beat-unit in tempo setting" << endl; - return; - } - if (!perminute) { - cerr << "Warning: missing per-minute in tempo setting" << endl; + if (!(isPageBreak || isSystemBreak)) { return; } + GridSlice* gs = outdata->back(); - int staff = 0; - int voice = 0; - - if (sound) { - string mmtok = "*MM"; - double mmv = stod(mmvalue); - double mmi = int(mmv + 0.001); - if (fabs(mmv - mmi) < 0.01) { - stringstream sstream; - sstream << mmi; - mmtok += sstream.str(); - } else { - mmtok += mmvalue; + HTp token = NULL; + if (gs && gs->size() > 0) { + if (gs->at(0)->size() > 0) { + if (gs->at(0)->at(0)->size() > 0) { + token = gs->at(0)->at(0)->at(0)->getToken(); + } } - HumNum timestamp = slice->getTimestamp(); - measure->addTempoToken(mmtok, timestamp, partindex, staff, voice, m_maxstaff); } - string butext = beatunit.child_value(); - string pmtext = perminute.child_value(); - string stylestring; - - // create textual tempo marking - string text; - text = "["; - text += butext; - if (beatunitdot) { - text += "-dot"; + if (isPageBreak) { + if (!token || *token != "!!pagebreak:original") { + outdata->addGlobalComment("!!pagebreak:original", timestamp); + } + } else if (isSystemBreak) { + if (!token || *token != "!!linebreak:original") { + outdata->addGlobalComment("!!linebreak:original", timestamp); + } } - text += "]"; - text += "="; - text += pmtext; - - string output = "!LO:TX"; - output += placementstring; - output += stylestring; - output += ":t="; - output += text; - - // The text direction needs to be added before the last line in the measure object. - // If there is already an empty layout slice before the current one (with no spine manipulators - // in between), then insert onto the existing layout slice; otherwise create a new layout slice. - measure->addTempoToken(slice, partindex, output); } -////////////////////////////// +/////////////////////////////// // -// setEditorialAccidental -- +// Tool_musicxml2hum::addEventToList -- // -void Tool_musicxml2hum::setEditorialAccidental(int accidental, GridSlice* slice, - int partindex, int staffindex, int voiceindex) { - - HTp tok = slice->at(partindex)->at(staffindex)->at(voiceindex)->getToken(); - - if ((accidental < 0) && (tok->find("-") == string::npos)) { - cerr << "Editorial error for " << tok << ": no flat to mark" << endl; - return; +void Tool_musicxml2hum::addEventToList(vector > > >& list, + MxmlEvent* event) { + int pindex = event->getPartIndex(); + int staffindex = event->getStaffIndex(); + int voiceindex = event->getVoiceIndex(); + if (pindex >= (int)list.size()) { + list.resize(pindex+1); } - if ((accidental > 0) && (tok->find("#") == string::npos)) { - cerr << "Editorial error for " << tok << ": no sharp to mark" << endl; - return; + if (staffindex >= (int)list[pindex].size()) { + list[pindex].resize(staffindex+1); } - if ((accidental == 0) && - ((tok->find("#") != string::npos) || (tok->find("-") != string::npos))) { - cerr << "Editorial error for " << tok << ": requesting a natural accidental" << endl; - return; + if (voiceindex >= (int)list[pindex][staffindex].size()) { + list[pindex][staffindex].resize(voiceindex+1); } + list[pindex][staffindex][voiceindex].push_back(event); +} - string newtok = *tok; - if (accidental == -1) { - auto loc = newtok.find("-"); - if (loc < newtok.size()) { - if (newtok[loc+1] == 'X') { - // replace explicit accidental with editorial accidental - newtok[loc+1] = 'i'; - tok->setText(newtok); - m_hasEditorial = 'i'; - } else { - // append i after -: - newtok.insert(loc+1, "i"); - tok->setText(newtok); - m_hasEditorial = 'i'; + +/////////////////////////////// +// +// Tool_musicxml2hum::addGraceLines -- Add grace note lines. The number of +// lines is equal to the maximum number of successive grace notes in +// any part. Grace notes are filled in reverse sequence. +// + +void Tool_musicxml2hum::addGraceLines(GridMeasure* outdata, + vector > > >& notes, + vector& partdata, HumNum nowtime) { + + int maxcount = 0; + + for (int i=0; i<(int)notes.size(); i++) { + for (int j=0; j<(int)notes.at(i).size(); j++) { + for (int k=0; k<(int)notes.at(i).at(j).size(); k++) { + if (maxcount < (int)notes.at(i).at(j).at(k).size()) { + maxcount = (int)notes.at(i).at(j).at(k).size(); + } } } - return; } - if (accidental == +1) { - auto loc = newtok.find("#"); - if (loc < newtok.size()) { - if (newtok[loc+1] == 'X') { - // replace explicit accidental with editorial accidental - newtok[loc+1] = 'i'; - tok->setText(newtok); - m_hasEditorial = 'i'; - } else { - // append i after -: - newtok.insert(loc+1, "i"); - tok->setText(newtok); - m_hasEditorial = 'i'; - } - } + if (maxcount == 0) { return; } - if (accidental == 0) { - auto loc = newtok.find("n"); - if (loc < newtok.size()) { - if (newtok[loc+1] == 'X') { - // replace explicit accidental with editorial accidental - newtok[loc+1] = 'i'; - tok->setText(newtok); - m_hasEditorial = 'i'; - } else { - // append i after -: - newtok.insert(loc+1, "i"); - tok->setText(newtok); - m_hasEditorial = 'i'; + vector slices(maxcount); + for (int i=0; i<(int)slices.size(); i++) { + slices[i] = new GridSlice(outdata, nowtime, SliceType::GraceNotes); + outdata->push_back(slices[i]); + slices[i]->initializePartStaves(partdata); + } + + for (int i=0; i<(int)notes.size(); i++) { + for (int j=0; j<(int)notes[i].size(); j++) { + for (int k=0; k<(int)notes[i][j].size(); k++) { + int startm = maxcount - (int)notes[i][j][k].size(); + for (int m=0; m<(int)notes[i][j][k].size(); m++) { + addEvent(slices.at(startm+m), outdata, notes[i][j][k][m], nowtime); + } } - } else { - // no natural sign, so add it after any pitch classes. - HumRegex hre; - hre.search(newtok, R"(([a-gA-G]+))"); - string diatonic = hre.getMatch(1); - string newacc = diatonic + "i"; - hre.replaceDestructive(newtok, newacc, diatonic); - tok->setText(newtok); - m_hasEditorial = 'i'; } - return; } } @@ -106266,203 +110292,95 @@ void Tool_musicxml2hum::setEditorialAccidental(int accidental, GridSlice* slice, ////////////////////////////// // -// Tool_musicxml2hum::addDynamic -- extract any dynamics for the event -// -// Such as: -// -// -// -// -// -// -// -// -// -// Hairpins: -// -// -// -// -// -// -// -// -// -// -// +// Tool_musicxml2hum::addClefLine -- // -void Tool_musicxml2hum::addDynamic(GridPart* part, MxmlEvent* event, int partindex) { - vector directions = event->getDynamics(); - if (directions.empty()) { - return; - } - - HTp tok = NULL; - - for (int i=0; i<(int)directions.size(); i++) { - xml_node direction = directions[i]; - xml_attribute placement = direction.attribute("placement"); - bool above = false; - if (placement) { - string value = placement.value(); - if (value == "above") { - above = true; - } - } - xml_node child = direction.first_child(); - if (!child) { - continue; - } - if (!nodeType(child, "direction-type")) { - continue; - } - xml_node grandchild = child.first_child(); - if (!grandchild) { - continue; - } - - if (!(nodeType(grandchild, "dynamics") || nodeType(grandchild, "wedge"))) { - continue; - } - - if (nodeType(grandchild, "dynamics")) { - xml_node dynamic = grandchild.first_child(); - if (!dynamic) { - continue; - } - string dstring = getDynamicString(dynamic); - if (!tok) { - tok = new HumdrumToken(dstring); - } else { - string oldtext = tok->getText(); - string newtext = oldtext + " " + dstring; - tok->setText(newtext); - } - } else if ( nodeType(grandchild, "wedge")) { - xml_node hairpin = grandchild; +void Tool_musicxml2hum::addClefLine(GridMeasure* outdata, + vector >& clefs, vector& partdata, + HumNum nowtime) { - if (isUsedHairpin(hairpin, partindex)) { - // need to suppress wedge ending if already used in [[ or ]] - continue; - } - if (!hairpin) { - cerr << "Warning: Expecting a hairpin, but found nothing" << endl; - continue; - } - string hstring = getHairpinString(hairpin, partindex); - if (!tok) { - tok = new HumdrumToken(hstring); - } else { - string oldtext = tok->getText(); - string newtext = oldtext + " " + hstring; - tok->setText(newtext); - } + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::Clefs); + outdata->push_back(slice); + slice->initializePartStaves(partdata); - // Deal here with adding an index if there is more than one hairpin. - if ((hstring != "[") && (hstring != "]") && above) { - tok->setValue("LO", "HP", "a", "true"); + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)clefs[i].size(); j++) { + if (clefs[i][j]) { + insertPartClefs(clefs[i][j], *slice->at(i)); } } } - if (tok) { - part->setDynamics(tok); - } } ////////////////////////////// // -// Tool_musicxml::isUsedHairpin -- Needed to avoid double-insertion -// of hairpins which were stored before a barline so that they -// are not also repeated on the first beat of the next barline. -// This fuction will remove the hairpin from the used array -// when it is checked. The used array is only for storing -// hairpins that end on measures, so in theory there should not -// be too many, and they will be removed fairly quickly. +// Tool_musicxml2hum::addStriaLine -- // -bool Tool_musicxml2hum::isUsedHairpin(xml_node hairpin, int partindex) { - for (int i=0; i<(int)m_used_hairpins.at(partindex).size(); i++) { - if (hairpin == m_used_hairpins.at(partindex).at(i)) { - // Cannot delete yet: the hairpin endings are being double accessed somewhere. - //m_used_hairpins[partindex].erase(m_used_hairpins[partindex].begin() + i); - return true; +void Tool_musicxml2hum::addStriaLine(GridMeasure* outdata, + vector >& stafflines, vector& partdata, + HumNum nowtime) { + + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::Stria); + outdata->push_back(slice); + slice->initializePartStaves(partdata); + + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)stafflines[i].size(); j++) { + if (stafflines[i][j]) { + string lines = stafflines[i][j].child_value(); + int linecount = stoi(lines); + insertPartStria(linecount, *slice->at(i)); + } } } - return false; } ////////////////////////////// // -// Tool_musicxml2hum::addHairpinEnding -- extract any hairpin ending -// at the end of a measure. -// -// Hairpins: -// -// -// -// -// +// Tool_musicxml2hum::addTimeSigLine -- // -void Tool_musicxml2hum::addHairpinEnding(GridPart* part, MxmlEvent* event, int partindex) { +void Tool_musicxml2hum::addTimeSigLine(GridMeasure* outdata, + vector >& timesigs, vector& partdata, + HumNum nowtime) { - xml_node direction = event->getHairpinEnding(); - if (!direction) { - return; - } + GridSlice* slice = new GridSlice(outdata, nowtime, SliceType::TimeSigs); + outdata->push_back(slice); + slice->initializePartStaves(partdata); - xml_node child = direction.first_child(); - if (!child) { - return; - } - if (!nodeType(child, "direction-type")) { - return; - } - xml_node grandchild = child.first_child(); - if (!grandchild) { - return; + bool status = false; + + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)timesigs[i].size(); j++) { + if (timesigs[i][j]) { + status |= insertPartTimeSigs(timesigs[i][j], *slice->at(i)); + } + } } - if (!nodeType(grandchild, "wedge")) { + if (!status) { return; } - if (nodeType(grandchild, "wedge")) { - xml_node hairpin = grandchild; - if (!hairpin) { - return; - } - string hstring = getHairpinString(hairpin, partindex); - if (hstring == "[") { - hstring = "[["; - } else if (hstring == "]") { - hstring = "]]"; - } - m_used_hairpins.at(partindex).push_back(hairpin); - HTp current = part->getDynamics(); - if (!current) { - HTp htok = new HumdrumToken(hstring); - part->setDynamics(htok); - } else { - string text = current->getText(); - text += " "; - text += hstring; - // Set single-note crescendos - if (text == "< [[") { - text = "<["; - } else if (text == "> ]]") { - text = ">]"; - } else if (text == "< [") { - text = "<["; - } else if (text == "> ]") { - text = ">]"; + // Add mensurations related to time signatures + + slice = new GridSlice(outdata, nowtime, SliceType::MeterSigs); + outdata->push_back(slice); + slice->initializePartStaves(partdata); + + // now add mensuration symbols associated with time signatures + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)timesigs[i].size(); j++) { + if (timesigs[i][j]) { + insertPartMensurations(timesigs[i][j], *slice->at(i)); } - current->setText(text); } } } @@ -106471,1946 +110389,2083 @@ void Tool_musicxml2hum::addHairpinEnding(GridPart* part, MxmlEvent* event, int p ////////////////////////////// // -// Tool_musicxml2hum::convertFiguredBassNumber -- +// Tool_musicxml2hum::addOttavaLine -- Probably there will be a problem if +// an ottava line ends and another one starts at the same timestamp. +// Maybe may OttavaStart and OttavaEnd be separate categories? // -string Tool_musicxml2hum::convertFiguredBassNumber(const xml_node& figure) { - string output; - xml_node fnum = figure.select_node("figure-number").node(); - // assuming one each of prefix/suffix: - xml_node prefixelement = figure.select_node("prefix").node(); - xml_node suffixelement = figure.select_node("suffix").node(); +void Tool_musicxml2hum::addOttavaLine(GridMeasure* outdata, + vector>>& ottavas, vector& partdata, + HumNum nowtime) { - string prefix; - if (prefixelement) { - prefix = prefixelement.child_value(); - } + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::Ottavas); + outdata->push_back(slice); + slice->initializePartStaves(partdata); - string suffix; - if (suffixelement) { - suffix = suffixelement.child_value(); + for (int p=0; p<(int)ottavas.size(); p++) { // part loop + for (int s=0; s<(int)ottavas[p].size(); s++) { // staff loop + for (int j=0; j<(int)ottavas[p][s].size(); j++) { // ottava loop + if (ottavas[p][s][j]) { + // int scount = partdata[p].getStaffCount(); + // int ss = scount - s - 1; + insertPartOttavas(ottavas[p][s][j], *slice->at(p), p, s, partdata[p].getStaffCount()); + } + } + } } +} - string number; - if (fnum) { - number = fnum.child_value(); - } - string accidental; - string slash; - if (prefix == "flat-flat") { - accidental = "--"; - } else if (prefix == "flat") { - accidental = "-"; - } else if (prefix == "double-sharp" || prefix == "sharp-sharp") { - accidental = "##"; - } else if (prefix == "sharp") { - accidental = "#"; - } else if (prefix == "natural") { - accidental = "n"; - } else if (suffix == "flat-flat") { - accidental = "--r"; - } else if (suffix == "flat") { - accidental = "-r"; - } else if (suffix == "double-sharp" || suffix == "sharp-sharp") { - accidental = "##r"; - } else if (suffix == "sharp") { - accidental = "#r"; - } else if (suffix == "natural") { - accidental = "nr"; - } +////////////////////////////// +// +// Tool_musicxml2hum::addKeySigLine -- Only adding one key signature +// for each part for now. +// - // If suffix is "cross", "slash" or "backslash", then an accidental - // should be given (probably either a natural or a sharp in general, but - // could be a flat). At the moment do not assign the accidental, but - // in the future assign an accidental to the slashed figure, probably - // with a post-processing tool. - if (suffix == "cross" || prefix == "cross" || suffix == "vertical" || prefix == "vertical") { - slash = "|"; - if (accidental.empty()) { - accidental = "#"; - } - } else if ((suffix == "backslash" || suffix == "back-slash") || (prefix == "backslash" || prefix == "back-slash")) { - slash = "\\"; - if (accidental.empty()) { - accidental = "#"; - } - } else if ((suffix == "slash") || (prefix == "slash")) { - slash = "/"; - if (accidental.empty()) { - accidental = "-"; - } - } +void Tool_musicxml2hum::addKeySigLine(GridMeasure* outdata, + vector >& keysigs, + vector& partdata, HumNum nowtime) { - string editorial; - string extension; + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::KeySigs); + outdata->push_back(slice); + slice->initializePartStaves(partdata); - xml_node extendelement = figure.select_node("extend").node(); - if (extendelement) { - string typestring = extendelement.attribute("type").value(); - if (typestring == "start") { - extension = "_"; + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)keysigs[i].size(); j++) { + if (keysigs[i][j]) { + insertPartKeySigs(keysigs[i][j], *slice->at(i)); + } } } - - output += accidental + number + slash + editorial + extension; - - return output; } ////////////////////////////// // -// Tool_musicxml2hum::getDynanmicsParameters -- Already presumed to be -// a dynamic. +// Tool_musicxml2hum::addKeyDesignationLine -- Only adding one key designation line +// for each part for now. // -string Tool_musicxml2hum::getDynamicsParameters(xml_node element) { - string output; - if (!nodeType(element, "direction")) { - return output; - } +void Tool_musicxml2hum::addKeyDesignationLine(GridMeasure* outdata, + vector >& keydesigs, + vector& partdata, HumNum nowtime) { - xml_attribute placement = element.attribute("placement"); - if (!placement) { - return output; - } - string value = placement.value(); - if (value == "above") { - output = ":a"; - } - xml_node child = element.first_child(); - if (!child) { - return output; - } - if (!nodeType(child, "direction-type")) { - return output; - } - xml_node grandchild = child.first_child(); - if (!grandchild) { - return output; - } - if (!nodeType(grandchild, "wedge")) { - return output; - } + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::KeyDesignations); + outdata->push_back(slice); + slice->initializePartStaves(partdata); - xml_attribute wtype = grandchild.attribute("type"); - if (!wtype) { - return output; - } - string value2 = wtype.value(); - if (value2 == "stop") { - // don't apply parameters to ends of hairpins. - output = ""; + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)keydesigs[i].size(); j++) { + if (keydesigs[i][j]) { + insertPartKeyDesignations(keydesigs[i][j], *slice->at(i)); + } + } } - - return output; } ////////////////////////////// // -// Tool_musicxml2hum::getFiguredBassParameters -- Already presumed to be -// figured bass. +// Tool_musicxml2hum::addTranspositionLine -- Transposition codes to +// produce written parts. // -string Tool_musicxml2hum::getFiguredBassParameters(xml_node element) { - string output; - if (!nodeType(element, "figured-bass")) { - return output; +void Tool_musicxml2hum::addTranspositionLine(GridMeasure* outdata, + vector >& transpositions, + vector& partdata, HumNum nowtime) { + + GridSlice* slice = new GridSlice(outdata, nowtime, + SliceType::Transpositions); + outdata->push_back(slice); + slice->initializePartStaves(partdata); + + for (int i=0; i<(int)partdata.size(); i++) { + for (int j=0; j<(int)transpositions[i].size(); j++) { + if (transpositions[i][j]) { + insertPartTranspositions(transpositions[i][j], *slice->at(i)); + } + } } - return output; } ////////////////////////////// // -// Tool_musicxml2hum::getHairpinString -- -// -// Hairpins: -// -// -// -// -// -// -// -// -// -// -// +// Tool_musicxml2hum::insertPartClefs -- // -string Tool_musicxml2hum::getHairpinString(xml_node element, int partindex) { - if (nodeType(element, "wedge")) { - xml_attribute wtype = element.attribute("type"); - if (!wtype) { - return "???"; - } - string output; - string wstring = wtype.value(); - if (wstring == "diminuendo") { - m_stop_char.at(partindex) = "]"; - output = ">"; - } else if (wstring == "crescendo") { - m_stop_char.at(partindex) = "["; - output = "<"; - } else if (wstring == "stop") { - output = m_stop_char.at(partindex); - } else { - output = "???"; - } - return output; +void Tool_musicxml2hum::insertPartClefs(xml_node clef, GridPart& part) { + if (!clef) { + // no clef for some reason. + return; } - return "???"; + HTp token; + int staffnum = 0; + while (clef) { + clef = convertClefToHumdrum(clef, token, staffnum); + part[staffnum]->setTokenLayer(0, token, 0); + } + + // go back and fill in all NULL pointers with null interpretations + fillEmpties(&part, "*"); } ////////////////////////////// // -// Tool_musicxml2hum::getDynamicString -- +// Tool_musicxml2hum::insertPartStria -- // -string Tool_musicxml2hum::getDynamicString(xml_node element) { +void Tool_musicxml2hum::insertPartStria(int lines, GridPart& part) { + HTp token = new HumdrumToken; + string value = "*stria" + to_string(lines); + token->setText(value); + part[0]->setTokenLayer(0, token, 0); - if (nodeType(element, "f")) { - return "f"; - } else if (nodeType(element, "p")) { - return "p"; - } else if (nodeType(element, "mf")) { - return "mf"; - } else if (nodeType(element, "mp")) { - return "mp"; - } else if (nodeType(element, "ff")) { - return "ff"; - } else if (nodeType(element, "pp")) { - return "pp"; - } else if (nodeType(element, "sf")) { - return "sf"; - } else if (nodeType(element, "sfp")) { - return "sfp"; - } else if (nodeType(element, "sfpp")) { - return "sfpp"; - } else if (nodeType(element, "fp")) { - return "fp"; - } else if (nodeType(element, "rf")) { - return "rfz"; - } else if (nodeType(element, "rfz")) { - return "rfz"; - } else if (nodeType(element, "sfz")) { - return "sfz"; - } else if (nodeType(element, "sffz")) { - return "sffz"; - } else if (nodeType(element, "fz")) { - return "fz"; - } else if (nodeType(element, "fff")) { - return "fff"; - } else if (nodeType(element, "ppp")) { - return "ppp"; - } else if (nodeType(element, "ffff")) { - return "ffff"; - } else if (nodeType(element, "pppp")) { - return "pppp"; - } else { - return "???"; - } + // go back and fill in all NULL pointers with null interpretations + fillEmpties(&part, "*"); } + ////////////////////////////// // -// Tool_musicxml2hum::addFiguredBass -- -// -// Such as: -// -// -//
-// 0 -//
-//
-// or: -// -//
-// 5 -// backslash -//
-//
-// 2 -// cross -//
-//
-// -// -//
-// flat -//
-//
-// -// Case where there is more than one figure attached to a note: -// (notice element) -// -// -//
-// 6 -// -//
-// 2 -// -// +// Tool_musicxml2hum::insertPartOttavas -- // -int Tool_musicxml2hum::addFiguredBass(GridPart* part, MxmlEvent* event, HumNum nowtime, int partindex) { - if (m_current_figured_bass[partindex].empty()) { - return 0; +void Tool_musicxml2hum::insertPartOttavas(xml_node ottava, GridPart& part, int partindex, + int partstaffindex, int staffcount) { + if (!ottava) { + // no ottava for some reason. + return; } - int dursum = 0; - for (int i=0; i<(int)m_current_figured_bass[partindex].size(); i++) { - xml_node fnode = m_current_figured_bass[partindex].at(i); - if (!fnode) { - // strange problem - continue; - } - string fstring = getFiguredBassString(fnode); - - HTp ftok = new HumdrumToken(fstring); - if (i == 0) { - part->setFiguredBass(ftok); - } else { - // store the figured bass for later handling at end of - // measure processing. - MusicXmlFiguredBassInfo finfo; - finfo.timestamp = dursum; - finfo.timestamp /= (int)event->getQTicks(); - finfo.timestamp += nowtime; - finfo.partindex = partindex; - finfo.token = ftok; - m_offsetFiguredBass.push_back(finfo); - } - if (i < (int)m_current_figured_bass[partindex].size() - 1) { - dursum += getFiguredBassDuration(fnode); - } + HTp token = NULL; + while (ottava) { + ottava = convertOttavaToHumdrum(ottava, token, partstaffindex, partindex, partstaffindex, staffcount); + part[partstaffindex]->setTokenLayer(0, token, 0); } - m_current_figured_bass[partindex].clear(); - return 1; + // go back and fill in all NULL pointers with null interpretations + fillEmpties(&part, "*"); +} -/* deal with figured bass layout parameters?: - string fparam = getFiguredBassParameters(fnode); - if (fparam != "") { - GridMeasure *gm = slice->getMeasure(); - string fullparam = "!LO:FB" + fparam; - if (gm) { - gm->addFiguredBassLayoutParameters(slice, partindex, fullparam); + + +////////////////////////////// +// +// Tool_musicxml2hum::fillEmpties -- +// + +void Tool_musicxml2hum::fillEmpties(GridPart* part, const char* string) { + int staffcount = (int)part->size(); + GridVoice* gv; + int vcount; + + for (int s=0; sat(s); + if (staff == NULL) { + cerr << "Strange error here" << endl; + continue; + } + vcount = (int)staff->size(); + if (vcount == 0) { + gv = new GridVoice(string, 0); + staff->push_back(gv); + } else { + for (int v=0; vat(v); + if (gv == NULL) { + gv = new GridVoice(string, 0); + staff->at(v) = gv; } } } -*/ - + } } ////////////////////////////// // -// Tool_musicxml2hum::getFiguredBassString -- extract any figured bass string -// from XML node. +// Tool_musicxml2hum::insertPartKeySigs -- // -string Tool_musicxml2hum::getFiguredBassString(xml_node fnode) { - string output; - - // Parentheses can only enclose an entire figure stack, not - // individual numbers or accidentals on numbers in MusicXML, - // so apply an editorial mark for parentheses. - string editorial; - xml_attribute pattr = fnode.attribute("parentheses"); - if (pattr) { - string pval = pattr.value(); - if (pval == "yes") { - editorial = "i"; - } - } - // There is no bracket for FB in musicxml (3.0). - - auto children = fnode.select_nodes("figure"); - for (int i=0; i<(int)children.size(); i++) { - output += convertFiguredBassNumber(children[i].node()); - output += editorial; - if (i < (int)children.size() - 1) { - output += " "; - } +void Tool_musicxml2hum::insertPartKeySigs(xml_node keysig, GridPart& part) { + if (!keysig) { + return; } - HumRegex hre; - hre.replaceDestructive(output, "", R"(^\s+|\s+$)"); - - if (output.empty()) { - if (children.size()) { - cerr << "WARNING: figured bass string is empty but has " - << children.size() << " figure elements as children. " - << "The output has been replaced with \".\"" << endl; + HTp token; + int staffnum = 0; + while (keysig) { + keysig = convertKeySigToHumdrum(keysig, token, staffnum); + if (staffnum < 0) { + // key signature applies to all staves in part (most common case) + for (int s=0; s<(int)part.size(); s++) { + if (s==0) { + part[s]->setTokenLayer(0, token, 0); + } else { + HTp token2 = new HumdrumToken(*token); + part[s]->setTokenLayer(0, token2, 0); + } + } + } else { + part[staffnum]->setTokenLayer(0, token, 0); } - output = "."; } - - return output; - - // HTp fbtok = new HumdrumToken(fbstring); - // part->setFiguredBass(fbtok); } ////////////////////////////// // -// Tool_musicxml2hum::addHarmony -- +// Tool_musicxml2hum::insertPartKeyDesignations -- // -int Tool_musicxml2hum::addHarmony(GridPart* part, MxmlEvent* event, HumNum nowtime, - int partindex) { - xml_node hnode = event->getHNode(); - if (!hnode) { - return 0; +void Tool_musicxml2hum::insertPartKeyDesignations(xml_node keydesig, GridPart& part) { + if (!keydesig) { + return; } - // fill in X with the harmony values from the node - string hstring = getHarmonyString(hnode); - int offset = getHarmonyOffset(hnode); - HTp htok = new HumdrumToken(hstring); - if (offset == 0) { - part->setHarmony(htok); - } else { - MusicXmlHarmonyInfo hinfo; - hinfo.timestamp = offset; - hinfo.timestamp /= (int)event->getQTicks(); - hinfo.timestamp += nowtime; - hinfo.partindex = partindex; - hinfo.token = htok; - offsetHarmony.push_back(hinfo); + HTp token; + int staffnum = 0; + while (keydesig) { + token = NULL; + keydesig = convertKeySigToHumdrumKeyDesignation(keydesig, token, staffnum); + if (token == NULL) { + return; + } + if (staffnum < 0) { + // key signature applies to all staves in part (most common case) + for (int s=0; s<(int)part.size(); s++) { + if (s==0) { + part[s]->setTokenLayer(0, token, 0); + } else { + string value = *token; + HTp token2 = new HumdrumToken(value); + part[s]->setTokenLayer(0, token2, 0); + } + } + } else { + part[staffnum]->setTokenLayer(0, token, 0); + } } - - return 1; } ////////////////////////////// // -// Tool_musicxml2hum::getHarmonyOffset -- -// -// -// C -// -// major-ninth -// -// E -// -// -8 -// +// Tool_musicxml2hum::insertPartTranspositions -- // -int Tool_musicxml2hum::getHarmonyOffset(xml_node hnode) { - if (!hnode) { - return 0; - } - xml_node child = hnode.first_child(); - if (!child) { - return 0; +void Tool_musicxml2hum::insertPartTranspositions(xml_node transposition, GridPart& part) { + if (!transposition) { + return; } - while (child) { - if (nodeType(child, "offset")) { - return atoi(child.child_value()); + + HTp token; + int staffnum = 0; + while (transposition) { + transposition = convertTranspositionToHumdrum(transposition, token, staffnum); + if (staffnum < 0) { + // Transposition applies to all staves in part (most common case) + for (int s=0; s<(int)part.size(); s++) { + if (s==0) { + part[s]->setTokenLayer(0, token, 0); + } else { + HTp token2 = new HumdrumToken(*token); + part[s]->setTokenLayer(0, token2, 0); + } + } + } else { + part[staffnum]->setTokenLayer(0, token, 0); } - child = child.next_sibling(); } - - return 0; } ////////////////////////////// // -// Tool_musicxml2hum::getFiguredBaseDuration -- Needed for cases where there is more -// than one figure attached to a note. Return value is the integer of the duration -// element. If will need to be converted to quarter notes later. -// -// -//
-// 5 -//
-//
-// 3 -//
-// 2 <-- get this field if it exists. -//
+// Tool_musicxml2hum::insertPartTimeSigs -- Only allowing one +// time signature per part for now. // -int Tool_musicxml2hum::getFiguredBassDuration(xml_node fnode) { - if (!fnode) { - return 0; - } - xml_node child = fnode.first_child(); - if (!child) { - return 0; +bool Tool_musicxml2hum::insertPartTimeSigs(xml_node timesig, GridPart& part) { + if (!timesig) { + // no timesig + return false; } - while (child) { - if (nodeType(child, "duration")) { - return atoi(child.child_value()); + + bool hasmensuration = false; + HTp token; + int staffnum = 0; + + while (timesig) { + hasmensuration |= checkForMensuration(timesig); + timesig = convertTimeSigToHumdrum(timesig, token, staffnum); + if (token && (staffnum < 0)) { + // time signature applies to all staves in part (most common case) + for (int s=0; s<(int)part.size(); s++) { + if (s==0) { + part[s]->setTokenLayer(0, token, 0); + } else { + HTp token2 = new HumdrumToken(*token); + part[s]->setTokenLayer(0, token2, 0); + } + } + } else if (token) { + part[staffnum]->setTokenLayer(0, token, 0); } - child = child.next_sibling(); } - return 0; + return hasmensuration; } ////////////////////////////// // -// Tool_musicxml2hum::getHarmonyString -- -// -// -// C -// -// major-ninth -// -// E -// -// -8 -// -// -// For harmony labels from Musescore: -// -// -// -// C -// -// none -// -// -// Converts to: "V43" ignoring the root-step and kind contents -// if they are both "C" and "none". +// Tool_musicxml2hum::insertPartMensurations -- // -string Tool_musicxml2hum::getHarmonyString(xml_node hnode) { - if (!hnode) { - return ""; - } - xml_node child = hnode.first_child(); - if (!child) { - return ""; +void Tool_musicxml2hum::insertPartMensurations(xml_node timesig, + GridPart& part) { + if (!timesig) { + // no timesig + return; } - string root; - string kind; - string kindtext; - string bass; - int rootalter = 0; - int bassalter = 0; - xml_node grandchild; - while (child) { - if (nodeType(child, "root")) { - grandchild = child.first_child(); - while (grandchild) { - if (nodeType(grandchild, "root-step")) { - root = grandchild.child_value(); - } if (nodeType(grandchild, "root-alter")) { - rootalter = atoi(grandchild.child_value()); - } - grandchild = grandchild.next_sibling(); - } - } else if (nodeType(child, "kind")) { - kindtext = getAttributeValue(child, "text"); - kind = child.child_value(); - if (kind == "") { - kind = child.attribute("text").value(); - transform(kind.begin(), kind.end(), kind.begin(), ::tolower); - } - } else if (nodeType(child, "bass")) { - grandchild = child.first_child(); - while (grandchild) { - if (nodeType(grandchild, "bass-step")) { - bass = grandchild.child_value(); - } if (nodeType(grandchild, "bass-alter")) { - bassalter = atoi(grandchild.child_value()); + + HTp token = NULL; + int staffnum = 0; + + while (timesig) { + timesig = convertMensurationToHumdrum(timesig, token, staffnum); + if (staffnum < 0) { + // time signature applies to all staves in part (most common case) + for (int s=0; s<(int)part.size(); s++) { + if (s==0) { + part[s]->setTokenLayer(0, token, 0); + } else { + HTp token2 = new HumdrumToken(*token); + part[s]->setTokenLayer(0, token2, 0); } - grandchild = grandchild.next_sibling(); } + } else { + part[staffnum]->setTokenLayer(0, token, 0); } - child = child.next_sibling(); } - stringstream ss; - if ((kind == "none") && (root == "C") && !kindtext.empty()) { - ss << kindtext; - string output = cleanSpaces(ss.str()); - return output; - } +} - ss << root; - if (rootalter > 0) { - for (int i=0; i +//
https://bit.ly/humdrum-instrument-codes.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + return; + } + + bool found1 = false; + bool found2 = false; + string inst1; + string inst2; + HumRegex hre; + if (hre.match(token, "^\\*I(.*)[&|](I.*)")) { + inst1 = hre.getMatch(1); + inst2 = hre.getMatch(2); + + for (int i=0; i<(int)instrumentList.size(); i++) { + if (instrumentList[i].first == inst1) { + found1 = true; + } + if (instrumentList[i].first == inst2) { + found2 = true; + } } } + + if (!found1) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + inst1 + "\" in token " + *token + " on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at https://bit.ly/humdrum-instrument-codes.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + + if (!found2) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + inst2 + "\" in token " + *token + " on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at https://bit.ly/humdrum-instrument-codes.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } ////////////////////////////// // -// Tool_myank::printStarting -- print header information before start of data. +// Tool_nproof::checkInstrumentInformation -- // -void Tool_myank::printStarting(HumdrumFile& infile) { - int i, j; - int exi = -1; - for (i=0; i> instrumentList = Convert::getInstrumentList(); + + for (int i=0; iisKern()) { + continue; + } + if (token->compare(0, 3, "*IC") == 0) { + if (classLine < 0) { + classLine = i; + } + } else if (hre.search(token, "^\\*I[a-z]")) { + if (codeLine < 0) { + codeLine = i; + } } } } - // keep *part interpretations - bool hasPart = false; - for (i=exi+1; icompare(0, 5, "*part") == 0) { - hasPart = true; - break; - } - } - if (hasPart) { - for (j=0; jcompare(0, 5, "*part") == 0) { - m_humdrum_text << infile.token(i, j); + if (codeLine < 0) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No instrument code line.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } else { + for (int i=0; iisKern()) { + if (!hre.search(token, "^\\*I[a-z]")) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": expected instrument code on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } else { - m_humdrum_text << "*"; + checkForValidInstrumentCode(token, instrumentList); } - if (j < infile[i].getFieldCount() - 1) { - m_humdrum_text << "\t"; + } else { + if (*token != "*") { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Expected null interpretation on instrument code line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } } - m_humdrum_text << "\n"; } } - // keep *staff interpretations - bool hasStaff = false; - for (i=exi+1; icompare(0, 6, "*staff") == 0) { - hasStaff = true; - break; - } - } - if (hasStaff) { - for (j=0; jcompare(0, 6, "*staff") == 0) { - m_humdrum_text << infile.token(i, j); - } else { - m_humdrum_text << "*"; + if (classLine < 0) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No instrument class line.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } else { + for (int i=0; iisKern()) { + if (!hre.search(token, "^\\*IC[a-z]")) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": expected instrument class on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } - if (j < infile[i].getFieldCount() - 1) { - m_humdrum_text << "\t"; + } else { + if (*token != "*") { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Expected null interpretation on instrument class line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } } - m_humdrum_text << "\n"; } } +} - int hasI = 0; - if (m_instrumentQ) { - // print any tandem interpretations which start with *I found - // at the start of the data before measures, notes, or any - // spine manipulator lines - for (i=exi+1; i foundENC; // Musescore encoder's name + vector foundEND; // Musescore encdoer's date + vector foundEED; // VHV editor's name + vector foundEEV; // VHV editor's date + + HumRegex hre; + for (int i=0; i\n"; } - if (!infile[i].isInterpretation()) { - continue; + } + if (hre.search(key, "^EEV\\d*$")) { + if (key == "EEV") { + foundEEV.push_back(i);; } - if (infile[i].isManipulator()) { - break; + string value = infile[i].getReferenceValue(); + if (!hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For EEV (ElEctronic Version) record on line " + to_string(i+1) + ", found a name rather than a date (or invalid date): " + value + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } - hasI = 0; - for (j=0; jcompare(0, 2, "*I") == 0) { - hasI = 1; - break; + } + if (hre.search(key, "^ENC\\d*(-modern|-iiif)?$")) { + string value = infile[i].getReferenceValue(); + if (hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For ENC (Electronic eNCoder) record on line " + to_string(i+1) + ", found a date rather than a name: " + value + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } + if (hre.search(key, "^END\\d*(-modern|-iiif)?$")) { + string value = infile[i].getReferenceValue(); + if (!hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For END (Electronic eNcoding Date) record on line " + to_string(i+1) + ", found a name rather than a date (or an invalid date): " + value + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } + if (hre.search(key, "^ENC(\\d*.*)$")) { + if (key == "ENC") { + foundENC.push_back(i); + } + } + if (hre.search(key, "^ENC-(\\d+.*)$")) { + string newvalue = "ENC" + hre.getMatch(1); + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not include a dash and instead be: " + newvalue + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (hre.search(key, "END(\\d*.*)")) { + if (key == "END") { + foundEND.push_back(i); } + } + if (hre.search(key, "^END-(\\d+.*)$")) { + string newvalue = "END" + hre.getMatch(1); + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not include a dash and instead be: " + newvalue + ".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (key == "filter-") { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": \"filter-\" reference record on line " + to_string(i+1) + " should probably be \"filter-modern\" instead.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (key == "ENC-mod") { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": ENC-mod reference record on line " + to_string(i+1) + " should be ENC-modern instead.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (key == "END-mod") { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": END-mod reference record on line " + to_string(i+1) + " should be END-modern instead.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (key == "AIN-mod") { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": AIN-mod reference record on line " + to_string(i+1) + " should be AIN-modern instead.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (hre.search(key, "^(.*)-ori$")) { + string piece = hre.getMatch(1); + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not be used (either use " + piece + "-mod or don't add -ori qualifier to " + piece + ").\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } + + // vector foundENC; // Musescore encoder's name + // vector foundEND; // Musescore encdoer's date + // vector foundEED; // VHV editor's name + // vector foundEEV; // VHV editor's date + + if (foundENC.empty()) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing ENC (initial encoder's name) reference record.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (foundEND.empty()) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing END (initial encoding date) reference record.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (foundEED.empty()) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing EED (Humdrum electronic editor's name) reference record.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + if (foundEEV.empty()) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing EEV (Humdrum electronic edition date) reference record.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + + if ((foundENC.size() == 1) && (foundEED.size() == 1)) { + if (foundENC[0] > foundEED[0]) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": ENC reference record on line " + to_string(foundENC[0]+1) + " should come before EED reference record on line " + to_string(foundEED[0]+1) + "\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } + + if ((foundEND.size() == 1) && (foundEEV.size() == 1)) { + if (foundEND[0] > foundEEV[0]) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": END reference record on line " + to_string(foundEND[0]+1) + " should come before EEV reference record on line " + to_string(foundEEV[0]+1) + "\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } + + + if ((foundENC.size() == 2) && (foundEED.size() == 0)) { + string date1; + string date2; + if (foundEND.size() == 2) { + date1 = infile[foundEND[0]].getReferenceValue(); + date2 = infile[foundEND[1]].getReferenceValue(); + hre.replaceDestructive(date1, "", "-", "g"); + hre.replaceDestructive(date2, "", "-", "g"); + int number1 = 0; + int number2 = 0; + if (hre.search(date1, "^(20\\d{6})$")) { + number1 = hre.getMatchInt(1); } - if (hasI) { - for (j=0; jcompare(0, 2, "*I") == 0) { - m_humdrum_text << infile.token(i, j); - } else { - m_humdrum_text << "*"; - } - if (j < infile[i].getFieldCount() - 1) { - m_humdrum_text << "\t"; - } + if (hre.search(date2, "^(20\\d{6})$")) { + number2 = hre.getMatchInt(1); + } + if ((number1 > 0) && (number2 > 0)) { + if (number1 > number2) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Second ENC reference record on line " + to_string(foundENC[1]+1) + " should probably be changed to EED reference record (and second END reference record on line " + to_string(foundEND[1]+1) + " changed to EEV).\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } - m_humdrum_text << "\n"; } + } else { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": There are two ENC records on lines " + to_string(foundENC[0]+1) + " and " + to_string(foundENC[1]+1) + ". The Humdrum editor's name should be changed to EED, and the editing date should be changed from END to EEV if necessary.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } + } + +} + + + +////////////////////////////// +// +// Tool_nproof::checkKeyInformation -- +// + +void Tool_nproof::checkKeyInformation(HumdrumFile& infile) { + int foundKey = -1; + for (int i=0; icompare(0, 7, "!!!key:") == 0) { + foundKey = i; + break; + } + } + + if (foundKey < 0) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No !!!key: reference record.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + return; + } + + string value = infile[foundKey].getReferenceValue(); + if (value.empty()) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": !!!key: reference record on line " + to_string(foundKey+1) + " should not be empty. If no key, then use \"none\".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + return; + } + + HumRegex hre; + if (hre.search(value, "^([a-gA-G][#-n]?):(dor|phr|lyd|mix|aeo|loc|ion)$")) { + string tonic = hre.getMatch(1); + string mode = hre.getMatch(2); + int major = 0; + if ((mode == "lyd") || (mode == "mix") || (mode == "ion")) { + major = 1; + } + int uppercase = isupper(tonic[0]); + if ((major == 1) && (uppercase == 0)) { + tonic[0] = toupper(tonic[0]); + string correct = tonic + ":" + mode; + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": !!!key: reference record on line " + to_string(foundKey + 1) + " should be \"" + correct + "\".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } else if ((major == 0) && (uppercase == 1)) { + tonic[0] = tolower(tonic[0]); + string correct = tonic + ":" + mode; + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": !!!key: reference record on line " + to_string(foundKey + 1) + " should be \"" + correct + "\".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } + } else if (hre.search(value, "([a-gA-G][#-n]?):(.+)")) { + string mode = hre.getMatch(2); + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown mode in !!!key: reference record contents on line " + to_string(foundKey + 1) + ": \"" + mode + "\".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + } else if (!hre.search(value, "([a-gA-G][#-n]?):?")) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown key designation in !!!key: reference record contents on line " + to_string(foundKey + 1) + ": \"" + value + "\".\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } } @@ -110838,408 +115050,569 @@ void Tool_myank::printStarting(HumdrumFile& infile) { ////////////////////////////// // -// Tool_myank::printEnding -- print the spine terminators and any -// content after the end of the data. +// Tool_nproof::checkSpineTerminations -- // -void Tool_myank::printEnding(HumdrumFile& infile, int lastline, int adjlin) { - if (m_debugQ) { - m_humdrum_text << "IN printEnding" << endl; - } - int ending = -1; - int marker = -1; - int i; - for (i=infile.getLineCount()-1; i>=0; i--) { - if (infile[i].isInterpretation() && (ending <0) - && (*infile.token(i, 0) == "*-")) { - ending = i; +void Tool_nproof::checkSpineTerminations(HumdrumFile& infile) { + int foundTerminal = 0; + for (int i=infile.getLineCount() - 1; i>0; i--) { + if (!infile[i].isInterpretation()) { + continue; } - if (infile[i].isData()) { - marker = i+1; + HTp token = infile.token(i, 0); + if (*token == "*-") { + foundTerminal = i; break; } - if (infile[i].isBarline()) { - marker = i+1; + } + + if (!foundTerminal) { + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No spine terminators.\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + return; + } + + bool problem = false; + for (int i=0; igetSpineInfo(); + if (value.find(" ") != string::npos) { + problem = true; break; } } - if (ending >= 0) { - reconcileSpineBoundary(infile, adjlin, ending); + if (!problem) { + return; } - int startline = ending; - if (marker >= 0) { - // capture any comment which occur after the last measure - // line in the data. - startline = marker; + m_errorCount++; + m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Incorrect spine merger(s): "; + for (int i=0; igetSpineInfo() + ">"; + if (i < infile[foundTerminal].getFieldCount() - 1) { + m_errorList += " "; + } + } + m_errorList += "\n"; + m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; +} + + + + + +///////////////////////////////// +// +// Tool_ordergps::Tool_ordergps -- Set the recognized options for the tool. +// + +Tool_ordergps::Tool_ordergps(void) { + define("e|empty=b", "list files that have no group/part/staff (used with -p option)."); + define("f|file=b", "list input files only."); + define("l|list=b", "list files that will be changed."); + define("p|problem=b", "list files that have mixed content for *group, *part, *staff info."); + define("r|reverse=b", "order *staff, *part, *group"); + define("s|staff=b", "Add staff line if none present already in score."); + define("t|top=b", "Place group/part/staff lines first after exinterp."); +} + + + +///////////////////////////////// +// +// Tool_ordergps::run -- Do the main work of the tool. +// + +bool Tool_ordergps::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i= 0) { - for (i=startline; i ending)) { - if (infile[i].rfind("!!!RDF", 0) == 0 || infile[i].rfind("!!!system-decoration", 0) == 0) { - m_humdrum_text << infile[i] << "\n"; - } - } else { - m_humdrum_text << infile[i] << "\n"; - } - } +bool Tool_ordergps::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; + } + return status; +} + + +bool Tool_ordergps::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } + return status; +} + +bool Tool_ordergps::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_myank::getMeasureStartStop -- Get a list of the (numbered) measures in the -// input file, and store the start/stop lines for those measures. -// All data before the first numbered measure is in measure 0. -// although, if the first measure is not labeled, then ... +// Tool_ordergps::initialize -- Setup to do before processing a file. // -void Tool_myank::getMeasureStartStop(vector& measurelist, HumdrumFile& infile) { - measurelist.reserve(infile.getLineCount()); - measurelist.resize(0); +void Tool_ordergps::initialize(void) { + m_emptyQ = getBoolean("empty"); + m_fileQ = getBoolean("file"); + m_listQ = getBoolean("list"); + m_problemQ = getBoolean("problem"); + m_reverseQ = getBoolean("reverse"); + m_staffQ = getBoolean("staff"); + m_topQ = getBoolean("top"); +} - MeasureInfo current; - int i, ii; - int lastend = -1; - int dataend = -1; - int barnum1 = -1; - int barnum2 = -1; - HumRegex hre; - insertZerothMeasure(measurelist, infile); - for (i=0; i groupIndex; + vector partIndex; + vector staffIndex; + bool foundProblem = false; + + for (int i=0; ic_str(), "=%d", &barnum1)) { + if (infile[i].isExclusiveInterpretation()) { continue; } - current.clear(); - current.start = i; - current.num = barnum1; - for (ii=i+1; iicompare(0, 6, "*group") == 0) { + hasGroup = true; + } else if (token->compare(0, 5, "*part") == 0) { + hasPart = true; + } else if (token->compare(0, 6, "*staff") == 0) { + hasStaff = true; } else { - if (atEndOfFile(infile, ii)) { - break; - } + hasOther = true; } } - } - int lastdata = -1; // last line in file with data - int lastmeasure = -1; // last line in file with measure + if (hasOther && hasGroup) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MIXED GROUP LINE:" << endl; + cerr << "\t" << infile[i] << endl; + } + } - for (i=infile.getLineCount()-1; i>=0; i--) { - if ((lastdata < 0) && infile[i].isData()) { - lastdata = i; + if (hasOther && hasPart) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MIXED PART LINE:" << endl; + cerr << "\t" << infile[i] << endl; + } } - if ((lastmeasure < 0) && infile[i].isBarline()) { - lastmeasure = i; + + if (hasOther && hasStaff) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MIXED STAFF LINE:" << endl; + cerr << "\t" << infile[i] << endl; + } } - if ((lastmeasure >= 0) && (lastdata >= 0)) { - break; + + if (hasOther) { + continue; + } + + if (hasGroup && hasPart) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MIXED GROUP AND PART LINE:" << endl; + cerr << "\t" << infile[i] << endl; + } + } + + if (hasGroup && hasStaff) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MIXED GROUP AND STAFF LINE:" << endl; + cerr << "\t" << infile[i] << endl; + } + } + + if (hasPart && hasStaff) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MIXED PART AND STAFF LINE:" << endl; + cerr << "\t" << infile[i] << endl; + } + } + + if (hasGroup) { + groupIndex.push_back(i); + } + + if (hasPart) { + partIndex.push_back(i); + } + + if (hasStaff) { + staffIndex.push_back(i); } } - if (lastmeasure < lastdata) { - // no final barline, so set to ignore - lastmeasure = -1; - lastdata = -1; + if (groupIndex.size() > 1) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MORE THAN ONE GROUP LINE:" << endl; + for (int i=0; i<(int)groupIndex.size(); i++) { + cerr << "\t" << infile[groupIndex[i]] << endl; + } + } } - if ((barnum2 >= 0) && (lastend >= 0) && (dataend >= 0)) { - current.clear(); - current.num = barnum2; - current.start = lastend; - current.stop = dataend; - if (lastmeasure > lastdata) { - current.stop = lastmeasure; + if (partIndex.size() > 1) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MORE THAN ONE PART LINE:" << endl; + for (int i=0; i<(int)partIndex.size(); i++) { + cerr << "\t" << infile[partIndex[i]] << endl; + } } - current.file = &infile; - measurelist.push_back(current); } - // allow "myank -l" when there are no measure numbers - if (getBoolean("lines") && measurelist.size() == 0) { - current.clear(); - current.num = 0; - current.start = 0; - current.stop = dataend; - current.file = &infile; - measurelist.push_back(current); + if (staffIndex.size() > 1) { + foundProblem = true; + if (m_problemQ) { + cerr << infile.getFilename() << " HAS MORE THAN ONE STAFF LINE:" << endl; + for (int i=0; i<(int)staffIndex.size(); i++) { + cerr << "\t" << infile[staffIndex[i]] << endl; + } + } } + if (m_problemQ) { + if (m_emptyQ) { + if (groupIndex.empty() && partIndex.empty() && staffIndex.empty()) { + cerr << infile.getFilename() << " HAS NO GROUP/PART/STAFF INFO" << endl; + } + } + } else { + if (foundProblem) { + // Don try to fix anything, just echo the input: + m_humdrum_text << infile; + } else { + if (m_staffQ && groupIndex.empty() && partIndex.empty() && staffIndex.empty()) { + printStaffLine(infile); + } else { + // Process further here + // Check the order of the group/part/staff lines. + int gindex = groupIndex.empty() ? -1 : groupIndex.at(0); + int pindex = partIndex.empty() ? -1 : partIndex.at(0); + int sindex = staffIndex.empty() ? -1 : staffIndex.at(0); + if (m_topQ) { + printFileTop(infile, gindex, pindex, sindex); + } else { + printFile(infile, gindex, pindex, sindex); + } + } + } + } } ////////////////////////////// // -// Tool_myank::getSectionCount -- Count the number of sections in a file according to -// JRP rules: sections are defined by double barlines. There may be some -// corner cases to consider. +// Tool_ordergps::printFileTop -- Print group/part/staff first after exclusive +// interpretations. // -int Tool_myank::getSectionCount(HumdrumFile& infile) { - int i; - int count = 0; - int dataQ = 0; - for (i=0; ifind("||") != string::npos) { - dataQ = 0; + } else if (i == pindex) { + continue; + } else if (i == sindex) { + continue; + } else if (infile[i].isExclusiveInterpretation()) { + m_humdrum_text << infile[i] << endl; + if (m_reverseQ) { + if (sindex >= 0) { + m_humdrum_text << infile[sindex] << endl; + } + if (pindex >= 0) { + m_humdrum_text << infile[pindex] << endl; + } + if (gindex >= 0) { + m_humdrum_text << infile[gindex] << endl; + } + } else { + if (gindex >= 0) { + m_humdrum_text << infile[gindex] << endl; + } + if (pindex >= 0) { + m_humdrum_text << infile[pindex] << endl; + } + if (sindex >= 0) { + m_humdrum_text << infile[sindex] << endl; + } } + } else { + m_humdrum_text << infile[i] << endl; } } - return count; } ////////////////////////////// // -// Tool_myank::getSectionString -- return the measure range of a section. +// Tool_ordergps::printFile -- Check to see if the group/part/staff +// lines need to be adjusted, and the print the file. Lines +// will be ordered group/part/staff, placing the lines where +// the first of group/part/staff is found. // -void Tool_myank::getSectionString(string& sstring, HumdrumFile& infile, int sec) { - int i; - int first = -1; - int second = -1; - int barnum = 0; - int count = 0; - int dataQ = 0; - HumRegex hre; - for (i=0; i= 0) { + if (startIndex < 0) { + startIndex = pindex; + } else if (pindex < startIndex) { + startIndex = pindex; + } + } + if (sindex >= 0) { + if (startIndex < 0) { + startIndex = sindex; + } else if (sindex < startIndex) { + startIndex = sindex; + } + } + if (startIndex < 0) { + // no group/part/staff lines in file, so just print it: + m_humdrum_text << infile; + return; + } + + for (int i=0; i= 0) { + m_humdrum_text << infile[sindex] << endl; + } + if (pindex >= 0) { + m_humdrum_text << infile[pindex] << endl; + } + if (gindex >= 0) { + m_humdrum_text << infile[gindex] << endl; + } + } else { + if (gindex >= 0) { + m_humdrum_text << infile[gindex] << endl; + } + if (pindex >= 0) { + m_humdrum_text << infile[pindex] << endl; + } + if (sindex >= 0) { + m_humdrum_text << infile[sindex] << endl; + } } + } else if (i == gindex) { + continue; + } else if (i == pindex) { + continue; + } else if (i == sindex) { + continue; + } else { + m_humdrum_text << infile[i] << endl; + } + } +} + + + +////////////////////////////// +// +// Tool_ordergps::printStaffLine -- Add a *staff at the start of the +// data since none was detected. Does not label staff-like spines +// other than **kern (such as **kernyy, **kern-mod, **mens). + +void Tool_ordergps::printStaffLine(HumdrumFile& infile) { + for (int i=0; ifind("||") != string::npos) { - dataQ = 0; + m_humdrum_text << infile[i] << endl; + vector staffLine(infile[i].getFieldCount(), "*"); + int counter = 0; + for (int j=infile[i].getFieldCount() - 1; j>=0; j--) { + HTp token = infile.token(i, j); + if (token->isKern()) { + counter++; + string text = "*staff" + to_string(counter); + staffLine.at(j) = text; } - if (hre.search(infile.token(i, 0), "(\\d+)")) { - barnum = hre.getMatchInt(1); + } + for (int j=0; j<(int)staffLine.size(); j++) { + m_humdrum_text << staffLine[j]; + if (j < (int)staffLine.size() - 1) { + m_humdrum_text << '\t'; } } + m_humdrum_text << endl; } - if (second < 0) { - second = barnum; - } - sstring = to_string(first); - sstring += "-"; - sstring += to_string(second); } -////////////////////////////// + +///////////////////////////////// // -// Tool_myank::atEndOfFile -- +// Tool_pbar::Tool_pbar -- Set the recognized options for the tool. // -int Tool_myank::atEndOfFile(HumdrumFile& infile, int line) { - int i; - for (i=line+1; i& measurelist, - HumdrumFile& infile) { +void Tool_pbar::initialize(void) { + m_invisibleQ = getBoolean("invisible-barlines"); +} - HumRegex hre; - int exinterpline = -1; - int startline = -1; - int stopline = -1; - int i; - for (i=0; i& measureout, - vector& measurein, HumdrumFile& infile, - const string& optionstring) { - - HumRegex hre; - // find the largest measure number in the score - int maxmeasure = -1; - int minmeasure = -1; - for (int i=0; i<(int)measurein.size(); i++) { - if (maxmeasure < measurein[i].num) { - maxmeasure = measurein[i].num; - } - if ((minmeasure == -1) || (minmeasure > measurein[i].num)) { - minmeasure = measurein[i].num; - } - } - if (maxmeasure <= 0 && !getBoolean("lines")) { - cerr << "Error: There are no measure numbers present in the data" << endl; - exit(1); - } - if (maxmeasure > 1123123) { - cerr << "Error: ridiculusly large measure number: " << maxmeasure << endl; - exit(1); +void Tool_pbar::processFile(HumdrumFile& infile) { + vector kstarts = infile.getKernSpineStartList(); + for (int i=0; i<(int)kstarts.size(); i++) { + processSpine(kstarts[i]); } - if (m_maxQ) { - if (measurein.size() == 0) { - m_humdrum_text << 0 << endl; - } else { - m_humdrum_text << maxmeasure << endl; + + for (int i=0; i inmap(maxmeasure+1); - fill(inmap.begin(), inmap.end(), -1); - for (int i=0; i<(int)measurein.size(); i++) { - inmap[measurein[i].num] = i; - } - - fillGlobalDefaults(infile, measurein, inmap); - string ostring = optionstring; - removeDollarsFromString(ostring, maxmeasure); - - if (m_debugQ) { - m_free_text << "Option string expanded: " << ostring << endl; - } - - hre.replaceDestructive(ostring, "", "\\s+", "g"); // remove any spaces between items. - hre.replaceDestructive(ostring, "-", "--+", "g"); // remove extra dashes - int value = 0; - int start = 0; - vector& range = measureout; - range.reserve(10000); - string searchexp = "^([\\d$-]+[^\\d$-]*)"; - value = hre.search(ostring, searchexp); - while (value != 0) { - start += value - 1; - start += (int)hre.getMatch(1).size(); - processFieldEntry(range, hre.getMatch(1), infile, maxmeasure, measurein, inmap); - value = hre.search(ostring, start, searchexp); } } @@ -111247,827 +115620,856 @@ void Tool_myank::expandMeasureOutList(vector& measureout, ////////////////////////////// // -// Tool_myank::fillGlobalDefaults -- keep track of the clef, key signature, key, etc. +// Tool_pbar::printInvisibleBarlines -- // -void Tool_myank::fillGlobalDefaults(HumdrumFile& infile, vector& measurein, - vector& inmap) { - int i, j; +void Tool_pbar::printInvisibleBarlines(HumdrumFile& infile, int index) { HumRegex hre; - - int tracks = infile.getMaxTrack(); - // cerr << "MAX TRACKS " << tracks << " ===============================" << endl; - - vector currclef(tracks+1); - vector currkeysig(tracks+1); - vector currkey(tracks+1); - vector currtimesig(tracks+1); - vector currmet(tracks+1); - vector currtempo(tracks+1); - - MyCoord undefMyCoord; - undefMyCoord.clear(); - - fill(currclef.begin(), currclef.end(), undefMyCoord); - fill(currkeysig.begin(), currkeysig.end(), undefMyCoord); - fill(currkey.begin(), currkey.end(), undefMyCoord); - fill(currtimesig.begin(), currtimesig.end(), undefMyCoord); - fill(currmet.begin(), currmet.end(), undefMyCoord); - fill(currtempo.begin(), currtempo.end(), undefMyCoord); - - int currmeasure = -1; - int lastmeasure = -1; - int datafound = 0; - int track; - int thingy = 0; - - for (i=0; i= 0) { - measurein[inmap[currmeasure]].eclef = currclef; - measurein[inmap[currmeasure]].ekeysig = currkeysig; - measurein[inmap[currmeasure]].ekey = currkey; - measurein[inmap[currmeasure]].etimesig = currtimesig; - measurein[inmap[currmeasure]].emet = currmet; - measurein[inmap[currmeasure]].etempo = currtempo; - } - - lastmeasure = currmeasure; - currmeasure = hre.getMatchInt(1); - - if (currmeasure < (int)inmap.size()) { - // [20120818] Had to compensate for last measure being single - // and un-numbered. - if (inmap[currmeasure] < 0) { - // [20111008] Had to compensate for "==85" barline - datafound = 0; - break; - } -// cerr << "CURRCLEF: "; -// for (int z=0; z<(int)currclef.size(); z++) { -// cerr << "(" << currclef[z].x << "," << currclef[z].y << ") "; -// } -// cerr << endl; - measurein[inmap[currmeasure]].sclef = currclef; - measurein[inmap[currmeasure]].skeysig = currkeysig; - measurein[inmap[currmeasure]].skey = currkey; - measurein[inmap[currmeasure]].stimesig = currtimesig; - // measurein[inmap[currmeasure]].smet = metstates[i]; - measurein[inmap[currmeasure]].smet = currmet; - measurein[inmap[currmeasure]].stempo = currtempo; - } - - datafound = 0; - continue; + if (i < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; } - if (infile[i].isInterpretation()) { - for (j=0; jisKern()) { - continue; - } - track = infile.token(i, j)->getTrack(); - - if ((datafound == 0) && (lastmeasure >= 0)) { - if (infile.token(i, j)->compare(0, 5, "*clef") == 0) { - measurein[inmap[currmeasure]].sclef[track].x = -1; - measurein[inmap[currmeasure]].sclef[track].y = -1; - } else if (hre.search(infile.token(i, j), "^\\*k\\[.*\\]", "")) { - measurein[inmap[currmeasure]].skeysig[track].x = -1; - measurein[inmap[currmeasure]].skeysig[track].y = -1; - } else if (hre.search(infile.token(i, j), "^\\*[A-G][#-]?:", "i")) { - measurein[inmap[currmeasure]].skey[track].x = -1; - measurein[inmap[currmeasure]].skey[track].y = -1; - } else if (hre.search(infile.token(i, j), R"(^\*M\d+/\d+)")) { - measurein[inmap[currmeasure]].stimesig[track].x = -1; - measurein[inmap[currmeasure]].stimesig[track].y = -1; - } else if (hre.search(infile.token(i, j), R"(^\*met\(.*\))")) { - measurein[inmap[currmeasure]].smet[track].x = -1; - measurein[inmap[currmeasure]].smet[track].y = -1; - } else if (hre.search(infile.token(i, j), "^\\*MM\\d+", "i")) { - measurein[inmap[currmeasure]].stempo[track].x = -1; - measurein[inmap[currmeasure]].stempo[track].y = -1; - } - } + } + m_humdrum_text << "\n"; +} - if (infile.token(i, j)->compare(0, 5, "*clef") == 0) { - currclef[track].x = i; - currclef[track].y = j; - continue; - } - if (hre.search(infile.token(i, j), R"(^\*k\[.*\])")) { - currkeysig[track].x = i; - currkeysig[track].y = j; - continue; - } - if (hre.search(infile.token(i, j), "^\\*[A-G][#-]?:", "i")) { - currkey[track].x = i; - currkey[track].y = j; - continue; - } - if (hre.search(infile.token(i, j), R"(^\*M\d+/\d+)")) { - currtimesig[track].x = i; - currtimesig[track].y = j; - continue; - } - if (hre.search(infile.token(i, j), R"(^\*met\(.*\))")) { - currmet[track].x = i; - currmet[track].y = j; - continue; - } - if (hre.search(infile.token(i, j), R"(^\*MM[\d.]+)")) { - currtempo[track].x = i; - currtempo[track].y = j; - continue; - } - } - } - if (infile[i].isData()) { - datafound = 1; - } - } - // store state of global music values at end of music - if ((currmeasure >= 0) && (currmeasure < (int)inmap.size()) - && (inmap[currmeasure] >= 0)) { - measurein[inmap[currmeasure]].eclef = currclef; - measurein[inmap[currmeasure]].ekeysig = currkeysig; - measurein[inmap[currmeasure]].ekey = currkey; - measurein[inmap[currmeasure]].etimesig = currtimesig; - measurein[inmap[currmeasure]].emet = currmet; - measurein[inmap[currmeasure]].etempo = currtempo; - } +/////////////////////////////// +// +// Tool_pbar::printDataLine -- +// - // go through the measure list and clean up start/end states - for (i=0; i<(int)measurein.size()-2; i++) { +void Tool_pbar::printDataLine(HumdrumFile& infile, int index) { + printBarLine(infile, index); + m_humdrum_text << infile[index] << endl; +} - if (measurein[i].sclef.size() == 0) { - measurein[i].sclef.resize(tracks+1); - fill(measurein[i].sclef.begin(), measurein[i].sclef.end(), undefMyCoord); - } - if (measurein[i].eclef.size() == 0) { - measurein[i].eclef.resize(tracks+1); - fill(measurein[i].eclef.begin(), measurein[i].eclef.end(), undefMyCoord); - } - if (measurein[i+1].sclef.size() == 0) { - measurein[i+1].sclef.resize(tracks+1); - fill(measurein[i+1].sclef.begin(), measurein[i+1].sclef.end(), undefMyCoord); - } - if (measurein[i+1].eclef.size() == 0) { - measurein[i+1].eclef.resize(tracks+1); - fill(measurein[i+1].eclef.begin(), measurein[i+1].eclef.end(), undefMyCoord); - } - for (j=1; j<(int)measurein[i].sclef.size(); j++) { - if (!measurein[i].eclef[j].isValid()) { - if (measurein[i].sclef[j].isValid()) { - measurein[i].eclef[j] = measurein[i].sclef[j]; - } - } - if (!measurein[i+1].sclef[j].isValid()) { - if (measurein[i].eclef[j].isValid()) { - measurein[i+1].sclef[j] = measurein[i].eclef[j]; - } - } - } - if (measurein[i].skeysig.size() == 0) { - measurein[i].skeysig.resize(tracks+1); - fill(measurein[i].skeysig.begin(), measurein[i].skeysig.end(), undefMyCoord); - } - if (measurein[i].ekeysig.size() == 0) { - measurein[i].ekeysig.resize(tracks+1); - fill(measurein[i].ekeysig.begin(), measurein[i].ekeysig.end(), undefMyCoord); - } - if (measurein[i+1].skeysig.size() == 0) { - measurein[i+1].skeysig.resize(tracks+1); - fill(measurein[i+1].skeysig.begin(), measurein[i+1].skeysig.end(), undefMyCoord); - } - if (measurein[i+1].ekeysig.size() == 0) { - measurein[i+1].ekeysig.resize(tracks+1); - fill(measurein[i+1].ekeysig.begin(), measurein[i+1].ekeysig.end(), undefMyCoord); - } - for (j=1; j<(int)measurein[i].skeysig.size(); j++) { - if (!measurein[i].ekeysig[j].isValid()) { - if (measurein[i].skeysig[j].isValid()) { - measurein[i].ekeysig[j] = measurein[i].skeysig[j]; - } - } - if (!measurein[i+1].skeysig[j].isValid()) { - if (measurein[i].ekeysig[j].isValid()) { - measurein[i+1].skeysig[j] = measurein[i].ekeysig[j]; - } - } - } - if (measurein[i].skey.size() == 0) { - measurein[i].skey.resize(tracks+1); - fill(measurein[i].skey.begin(), measurein[i].skey.end(), undefMyCoord); - } - if (measurein[i].ekey.size() == 0) { - measurein[i].ekey.resize(tracks+1); - fill(measurein[i].ekey.begin(), measurein[i].ekey.end(), undefMyCoord); - } - if (measurein[i+1].skey.size() == 0) { - measurein[i+1].skey.resize(tracks+1); - fill(measurein[i+1].skey.begin(), measurein[i+1].skey.end(), undefMyCoord); - } - if (measurein[i+1].ekey.size() == 0) { - measurein[i+1].ekey.resize(tracks+1); - fill(measurein[i+1].ekey.begin(), measurein[i+1].ekey.end(), undefMyCoord); - } - for (j=1; j<(int)measurein[i].skey.size(); j++) { - if (!measurein[i].ekey[j].isValid()) { - if (measurein[i].skey[j].isValid()) { - measurein[i].ekey[j] = measurein[i].skey[j]; - } - } - if (!measurein[i+1].skey[j].isValid()) { - if (measurein[i].ekey[j].isValid()) { - measurein[i+1].skey[j] = measurein[i].ekey[j]; - } - } - } +/////////////////////////////// +// +// Tool_pbar::printBarLine -- Add *bar line. +// - if (measurein[i].stimesig.size() == 0) { - measurein[i].stimesig.resize(tracks+1); - fill(measurein[i].stimesig.begin(), measurein[i].stimesig.end(), undefMyCoord); - } - if (measurein[i].etimesig.size() == 0) { - measurein[i].etimesig.resize(tracks+1); - fill(measurein[i].etimesig.begin(), measurein[i].etimesig.end(), undefMyCoord); - } - if (measurein[i+1].stimesig.size() == 0) { - measurein[i+1].stimesig.resize(tracks+1); - fill(measurein[i+1].stimesig.begin(), measurein[i+1].stimesig.end(), undefMyCoord); - } - if (measurein[i+1].etimesig.size() == 0) { - measurein[i+1].etimesig.resize(tracks+1); - fill(measurein[i+1].etimesig.begin(), measurein[i+1].etimesig.end(), undefMyCoord); +void Tool_pbar::printBarLine(HumdrumFile& infile, int index) { + bool hasBarline = false; + for (int j=0; jgetValue("auto", "pbar"); + if (value == "true") { + hasBarline = true; + break; } - for (j=1; j<(int)measurein[i].stimesig.size(); j++) { - if (!measurein[i].etimesig[j].isValid()) { - if (measurein[i].stimesig[j].isValid()) { - measurein[i].etimesig[j] = measurein[i].stimesig[j]; - } + } + + if (hasBarline) { + for (int j=0; jgetValue("auto", "pbar"); + if (value == "true") { + m_humdrum_text << "*bar"; + } else { + m_humdrum_text << "*"; } - if (!measurein[i+1].stimesig[j].isValid()) { - if (measurein[i].etimesig[j].isValid()) { - measurein[i+1].stimesig[j] = measurein[i].etimesig[j]; - } + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; } } + m_humdrum_text << "\n"; + } +} - if (measurein[i].smet.size() == 0) { - measurein[i].smet.resize(tracks+1); - fill(measurein[i].smet.begin(), measurein[i].smet.end(), undefMyCoord); - } - if (measurein[i].emet.size() == 0) { - measurein[i].emet.resize(tracks+1); - fill(measurein[i].emet.begin(), measurein[i].emet.end(), undefMyCoord); - } - if (measurein[i+1].smet.size() == 0) { - measurein[i+1].smet.resize(tracks+1); - fill(measurein[i+1].smet.begin(), measurein[i+1].smet.end(), undefMyCoord); - } - if (measurein[i+1].emet.size() == 0) { - measurein[i+1].emet.resize(tracks+1); - fill(measurein[i+1].emet.begin(), measurein[i+1].emet.end(), undefMyCoord); + + +/////////////////////////////// +// +// Tool_pbar::printLocalCommentLine -- +// + +void Tool_pbar::printLocalCommentLine(HumdrumFile& infile, int index) { + HumRegex hre; + bool hasKp = false; + bool hasOther = false; + for (int i=0; iisLocalComment()) { + current = current->getNextToken(); + continue; } - if (measurein[i+1].etempo.size() == 0) { - measurein[i+1].etempo.resize(tracks+1); - fill(measurein[i+1].etempo.begin(), measurein[i+1].etempo.end(), undefMyCoord); + if (hre.search(current, "kreska\\s*pseudotaktowa")) { + addBarLineToFollowingNoteOrRest(current); } - for (j=1; j<(int)measurein[i].stempo.size(); j++) { - if (!measurein[i].etempo[j].isValid()) { - if (measurein[i].stempo[j].isValid()) { - measurein[i].etempo[j] = measurein[i].stempo[j]; - } - } - if (!measurein[i+1].stempo[j].isValid()) { - if (measurein[i].etempo[j].isValid()) { - measurein[i+1].stempo[j] = measurein[i].etempo[j]; - } + current = current->getNextToken(); + } +} + + + +////////////////////////////// +// +// Tool_pbar::addBarLineToFollowingNoteOrRest -- +// + +void Tool_pbar::addBarLineToFollowingNoteOrRest(HTp token) { + HTp current = token->getNextToken(); + int counter = 0; + while (current) { + if (!current->isBarline()) { + if (!current->isData() || current->isNull()) { + current = current->getNextToken(); + continue; } } + counter++; + if (counter == 2) { + current->setValue("auto", "pbar", "true"); + break; + } + current = current->getNextToken(); } } -////////////////////////////// + +///////////////////////////////// // -// Tool_myank::processFieldEntry -- -// 3-6 expands to 3 4 5 6 -// $ expands to maximum spine track -// $0 expands to maximum spine track -// $1 expands to maximum spine track minus 1, etc. -// 2-$1 expands to 2 through the maximum minus one. -// 6-3 expands to 6 5 4 3 -// $2-5 expands to the maximum minus 2 down through 5. -// Ignore negative values and values which exceed the maximum value. +// Tool_gridtest::Tool_pccount -- Set the recognized options for the tool. // -void Tool_myank::processFieldEntry(vector& field, - const string& str, HumdrumFile& infile, int maxmeasure, - vector& inmeasures, vector& inmap) { +Tool_pccount::Tool_pccount(void) { + define("a|attacks=b", "count attacks instead of durations"); + define("d|data|vega-data=b", "display the vega-lite template."); + define("f|full=b", "full count attacks all single sharps and flats."); + define("ff|double-full=b", "full count attacks all double sharps and flats."); + define("h|html=b", "generate vega-lite HTML content"); + define("i|id=s:id", "ID for use as variable and in plot title"); + define("K|no-key|no-final=b", "do not label key tonic or final"); + define("m|maximum=b", "normalize by maximum count"); + define("n|normalize=b", "normalize counts"); + define("p|page=b", "generate vega-lite stand-alone HTML page"); + define("r|ratio|aspect-ratio=d:0.67", "width*ratio=height of vega-lite plot"); + define("s|script|vega-script=b", "generate vega-lite javascript content"); + define("title=s", "title for plot"); + define("t|template|vega-template=b", "display the vega-lite template."); + define("w|width=i:400", "width of vega-lite plot"); +} - MeasureInfo current; - HumRegex hre; - string buffer = str; - // remove any comma left at end of input string (or anywhere else) - hre.replaceDestructive(buffer, "", ",", "g"); +/////////////////////////////// +// +// Tool_pccount::run -- Primary interfaces to the tool. +// - string measureStyling = ""; - if (hre.search(buffer, "([|:!=]+)$")) { - measureStyling = hre.getMatch(1); - hre.replaceDestructive(buffer, "", "([|:!=]+)$"); +bool Tool_pccount::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i maxmeasure) { firstone = maxmeasure; } - if (lastone > maxmeasure) { lastone = maxmeasure; } - if (firstone < 0 ) { firstone = 0 ; } - if (lastone < 0 ) { lastone = 0 ; } +bool Tool_pccount::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + return run(infile, out); +} - if ((firstone < 1) && (firstone != 0)) { - cerr << "Error: range token: \"" << str << "\"" - << " contains too small a number at start: " << firstone << endl; - cerr << "Minimum number allowed is " << 1 << endl; - exit(1); + +bool Tool_pccount::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + return status; +} + + +bool Tool_pccount::run(HumdrumFile& infile) { + initialize(infile); + processFile(infile); + return true; +} + + + +////////////////////////////// +// +// Tool_pccount::initialize -- +// + +void Tool_pccount::initialize(HumdrumFile& infile) { + m_attack = getBoolean("attacks"); + m_full = getBoolean("full"); + m_doublefull = getBoolean("double-full"); + m_normalize = getBoolean("normalize"); + m_maximum = getBoolean("maximum"); + m_template = getBoolean("vega-template"); + m_data = getBoolean("vega-data"); + m_script = getBoolean("vega-script"); + m_width = getInteger("width"); + m_ratio = getDouble("aspect-ratio"); + m_key = !getBoolean("no-key"); + if (getBoolean("title")) { + m_title = getString("title"); + } + m_html = getBoolean("html"); + m_page = getBoolean("page"); + if (getBoolean("id")) { + m_id = getString("id"); + } else { + string filename = infile.getFilename(); + auto pos = filename.rfind("/"); + if (pos != string::npos) { + filename = filename.substr(pos+1); } - if ((lastone < 1) && (lastone != 0)) { - cerr << "Error: range token: \"" << str << "\"" - << " contains too small a number at end: " << lastone << endl; - cerr << "Minimum number allowed is " << 1 << endl; - exit(1); + pos = filename.find("-"); + if (pos != string::npos) { + m_id = filename.substr(0, pos); } + } + m_parttracks.clear(); + m_names.clear(); + m_abbreviations.clear(); + initializePartInfo(infile); - if (firstone > lastone) { - // reverse the order of the measures - for (int i=firstone; i>=lastone; i--) { - if (inmap[i] >= 0) { - current.clear(); - current.file = &infile; - current.num = i; - current.start = inmeasures[inmap[i]].start; - current.stop = inmeasures[inmap[i]].stop; - current.sclef = inmeasures[inmap[i]].sclef; - current.skeysig = inmeasures[inmap[i]].skeysig; - current.skey = inmeasures[inmap[i]].skey; - current.stimesig = inmeasures[inmap[i]].stimesig; - current.smet = inmeasures[inmap[i]].smet; - current.stempo = inmeasures[inmap[i]].stempo; + // https://encycolorpedia.com/36cd27 + m_vcolor.clear(); - current.eclef = inmeasures[inmap[i]].eclef; - current.ekeysig = inmeasures[inmap[i]].ekeysig; - current.ekey = inmeasures[inmap[i]].ekey; - current.etimesig = inmeasures[inmap[i]].etimesig; - current.emet = inmeasures[inmap[i]].emet; - current.etempo = inmeasures[inmap[i]].etempo; + m_vcolor["Canto"] = "#e49689"; + m_vcolor["Canto (Canto I)"] = "#e49689"; + m_vcolor["Canto I"] = "#e49689"; + m_vcolor["Canto Primo"] = "#e49689"; + m_vcolor["[Canto 1]"] = "#e49689"; + m_vcolor["[Canto]"] = "#e49689"; + m_vcolor["[Soprano o Tenore]"] = "#e49689"; + m_vcolor["Soprano"] = "#e49689"; - field.push_back(current); - } - } - } else { - // measure range not reversed - for (int i=firstone; i<=lastone; i++) { - if (inmap[i] >= 0) { - current.clear(); - current.file = &infile; - current.num = i; - current.start = inmeasures[inmap[i]].start; - current.stop = inmeasures[inmap[i]].stop; + m_vcolor["Canto 2."] = "#d67365"; + m_vcolor["Canto II"] = "#d67365"; + m_vcolor["Canto II [Sesto]"] = "#d67365"; + m_vcolor["Canto Sec."] = "#d67365"; + m_vcolor["Canto Secondo"] = "#d67365"; + m_vcolor["Canto secondo"] = "#d67365"; + m_vcolor["[Canto 2]"] = "#d67365"; - current.sclef = inmeasures[inmap[i]].sclef; - current.skeysig = inmeasures[inmap[i]].skeysig; - current.skey = inmeasures[inmap[i]].skey; - current.stimesig = inmeasures[inmap[i]].stimesig; - current.smet = inmeasures[inmap[i]].smet; - current.stempo = inmeasures[inmap[i]].stempo; + m_vcolor["Canto III [Settimo]"] = "#c54f43"; - current.eclef = inmeasures[inmap[i]].eclef; - current.ekeysig = inmeasures[inmap[i]].ekeysig; - current.ekey = inmeasures[inmap[i]].ekey; - current.etimesig = inmeasures[inmap[i]].etimesig; - current.emet = inmeasures[inmap[i]].emet; - current.etempo = inmeasures[inmap[i]].etempo; + m_vcolor["Alto"] = "#f4c6a1"; + m_vcolor["Alti"] = "#f4c6a1"; + m_vcolor["Alto (Canto III)"] = "#f4c6a1"; - field.push_back(current); - } - } - } - } else if (hre.search(buffer, "^(\\d+)([a-z]*)")) { - // processing a single measure number - int value = hre.getMatchInt(1); - // do something with letter later... + m_vcolor["Alto II"] = "#edb383"; - if ((value < 1) && (value != 0)) { - cerr << "Error: range token: \"" << str << "\"" - << " contains too small a number at end: " << value << endl; - cerr << "Minimum number allowed is " << 1 << endl; - exit(1); - } - if (inmap[value] >= 0) { - current.clear(); - current.file = &infile; - current.num = value; - current.start = inmeasures[inmap[value]].start; - current.stop = inmeasures[inmap[value]].stop; + m_vcolor["Tenor"] = "#ecdf7a"; + m_vcolor["Tenore"] = "#ecdf7a"; + m_vcolor["Tenore over Canto"] = "#ecdf7a"; + m_vcolor["[Tenore]"] = "#ecdf7a"; - current.sclef = inmeasures[inmap[value]].sclef; - current.skeysig = inmeasures[inmap[value]].skeysig; - current.skey = inmeasures[inmap[value]].skey; - current.stimesig = inmeasures[inmap[value]].stimesig; - current.smet = inmeasures[inmap[value]].smet; - current.stempo = inmeasures[inmap[value]].stempo; + m_vcolor["Sesto"] = "#c8f0bb"; + m_vcolor["Sesto (Canto II)"] = "#c8f0bb"; + m_vcolor["Sesto Canto II"] = "#c8f0bb"; - current.eclef = inmeasures[inmap[value]].eclef; - current.ekeysig = inmeasures[inmap[value]].ekeysig; - current.ekey = inmeasures[inmap[value]].ekey; - current.etimesig = inmeasures[inmap[value]].etimesig; - current.emet = inmeasures[inmap[value]].emet; - current.etempo = inmeasures[inmap[value]].etempo; + m_vcolor["Quinto"] = "#e3f5f8"; + m_vcolor["Qvinto"] = "#e3f5f8"; - field.push_back(current); - } - } + m_vcolor["Ottava parte [Ottavo]"] = "#e0e4f7"; - field.back().stopStyle = measureStyling; + m_vcolor["Nona parte [Nono]"] = "#a39ce5"; + + m_vcolor["Basso"] = "#d2aef7"; + m_vcolor["Bass"] = "#d2aef7"; + + m_vcolor["Basso II"] = "#c69af5"; + m_vcolor["Basso II [Decimo]"] = "#c69af5"; + m_vcolor["Basso Continuo"] = "#a071ec"; + m_vcolor["Basso continuo"] = "#a071ec"; + m_vcolor["[B. C.]"] = "#a071ec"; + m_vcolor["[Basso Continuo]"] = "#a071ec"; + m_vcolor["[Basso continuo]"] = "#a071ec"; + m_vcolor["B.C."] = "#a071ec"; + m_vcolor["B.c."] = "#a071ec"; } ////////////////////////////// // -// Tool_myank::removeDollarsFromString -- substitute $ sign for maximum track count. +// Tool_pccount::getFinal -- Extract the last unparenthesed letter from a ref record like this: +// +// !!!final: (A)D // -void Tool_myank::removeDollarsFromString(string& buffer, int maxx) { +string Tool_pccount::getFinal(HumdrumFile& infile) { + string finalref = infile.getReferenceRecord("final"); HumRegex hre; - HumRegex hre2; - string tbuf; - string obuf; - int outval; - int value; - - if (m_debugQ) { - m_free_text << "MEASURE STRING BEFORE DOLLAR REMOVAL: " << buffer << endl; + hre.replaceDestructive(finalref, "", "\\(.*?\\)", "g"); + hre.replaceDestructive(finalref, "", "\\s+", "g"); + if (hre.search(finalref, "^[A-G]$", "i")) { + return finalref; + } else { + return ""; } +} - while (hre.search(buffer, "(\\$\\d*)", "")) { - tbuf = hre.getMatch(1); - if (hre2.search(tbuf, "(\\$\\d+)")) { - sscanf(hre2.getMatch(1).c_str(), "$%d", &value); - outval = maxx - value; - } else { - outval = maxx; - } - if (outval < 0) { - outval = 0; - } - tbuf = to_string(outval); - obuf = "\\"; - obuf += hre.getMatch(1); - hre.replaceDestructive(buffer, tbuf, obuf); +////////////////////////////// +// +// Tool_pccount::processFile -- +// + +void Tool_pccount::processFile(HumdrumFile& infile) { + countPitches(infile); + + string datavar; + string target; + string jsonvar; + + if (m_attack) { + datavar = "data_" + m_id + "_count"; + target = "id_" + m_id + "_count"; + jsonvar = "vega_" + m_id + "_count"; + } else { + datavar = "data_" + m_id + "_dur"; + target = "id_" + m_id + "_dur"; + jsonvar = "vega_" + m_id + "_dur"; } - if (m_debugQ) { - m_free_text << "DOLLAR EXPAND: " << buffer << endl; + + if (m_template) { + printVegaLiteJsonTemplate(datavar, infile); + } else if (m_data) { + printVegaLiteJsonData(); + } else if (m_script) { + printVegaLiteScript(jsonvar, target, datavar, infile); + } else if (m_html) { + printVegaLiteHtml(jsonvar, target, datavar, infile); + } else if (m_page) { + printVegaLitePage(jsonvar, target, datavar, infile); + } else { + printHumdrumTable(); } } +////////////////////////////// +// +// Tool_pccount::printVegaLitePage -- +// + +void Tool_pccount::printVegaLitePage(const string& jsonvar, + const string& target, const string& datavar, HumdrumFile& infile) { + stringstream& out = m_free_text; + + out << "\n"; + out << "\n"; + out << " \n"; + out << " Vega-Lite Bar Chart\n"; + out << " \n"; + out << "\n"; + out << " \n"; + out << " \n"; + out << " \n"; + out << "\n"; + out << " \n"; + out << " \n"; + out << " \n"; + out << "

    Pitch-class histogram

    \n"; + printVegaLiteHtml(jsonvar, target, datavar, infile); + out << "\n"; + out << "\n"; +} + + + +////////////////////////////// +// +// Tool_pccount::printVegaLiteHtml -- +// + +void Tool_pccount::printVegaLiteHtml(const string& jsonvar, + const string& target, const string& datavar, HumdrumFile& infile) { + stringstream& out = m_free_text; + + out << "
    \n"; + out << "\n"; + out << "\n"; +} ////////////////////////////// // -// Tool_myank::example -- example function calls to the program. +// Tool_pccount::printVegaLiteScript -- // -void Tool_myank::example(void) { - +void Tool_pccount::printVegaLiteScript(const string& jsonvar, + const string& target, const string& datavar, HumdrumFile& infile) { + stringstream& out = m_free_text; + out << "var " << datavar << " =\n"; + printVegaLiteJsonData(); + out << ";\n"; + out << "\n"; + out << "var " << jsonvar << " =\n"; + printVegaLiteJsonTemplate(datavar, infile); + out << ";\n"; + out << "vegaEmbed('#" << target << "', " << jsonvar << ");\n"; } ////////////////////////////// // -// Tool_myank::usage -- command-line usage description and brief summary +// Tool_pccount::printVegaLiteJsonData -- // -void Tool_myank::usage(const string& ommand) { +void Tool_pccount::printVegaLiteJsonData(void) { + stringstream& out = m_free_text; + m_maxpc = 0; + for (int i=0; i<(int)m_counts[0].size(); i++) { + if (m_counts[0][i] > m_maxpc) { + m_maxpc = m_counts[0][i]; + } + } + out << "[\n"; + int commacounter = 0; + double percent = 100.0; + for (int i=1; i<(int)m_counts.size(); i++) { + for (int j=0; j<(int)m_counts[i].size(); j++) { + if (m_counts[i][j] == 0.0) { + continue; + } + if (commacounter > 0) { + out << ",\n\t"; + } else { + out << "\t"; + } + commacounter++; + if (m_attack) { + out << "{\"count\":" << m_counts[i][j] << ", "; + } else { + out << "{\"percent\":" << m_counts[i][j]/m_maxpc*percent << ", "; + } + out << "\"pitch class\":\"" << getPitchClassString(j) << "\", "; + out << "\"voice\":\"" << m_names[i] << "\""; + out << "}"; + } + } + out << "\n]\n"; } - -///////////////////////////////// +////////////////////////////// // -// Tool_nproof::Tool_nproof -- Set the recognized options for the tool. +// Tool_pccount::setFactorMaximum -- normalize by the maximum pitch-class value. // -Tool_nproof::Tool_nproof(void) { - define("B|no-blank|no-blanks=b", "do not check for blank lines.\n"); - define("b|only-blank|only-blanks=b", "only check for blank lines.\n"); - - define("I|no-instrument|no-instruments=b", "do not check instrument interpretations.\n"); - define("i|only-instrument|only-instruments=b", "only check instrument interpretations.\n"); +void Tool_pccount::setFactorMaximum(void) { + m_factor = 0.0; + for (int i=0; i<(int)m_counts[0].size(); i++) { + if (m_counts[0][i] > m_factor) { + m_factor = m_counts[0][i]; + } + } +} - define("K|no-key=b", "do not check for !!!key: manual initial key designation.\n"); - define("k|only-key=b", "only check for !!!key: manual initial key designation.\n"); - define("R|no-reference=b", "do not check for reference records.\n"); - define("r|only-reference=b", "only check for reference records.\n"); - define("T|no-termination|no-terminations=b", "do not check spine terminations.\n"); - define("t|only-termination|only-terminations=b", "only check spine terminations.\n"); +////////////////////////////// +// +// Tool_pccount::setFactorNormalize -- normalize by the sum of all pitch class values. +// - define("file|filename=b", "print filename with raw count (if available).\n"); - define("raw=b", "only print error count.\n"); +void Tool_pccount::setFactorNormalize(void) { + m_factor = 0.0; + for (int i=0; i<(int)m_counts[0].size(); i++) { + m_factor += m_counts[0][i]; + } } -///////////////////////////////// +////////////////////////////// // -// Tool_nproof::run -- Do the main work of the tool. +// Tool_pccount::printHumdrumTable -- // -bool Tool_nproof::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisKern()) { return; } - - if (m_errorCount > 0) { - m_humdrum_text << m_errorList; - m_humdrum_text << "!!!TOOL-nproof-error-count: " << m_errorCount << endl; - m_humdrum_text << "!!@@BEGIN: PREHTML\n"; - m_humdrum_text << "!!@TOOL: nproof\n"; - m_humdrum_text << "!!@CONTENT:\n"; - m_humdrum_text << "!!

    @{TOOL-nproof-error-count} problem"; - if (m_errorCount != 1) { - m_humdrum_text << "s"; + int track = sstart->getTrack(); + int kindex = m_rkern[track]; + HTp current = sstart; + while (current && (current != send)) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - m_humdrum_text << " detected

    \n"; - m_humdrum_text << "!!
      \n"; - m_humdrum_text << m_errorHtml; - m_humdrum_text << "!!
    \n"; - m_humdrum_text << "!!@@END: PREHTML\n"; - } else { - m_humdrum_text << "!!@@BEGIN: PREHTML\n"; - m_humdrum_text << "!!@TOOL: nproof\n"; - m_humdrum_text << "!!@CONTENT:\n"; - m_humdrum_text << "!!

    No problems detected

    \n"; - m_humdrum_text << "!!@@END: PREHTML\n"; + if (current->isNull() || current->isRest()) { + current = current->getNextToken(); + continue; + } + vector subtokens = current->getSubtokens(); + for (int i=0; i<(int)subtokens.size(); i++) { + if (m_attack) { + // ignore sustained parts of notes when counting attacks + if (subtokens[i].find("_") != string::npos) { + continue; + } + if (subtokens[i].find("]") != string::npos) { + continue; + } + } + int b40 = Convert::kernToBase40(subtokens[i]); + if (m_attack) { + m_counts[kindex][b40%40]++; + } else { + double duration = Convert::recipToDuration(subtokens[i]).getFloat(); + m_counts[kindex][b40%40] += duration; + } + } + current = current->getNextToken(); } } + ////////////////////////////// // -// Tool_nproof::checkForBlankLines -- +// Tool_pccount::initializePartInfo -- // -void Tool_nproof::checkForBlankLines(HumdrumFile& infile) { - vector blanks; - // -1: Not checking for a blank line at the very end of the score. - for (int i=0; i starts = infile.getKernSpineStartList(); + + int foundpart = false; + int foundabbr = false; + + int track = 0; + for (int i=0; i<(int)starts.size(); i++) { + track = starts[i]->getTrack(); + m_rkern[track] = i+1; + m_parttracks.push_back(track); + HTp current = starts[i]; + foundpart = false; + foundabbr = false; + if (!current->isKern()) { continue; } - HTp token = infile.token(i, 0); - if (*token == "") { - blanks.push_back(i+1); + while (current) { + if (current->isData()) { + break; + } + if ((!foundpart) && (current->compare(0, 3, "*I\"") == 0)) { + m_names.emplace_back(current->substr(3)); + foundpart = true; + } else if ((!foundabbr) && (current->compare(0, 3, "*I\'") == 0)) { + m_abbreviations.emplace_back(current->substr(3)); + foundabbr = true; + } + current = current->getNextToken(); } + //if (!foundpart) { + // m_names.emplace_back(""); + //} + //if (!foundabbr) { + // m_names.emplace_back(""); + //} } - if (blanks.empty()) { - return; - } - - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Blank lines on row"; - if (blanks.size() != 1) { - m_errorList += "s"; - } - m_errorList += ": "; - for (int i=0; i<(int)blanks.size(); i++) { - m_errorList += to_string(blanks[i]); - if (i < (int)blanks.size() - 1) { - m_errorList += ", "; - } - } - m_errorList += ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; } - ////////////////////////////// // -// Tool_nproof::checkForValidInstrumentCode -- +// printVegaLiteJsonTemplate -- // -void Tool_nproof::checkForValidInstrumentCode(HTp token, - vector>& instrumentList) { +void Tool_pccount::printVegaLiteJsonTemplate(const string& datavariable, HumdrumFile& infile) { + stringstream& out = m_free_text; - if ((token->find("&") == string::npos) && (token->find("|") == string::npos)) { - string code = token->substr(2); - for (int i=0; i<(int)instrumentList.size(); i++) { - if (instrumentList[i].first == code) { - return; - } + string idinfo; + if (m_id.empty() || m_id == "id") { + // do nothing + } else { + idinfo = "for " + m_id; + } + out << "{\n"; + out << " \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.0.0-beta.1.json\",\n"; + out << " \"data\": {\"values\": " << datavariable << "},\n"; + if (getBoolean("title")) { + out << " \"title\": \"" << m_title << "\",\n"; + } else { + if (m_attack) { + out << " \"title\": \"Note-count pitch-class distribution " << idinfo <<" \",\n"; + } else { + out << " \"title\": \"Duration-weighted pitch-class distribution " << idinfo <<" \",\n"; } - - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + code + "\" on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at https://bit.ly/humdrum-instrument-codes.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - return; } + out << " \"width\": " << m_width << ",\n"; + out << " \"height\": " << int(m_width * m_ratio) << ",\n"; + out << " \"encoding\": {\n"; + out << " \"y\": {\n"; + if (m_attack) { + out << " \"field\": \"count\",\n"; + out << " \"title\": \"Number of note attacks\",\n"; + } else { + out << " \"field\": \"percent\",\n"; + out << " \"title\": \"Percent of maximum pitch class\",\n"; + } + out << " \"type\": \"quantitative\",\n"; + if (m_attack) { + out << " \"scale\": {\"domain\": [0, " << m_maxpc << "]},\n"; + } else { + out << " \"scale\": {\"domain\": [0, 100]},\n"; + } + out << " \"aggregate\": \"sum\"\n"; + out << " },\n"; + out << " \"x\": {\n"; + out << " \"field\": \"pitch class\",\n"; + out << " \"type\": \"nominal\",\n"; + out << " \"scale\": {\n"; + out << " \"domain\": ["; + printPitchClassList(); + out << "]\n"; + out << " },\n"; + out << " \"axis\": {\n"; + out << " \"labelAngle\": 0\n"; + out << " }\n"; + out << " },\n"; + out << " \"order\": {\"type\": \"quantitative\"},\n"; + out << " \"color\": {\n"; + out << " \"field\": \"voice\",\n"; + out << " \"type\": \"nominal\",\n"; + if (m_counts.size() == 2) { + out << " \"legend\": {\"title\": \"Voice\"},\n"; + } else { + out << " \"legend\": {\"title\": \"Voices\"},\n"; + } + out << " \"scale\": {\n"; + out << " \"domain\": ["; + printVoiceList(); + out << "],\n"; + out << " \"range\": ["; + printColorList(); + out << "],\n"; + out << " }\n"; + out << " }\n"; + out << " },\n"; - bool found1 = false; - bool found2 = false; - string inst1; - string inst2; - HumRegex hre; - if (hre.match(token, "^\\*I(.*)[&|](I.*)")) { - inst1 = hre.getMatch(1); - inst2 = hre.getMatch(2); + out << " \"layer\": [\n"; + out << " {\"mark\": \"bar\"}"; - for (int i=0; i<(int)instrumentList.size(); i++) { - if (instrumentList[i].first == inst1) { - found1 = true; - } - if (instrumentList[i].first == inst2) { - found2 = true; - } + string final = getFinal(infile); + if (m_key && !final.empty()) { + out << ",\n"; + out << " {\n"; + out << " \"mark\": {\"type\":\"text\", \"align\":\"center\", \"fill\":\"black\", \"baseline\":\"bottom\"},\n"; + if (m_attack) { + int count = getCount(final); + out << " \"data\": {\"values\": [ {\"pitch class\":\"" << final << "\", \"count\":" << count << "}]},\n"; + } else { + double percent = getPercent(final); + out << " \"data\": {\"values\": [ {\"pitch class\":\"" << final << "\", \"percent\":" << percent << "}]},\n"; } + out << " \"encoding\": {\"text\": {\"value\":\"final\"}}\n"; + out << " }\n"; } - if (!found1) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + inst1 + "\" in token " + *token + " on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at https://bit.ly/humdrum-instrument-codes.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - - if (!found2) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + inst2 + "\" in token " + *token + " on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at https://bit.ly/humdrum-instrument-codes.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } + out << " ]\n"; + out << "}\n"; } @@ -112075,426 +116477,234 @@ void Tool_nproof::checkForValidInstrumentCode(HTp token, ////////////////////////////// // -// Tool_nproof::checkInstrumentInformation -- +// Tool_pccount::getCount -- // -void Tool_nproof::checkInstrumentInformation(HumdrumFile& infile) { - int codeLine = -1; - int classLine = -1; - HumRegex hre; +int Tool_pccount::getCount(const string& pitchclass) { + int b40 = Convert::kernToBase40(pitchclass); + int index = b40 % 40; + int output = (int)m_counts[0][index]; + return output; +} - vector> instrumentList = Convert::getInstrumentList(); - for (int i=0; iisKern()) { - continue; - } - if (token->compare(0, 3, "*IC") == 0) { - if (classLine < 0) { - classLine = i; - } - } else if (hre.search(token, "^\\*I[a-z]")) { - if (codeLine < 0) { - codeLine = i; - } - } - } - } - if (codeLine < 0) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No instrument code line.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } else { - for (int i=0; iisKern()) { - if (!hre.search(token, "^\\*I[a-z]")) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": expected instrument code on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } else { - checkForValidInstrumentCode(token, instrumentList); - } - } else { - if (*token != "*") { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Expected null interpretation on instrument code line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } - } - } +////////////////////////////// +// +// Tool_pccount::getPercent -- +// - if (classLine < 0) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No instrument class line.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } else { - for (int i=0; iisKern()) { - if (!hre.search(token, "^\\*IC[a-z]")) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": expected instrument class on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } else { - if (*token != "*") { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Expected null interpretation on instrument class line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } - } - } +double Tool_pccount::getPercent(const string& pitchclass) { + setFactorMaximum(); + int b40 = Convert::kernToBase40(pitchclass); + int index = b40 % 40; + double output = m_counts[0][index] / m_factor * 100.0; + return output; } ////////////////////////////// // -// Tool_nproof::checkReferenceRecords -- +// Tool_pccount::printColorList -- // -void Tool_nproof::checkReferenceRecords(HumdrumFile& infile) { - vector foundENC; // Musescore encoder's name - vector foundEND; // Musescore encdoer's date - vector foundEED; // VHV editor's name - vector foundEEV; // VHV editor's date - - HumRegex hre; - for (int i=0; i\n"; - } - } - if (hre.search(key, "^EEV\\d*$")) { - if (key == "EEV") { - foundEEV.push_back(i);; - } - string value = infile[i].getReferenceValue(); - if (!hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For EEV (ElEctronic Version) record on line " + to_string(i+1) + ", found a name rather than a date (or invalid date): " + value + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } - if (hre.search(key, "^ENC\\d*(-modern|-iiif)?$")) { - string value = infile[i].getReferenceValue(); - if (hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For ENC (Electronic eNCoder) record on line " + to_string(i+1) + ", found a date rather than a name: " + value + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } - if (hre.search(key, "^END\\d*(-modern|-iiif)?$")) { - string value = infile[i].getReferenceValue(); - if (!hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For END (Electronic eNcoding Date) record on line " + to_string(i+1) + ", found a name rather than a date (or an invalid date): " + value + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } - if (hre.search(key, "^ENC(\\d*.*)$")) { - if (key == "ENC") { - foundENC.push_back(i); - } - } - if (hre.search(key, "^ENC-(\\d+.*)$")) { - string newvalue = "ENC" + hre.getMatch(1); - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not include a dash and instead be: " + newvalue + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (hre.search(key, "END(\\d*.*)")) { - if (key == "END") { - foundEND.push_back(i); - } - } - if (hre.search(key, "^END-(\\d+.*)$")) { - string newvalue = "END" + hre.getMatch(1); - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not include a dash and instead be: " + newvalue + ".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (key == "filter-") { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": \"filter-\" reference record on line " + to_string(i+1) + " should probably be \"filter-modern\" instead.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (key == "ENC-mod") { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": ENC-mod reference record on line " + to_string(i+1) + " should be ENC-modern instead.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (key == "END-mod") { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": END-mod reference record on line " + to_string(i+1) + " should be END-modern instead.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (key == "AIN-mod") { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": AIN-mod reference record on line " + to_string(i+1) + " should be AIN-modern instead.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; +void Tool_pccount::printColorList(void) { + stringstream& out = m_free_text; + for (int i=(int)m_names.size() - 1; i>0; i--) { + string color = m_vcolor[m_names[i]]; + out << "\""; + if (color.empty()) { + out << "black"; + } else { + out << color; } - if (hre.search(key, "^(.*)-ori$")) { - string piece = hre.getMatch(1); - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not be used (either use " + piece + "-mod or don't add -ori qualifier to " + piece + ").\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + out << "\""; + if (i > 1) { + out << ", "; } } +} - // vector foundENC; // Musescore encoder's name - // vector foundEND; // Musescore encdoer's date - // vector foundEED; // VHV editor's name - // vector foundEEV; // VHV editor's date - if (foundENC.empty()) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing ENC (initial encoder's name) reference record.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (foundEND.empty()) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing END (initial encoding date) reference record.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (foundEED.empty()) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing EED (Humdrum electronic editor's name) reference record.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if (foundEEV.empty()) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing EEV (Humdrum electronic edition date) reference record.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - if ((foundENC.size() == 1) && (foundEED.size() == 1)) { - if (foundENC[0] > foundEED[0]) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": ENC reference record on line " + to_string(foundENC[0]+1) + " should come before EED reference record on line " + to_string(foundEED[0]+1) + "\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } +////////////////////////////// +// +// Tool_pccount::printVoiceList -- +// - if ((foundEND.size() == 1) && (foundEEV.size() == 1)) { - if (foundEND[0] > foundEEV[0]) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": END reference record on line " + to_string(foundEND[0]+1) + " should come before EEV reference record on line " + to_string(foundEEV[0]+1) + "\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; +void Tool_pccount::printVoiceList(void) { + stringstream& out = m_free_text; + for (int i=(int)m_names.size() - 1; i>0; i--) { + out << "\""; + out << m_names[i]; + out << "\""; + if (i > 1) { + out << ", "; } } +} - if ((foundENC.size() == 2) && (foundEED.size() == 0)) { - string date1; - string date2; - if (foundEND.size() == 2) { - date1 = infile[foundEND[0]].getReferenceValue(); - date2 = infile[foundEND[1]].getReferenceValue(); - hre.replaceDestructive(date1, "", "-", "g"); - hre.replaceDestructive(date2, "", "-", "g"); - int number1 = 0; - int number2 = 0; - if (hre.search(date1, "^(20\\d{6})$")) { - number1 = hre.getMatchInt(1); - } - if (hre.search(date2, "^(20\\d{6})$")) { - number2 = hre.getMatchInt(1); - } - if ((number1 > 0) && (number2 > 0)) { - if (number1 > number2) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Second ENC reference record on line " + to_string(foundENC[1]+1) + " should probably be changed to EED reference record (and second END reference record on line " + to_string(foundEND[1]+1) + " changed to EEV).\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } - } else { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": There are two ENC records on lines " + to_string(foundENC[0]+1) + " and " + to_string(foundENC[1]+1) + ". The Humdrum editor's name should be changed to EED, and the editing date should be changed from END to EEV if necessary.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + +////////////////////////////// +// +// Tool_pccount::printReverseVoiceList -- +// + +void Tool_pccount::printReverseVoiceList(void) { + stringstream& out = m_free_text; + for (int i=1; i<(int)m_names.size(); i++) { + out << "\""; + out << m_names[i]; + out << "\""; + if (i < (int)m_names.size() - 1) { + out << ", "; } } - } ////////////////////////////// // -// Tool_nproof::checkKeyInformation -- +// Tool_pccount::printPitchClassList -- // -void Tool_nproof::checkKeyInformation(HumdrumFile& infile) { - int foundKey = -1; - for (int i=0; icompare(0, 7, "!!!key:") == 0) { - foundKey = i; - break; - } - } - - if (foundKey < 0) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No !!!key: reference record.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - return; - } +void Tool_pccount::printPitchClassList(void) { + stringstream& out = m_free_text; - string value = infile[foundKey].getReferenceValue(); - if (value.empty()) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": !!!key: reference record on line " + to_string(foundKey+1) + " should not be empty. If no key, then use \"none\".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - return; - } + if (m_counts[0][0] > 0.0) { out << "\"C♭♭\", "; } + if (m_counts[0][1] > 0.0) { out << "\"C♭\", "; } + out << "\"C\""; + if (m_counts[0][3] > 0.0) { out << ", \"C♯\""; } + if (m_counts[0][4] > 0.0) { out << ", \"C♯♯\""; } + // 5 is empty - HumRegex hre; - if (hre.search(value, "^([a-gA-G][#-n]?):(dor|phr|lyd|mix|aeo|loc|ion)$")) { - string tonic = hre.getMatch(1); - string mode = hre.getMatch(2); - int major = 0; - if ((mode == "lyd") || (mode == "mix") || (mode == "ion")) { - major = 1; - } - int uppercase = isupper(tonic[0]); - if ((major == 1) && (uppercase == 0)) { - tonic[0] = toupper(tonic[0]); - string correct = tonic + ":" + mode; - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": !!!key: reference record on line " + to_string(foundKey + 1) + " should be \"" + correct + "\".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } else if ((major == 0) && (uppercase == 1)) { - tonic[0] = tolower(tonic[0]); - string correct = tonic + ":" + mode; - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": !!!key: reference record on line " + to_string(foundKey + 1) + " should be \"" + correct + "\".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } - } else if (hre.search(value, "([a-gA-G][#-n]?):(.+)")) { - string mode = hre.getMatch(2); - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown mode in !!!key: reference record contents on line " + to_string(foundKey + 1) + ": \"" + mode + "\".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } else if (!hre.search(value, "([a-gA-G][#-n]?):?")) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown key designation in !!!key: reference record contents on line " + to_string(foundKey + 1) + ": \"" + value + "\".\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - } + if (m_counts[0][6] > 0.0) { out << ", \"D♭♭\""; } + if (m_counts[0][7] > 0.0) { out << ", \"D♭\""; } + out << ", \"D\""; + if (m_counts[0][9] > 0.0) { out << ", \"D♯\""; } + if (m_counts[0][10] > 0.0) { out << ", \"D♯♯\""; } + // 11 is empty -} + if (m_counts[0][12] > 0.0) { out << ", \"E♭♭\""; } + if (m_counts[0][13] > 0.0) { out << ", \"E♭\""; } + out << ", \"E\""; + if (m_counts[0][15] > 0.0) { out << ", \"E♯\""; } + if (m_counts[0][16] > 0.0) { out << ", \"E♯♯\""; } + if (m_counts[0][17] > 0.0) { out << ", \"F♭♭\""; } + if (m_counts[0][18] > 0.0) { out << ", \"F♭\""; } + out << ", \"F\""; + if (m_counts[0][20] > 0.0) { out << ", \"F♯\""; } + if (m_counts[0][21] > 0.0) { out << ", \"F♯♯\""; } + // 22 is empty + if (m_counts[0][23] > 0.0) { out << ", \"G♭♭\""; } + if (m_counts[0][24] > 0.0) { out << ", \"G♭\""; } + out << ", \"G\""; + if (m_counts[0][26] > 0.0) { out << ", \"G♯\""; } + if (m_counts[0][27] > 0.0) { out << ", \"G♯♯\""; } + // 28 is empty -////////////////////////////// -// -// Tool_nproof::checkSpineTerminations -- -// + if (m_counts[0][29] > 0.0) { out << ", \"A♭♭\""; } + if (m_counts[0][30] > 0.0) { out << ", \"A♭\""; } + out << ", \"A\""; + if (m_counts[0][32] > 0.0) { out << ", \"A♯\""; } + if (m_counts[0][33] > 0.0) { out << ", \"A♯♯\""; } + // 34 is empty -void Tool_nproof::checkSpineTerminations(HumdrumFile& infile) { - int foundTerminal = 0; - for (int i=infile.getLineCount() - 1; i>0; i--) { - if (!infile[i].isInterpretation()) { - continue; - } - HTp token = infile.token(i, 0); - if (*token == "*-") { - foundTerminal = i; - break; - } - } + if (m_counts[0][35] > 0.0) { out << ", \"B♭♭\""; } + if (m_counts[0][36] > 0.0) { out << ", \"B♭\""; } + out << ", \"B\""; + if (m_counts[0][38] > 0.0) { out << ", \"B♯\""; } + if (m_counts[0][39] > 0.0) { out << ", \"B♯♯\""; } - if (!foundTerminal) { - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No spine terminators.\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; - return; - } +} - bool problem = false; - for (int i=0; igetSpineInfo(); - if (value.find(" ") != string::npos) { - problem = true; - break; - } - } - if (!problem) { - return; - } +////////////////////////////// +// +// Tool_pccount::getPitchClassString -- +// - m_errorCount++; - m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Incorrect spine merger(s): "; - for (int i=0; igetSpineInfo() + ">"; - if (i < infile[foundTerminal].getFieldCount() - 1) { - m_errorList += " "; - } +string Tool_pccount::getPitchClassString(int b40) { + switch (b40%40) { + case 0: return "C♭♭"; + case 1: return "C♭"; + case 2: return "C"; + case 3: return "C♯"; + case 4: return "C♯♯"; + // 5 is empty + case 6: return "D♭♭"; + case 7: return "D♭"; + case 8: return "D"; + case 9: return "D♯"; + case 10: return "D♯♯"; + // 11 is empty + case 12: return "E♭♭"; + case 13: return "E♭"; + case 14: return "E"; + case 15: return "E♯"; + case 16: return "E♯♯"; + case 17: return "F♭♭"; + case 18: return "F♭"; + case 19: return "F"; + case 20: return "F♯"; + case 21: return "F♯♯"; + // 22 is empty + case 23: return "G♭♭"; + case 24: return "G♭"; + case 25: return "G"; + case 26: return "G♯"; + case 27: return "G♯♯"; + // 28 is empty + case 29: return "A♭♭"; + case 30: return "A♭"; + case 31: return "A"; + case 32: return "A♯"; + case 33: return "A♯♯"; + // 34 is empty + case 35: return "B♭♭"; + case 36: return "B♭"; + case 37: return "B"; + case 38: return "B♯"; + case 39: return "B♯♯"; } - m_errorList += "\n"; - m_errorHtml += "!!
  • @{TOOL-nproof-error-" + to_string(m_errorCount) + "}
  • \n"; + + return "?"; } + ///////////////////////////////// // -// Tool_ordergps::Tool_ordergps -- Set the recognized options for the tool. +// Tool_periodicity::Tool_periodicity -- Set the recognized options for the tool. // -Tool_ordergps::Tool_ordergps(void) { - define("e|empty=b", "list files that have no group/part/staff (used with -p option)."); - define("f|file=b", "list input files only."); - define("l|list=b", "list files that will be changed."); - define("p|problem=b", "list files that have mixed content for *group, *part, *staff info."); - define("r|reverse=b", "order *staff, *part, *group"); - define("s|staff=b", "Add staff line if none present already in score."); - define("t|top=b", "Place group/part/staff lines first after exinterp."); +Tool_periodicity::Tool_periodicity(void) { + define("m|min=b", "minimum time unit (other than grace notes)"); + define("n|max-rows=i:-1", "maxumum number of rows in svg analysis display"); + define("t|track=i:0", "track to analyze"); + define("attacks=b", "extract attack grid)"); + define("raw=b", "show only raw period data"); + define("s|svg=b", "output svg image"); + define("p|power=d:2.0", "scaling power for visual display"); + define("1|one=b", "composite rhythms are not weighted by attack"); } ///////////////////////////////// // -// Tool_ordergps::run -- Do the main work of the tool. +// Tool_periodicity::run -- Primary interfaces to the tool. // -bool Tool_ordergps::run(HumdrumFileSet& infiles) { +bool Tool_periodicity::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i> attackgrids; + attackgrids.resize(infile.getTrackCount()+1); + fillAttackGrids(infile, attackgrids, minrhy); + if (getBoolean("attacks")) { + printAttackGrid(m_free_text, infile, attackgrids, minrhy); + return; + } + + int atrack = getInteger("track"); + vector> analysis; + doPeriodicityAnalysis(analysis, attackgrids[atrack], minrhy); + + if (getBoolean("raw")) { + printPeriodicityAnalysis(m_free_text, analysis); + return; + } + + printSvgAnalysis(m_free_text, analysis, minrhy); } ////////////////////////////// // -// Tool_ordergps::processFile -- Analyze an input file. +// Tool_periodicity::printPeriodicityAnalysis -- // -void Tool_ordergps::processFile(HumdrumFile& infile) { - vector groupIndex; - vector partIndex; - vector staffIndex; - bool foundProblem = false; - - for (int i=0; i>& analysis) { + for (int i=0; i<(int)analysis.size(); i++) { + for (int j=0; j<(int)analysis[i].size(); j++) { + out << analysis[i][j]; + if (j < (int)analysis[i].size() - 1) { + out << "\t"; } } - if (infile[i].isCommentLocal()) { - // Don't process after local comments. The file header - // is too complex perhaps, so do not alter anything - // after the local comment. This can be related to modori - // assignment for groups, for example. - break; + out << "\n"; + } +} + + + +////////////////////////////// +// +// Tool_periodicity::doPeriodicAnalysis -- +// + +void Tool_periodicity::doPeriodicityAnalysis(vector> &analysis, vector& grid, HumNum minrhy) { + analysis.resize(minrhy.getNumerator()); + for (int i=0; i<(int)analysis.size(); i++) { + doAnalysis(analysis, i, grid); + } +} + + + +////////////////////////////// +// +// Tool_periodicity::doAnalysis -- +// + +void Tool_periodicity::doAnalysis(vector>& analysis, int level, vector& grid) { + int period = level + 1; + analysis[level].resize(period); + std::fill(analysis[level].begin(), analysis[level].end(), 0.0); + for (int i=0; i>& grids, HumNum minrhy) { + out << "!!!minrhy: " << minrhy << endl; + out << "**all"; + for (int i=1; i<(int)grids.size(); i++) { + out << "\t**track"; + } + out << "\n"; + for (int j=0; j<(int)grids[0].size(); j++) { + for (int i=0; i<(int)grids.size(); i++) { + out << grids[i][j]; + if (i < (int)grids.size() - 1) { + out << "\t"; + } } - if (infile[i].isExclusiveInterpretation()) { - continue; + out << "\n"; + } + for (int i=0; i<(int)grids.size(); i++) { + out << "*-"; + if (i < (int)grids.size() - 1) { + out << "\t"; } - if (!infile[i].isInterpretation()) { + } + out << "\n"; + +} + + + +////////////////////////////// +// +// Tool_periodicity::fillAttackGrids -- +// + +void Tool_periodicity::fillAttackGrids(HumdrumFile& infile, vector>& grids, HumNum minrhy) { + HumNum elements = minrhy * infile.getScoreDuration() / 4; + + for (int t=0; t<(int)grids.size(); t++) { + grids[t].resize(elements.getNumerator()); + } + + for (int i=0; iisKern()) { continue; } - if (token->compare(0, 6, "*group") == 0) { - hasGroup = true; - } else if (token->compare(0, 5, "*part") == 0) { - hasPart = true; - } else if (token->compare(0, 6, "*staff") == 0) { - hasStaff = true; - } else { - hasOther = true; + if (token->isNull()) { + continue; + } + if (!token->isNoteAttack()) { + continue; } + int track = token->getTrack(); + grids.at(track).at(position.getNumerator()) += 1; } + } - if (hasOther && hasGroup) { - foundProblem = true; - if (m_problemQ) { - cerr << infile.getFilename() << " HAS MIXED GROUP LINE:" << endl; - cerr << "\t" << infile[i] << endl; + bool oneQ = getBoolean("one"); + for (int j=0; j<(int)grids.at(0).size(); j++) { + grids.at(0).at(j) = 0; + for (int i=0; i<(int)grids.size(); i++) { + if (!grids.at(i).at(j)) { + continue; + } + if (oneQ) { + grids.at(0).at(j) = 1; + } else { + grids.at(0).at(j) += grids.at(i).at(j); } } + } +} - if (hasOther && hasPart) { - foundProblem = true; - if (m_problemQ) { - cerr << infile.getFilename() << " HAS MIXED PART LINE:" << endl; - cerr << "\t" << infile[i] << endl; + + +////////////////////////////// +// +// Tool_periodicity::printSvgAnalysis -- +// + +void Tool_periodicity::printSvgAnalysis(ostream& out, vector>& analysis, HumNum minrhy) { + pugi::xml_document image; + auto declaration = image.prepend_child(pugi::node_declaration); + declaration.append_attribute("version") = "1.0"; + declaration.append_attribute("encoding") = "UTF-8"; + declaration.append_attribute("standalone") = "no"; + + auto svgnode = image.append_child("svg"); + svgnode.append_attribute("version") = "1.1"; + svgnode.append_attribute("xmlns") = "http://www.w3.org/2000/svg"; + svgnode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink"; + svgnode.append_attribute("overflow") = "visible"; + svgnode.append_attribute("viewBox") = "0 0 1000 1000"; + svgnode.append_attribute("width") = "1000px"; + svgnode.append_attribute("height") = "1000px"; + + auto style = svgnode.append_child("style"); + style.text().set(".label { font: 14px sans-serif; alignment-baseline: middle; text-anchor: left; }"); + + auto grid = svgnode.append_child("g"); + grid.append_attribute("id") = "grid"; + + auto labels = svgnode.append_child("g"); + + double hue = 0.0; + double saturation = 100; + double lightness = 75; + + pugi::xml_node crect; + double width; + double height; + + stringstream ss; + stringstream ssl; + //stringstream css; + double x; + double y; + + double imagewidth = 1000.0; + double imageheight = 1000.0; + + int maxrow = getInteger("max-rows"); + if (maxrow <= 0) { + maxrow = (int)analysis.back().size(); + } + + + // double sdur = (double)analysis.back().size(); + double sdur = (double)maxrow; + + double maxscore = 0.0; + for (int i=0; i maxhue) { + hue = maxhue; + } + if (hue < 0.0) { + hue = maxhue; + } + + saturation = 100.0; + lightness = 50.0; + + if (hue > 60) { + lightness = lightness - (hue-60) / (maxhue-60) * lightness / 1.5; + } +} + + + + +///////////////////////////////// +// +// Tool_gridtest::Tool_phrase -- Set the recognized options for the tool. +// + +Tool_phrase::Tool_phrase(void) { + define("A|no-average=b", "do not do average phrase-length analysis"); + define("R|remove2=b", "remove phrase boundaries in data and do not do analysis"); + define("m|mark=b", "mark phrase boundaries based on rests"); + define("r|remove=b", "remove phrase boundaries in data"); + define("c|color=s", "display color of analysis data"); +} + + + +/////////////////////////////// +// +// Tool_phrase::run -- Primary interfaces to the tool. +// + +bool Tool_phrase::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i0; i--) { + int track = m_starts[i]->getTrack(); + infile.insertDataSpineBefore(track, m_results[i-1], "", exinterp); + } + if (m_averageQ) { + addAverageLines(infile); + } + if (!m_color.empty()) { + int insertline = -1; + for (int i=0; i 0) { + stringstream ss; + int fsize = infile[insertline].getFieldCount(); + for (int j=0; jgetDataType(); + if (dt.empty() || (dt == "**cdata")) { + ss << "color:" << m_color; + } + if (j < fsize - 1) { + ss << "\t"; + } } + string output = ss.str(); + infile.insertLine(insertline, output); } + } +} - if (hasGroup) { - groupIndex.push_back(i); - } - if (hasPart) { - partIndex.push_back(i); - } - if (hasStaff) { - staffIndex.push_back(i); - } - } +/////////////////////////////// +// +// Tool_pharse::addAverageLines -- +// - if (groupIndex.size() > 1) { - foundProblem = true; - if (m_problemQ) { - cerr << infile.getFilename() << " HAS MORE THAN ONE GROUP LINE:" << endl; - for (int i=0; i<(int)groupIndex.size(); i++) { - cerr << "\t" << infile[groupIndex[i]] << endl; - } +void Tool_phrase::addAverageLines(HumdrumFile& infile) { + vector averages; + averages.resize(m_starts.size()+1); + int tcount = 0; + HumNum tsum = 0; + double average; + stringstream ss; + for (int i=0; i<(int)m_starts.size(); i++) { + if (m_pcount[i] > 0) { + average = m_psum[i].getFloat() / m_pcount[i]; + } else { + average = 0.0; } + ss.str(""); + ss.clear(); + ss << "!!average-phrase-length-k" << i+1 << ":\t" << average; + averages[i+1] = ss.str(); + tcount += m_pcount[i]; + tsum += m_psum[i]; } + average = tsum.getFloat() / tcount; + ss.str(""); + ss.clear(); + ss << "!!average-phrase-length:\t" << average; + averages[0] = ss.str(); - if (partIndex.size() > 1) { - foundProblem = true; - if (m_problemQ) { - cerr << infile.getFilename() << " HAS MORE THAN ONE PART LINE:" << endl; - for (int i=0; i<(int)partIndex.size(); i++) { - cerr << "\t" << infile[partIndex[i]] << endl; - } - } + for (int i=0; i<(int)averages.size(); i++) { + infile.appendLine(averages[i]); } +} - if (staffIndex.size() > 1) { - foundProblem = true; - if (m_problemQ) { - cerr << infile.getFilename() << " HAS MORE THAN ONE STAFF LINE:" << endl; - for (int i=0; i<(int)staffIndex.size(); i++) { - cerr << "\t" << infile[staffIndex[i]] << endl; - } - } - } - if (m_problemQ) { - if (m_emptyQ) { - if (groupIndex.empty() && partIndex.empty() && staffIndex.empty()) { - cerr << infile.getFilename() << " HAS NO GROUP/PART/STAFF INFO" << endl; - } - } - } else { - if (foundProblem) { - // Don try to fix anything, just echo the input: - m_humdrum_text << infile; - } else { - if (m_staffQ && groupIndex.empty() && partIndex.empty() && staffIndex.empty()) { - printStaffLine(infile); - } else { - // Process further here - // Check the order of the group/part/staff lines. - int gindex = groupIndex.empty() ? -1 : groupIndex.at(0); - int pindex = partIndex.empty() ? -1 : partIndex.at(0); - int sindex = staffIndex.empty() ? -1 : staffIndex.at(0); - if (m_topQ) { - printFileTop(infile, gindex, pindex, sindex); - } else { - printFile(infile, gindex, pindex, sindex); - } - } - } + +/////////////////////////////// +// +// Tool_phrase::initialize -- +// + +void Tool_phrase::initialize(HumdrumFile& infile) { + m_starts = infile.getKernSpineStartList(); + m_results.resize(m_starts.size()); + int lines = infile.getLineCount(); + for (int i=0; i<(int)m_results.size(); i++) { + m_results[i].resize(lines); + } + m_pcount.resize(m_starts.size()); + m_psum.resize(m_starts.size()); + std::fill(m_pcount.begin(), m_pcount.end(), 0); + std::fill(m_psum.begin(), m_psum.end(), 0); + m_markQ = getBoolean("mark"); + m_removeQ = getBoolean("remove"); + m_averageQ = !getBoolean("no-average"); + m_remove2Q = getBoolean("remove2"); + if (getBoolean("color")) { + m_color = getString("color"); } } -////////////////////////////// +/////////////////////////////// // -// Tool_ordergps::printFileTop -- Print group/part/staff first after exclusive -// interpretations. +// Tool_phrase::analyzeSpineByRests -- // -void Tool_ordergps::printFileTop(HumdrumFile& infile, int gindex, int pindex, int sindex) { - for (int i=0; iisBarline()) { + if (current->find("||") != std::string::npos) { + if (pstart) { + dur = current->getDurationFromStart() + - pstart->getDurationFromStart(); + ss.str(""); + ss.clear(); + ss << dur.getFloat(); + m_psum[index] += dur; + m_pcount[index]++; + m_results[index][pstart->getLineIndex()] = ss.str(); + pstart = NULL; + if (m_markQ && lastnote) { + lastnote->setText(lastnote->getText() + "}"); + } + } + } + } + if (!current->isData()) { + current = current->getNextToken(); continue; - } else if (i == sindex) { + } + if (current->isNull()) { + current = current->getNextToken(); continue; - } else if (infile[i].isExclusiveInterpretation()) { - m_humdrum_text << infile[i] << endl; - if (m_reverseQ) { - if (sindex >= 0) { - m_humdrum_text << infile[sindex] << endl; - } - if (pindex >= 0) { - m_humdrum_text << infile[pindex] << endl; - } - if (gindex >= 0) { - m_humdrum_text << infile[gindex] << endl; - } - } else { - if (gindex >= 0) { - m_humdrum_text << infile[gindex] << endl; - } - if (pindex >= 0) { - m_humdrum_text << infile[pindex] << endl; - } - if (sindex >= 0) { - m_humdrum_text << infile[sindex] << endl; + } + if (pstart && current->isRest()) { + if (lastnote) { + dur = current->getDurationFromStart() + - pstart->getDurationFromStart(); + ss.str(""); + ss.clear(); + ss << dur.getFloat(); + m_psum[index] += dur; + m_pcount[index]++; + m_results[index][pstart->getLineIndex()] = ss.str(); + if (m_markQ) { + lastnote->setText(lastnote->getText() + "}"); } } - } else { - m_humdrum_text << infile[i] << endl; + pstart = NULL; + lastnote = NULL; + current = current->getNextToken(); + continue; + } + if (current->isRest()) { + current = current->getNextToken(); + continue; + } + if (current->isNote()) { + lastnote = current; + } + if (pstart && current->isNote() && (current->find(";") != std::string::npos)) { + // fermata at end of phrase. + dur = current->getDurationFromStart() + current->getDuration() + - pstart->getDurationFromStart(); + ss.str(""); + ss.clear(); + ss << dur.getFloat(); + m_psum[index] += dur; + m_pcount[index]++; + m_results[index][pstart->getLineIndex()] = ss.str(); + if (m_markQ) { + current->setText(current->getText() + "}"); + } + current = current->getNextToken(); + pstart = NULL; + continue; + } + if (current->isNote() && pstart == NULL) { + pstart = current; + if (m_markQ) { + current->setText("{" + current->getText()); + } + } + current = current->getNextToken(); + } + if (pstart) { + dur = start->getOwner()->getOwner()->getScoreDuration() + - pstart->getDurationFromStart(); + ss.str(""); + ss.clear(); + ss << dur.getFloat(); + m_psum[index] += dur; + m_pcount[index]++; + m_results[index][pstart->getLineIndex()] = ss.str(); + if (m_markQ && lastnote) { + lastnote->setText(lastnote->getText() + "}"); } } } -////////////////////////////// +/////////////////////////////// // -// Tool_ordergps::printFile -- Check to see if the group/part/staff -// lines need to be adjusted, and the print the file. Lines -// will be ordered group/part/staff, placing the lines where -// the first of group/part/staff is found. +// Tool_phrase::analyzeSpineByPhrase -- // -void Tool_ordergps::printFile(HumdrumFile& infile, int gindex, int pindex, int sindex) { - int startIndex = gindex; - if (pindex >= 0) { - if (startIndex < 0) { - startIndex = pindex; - } else if (pindex < startIndex) { - startIndex = pindex; - } - } - if (sindex >= 0) { - if (startIndex < 0) { - startIndex = sindex; - } else if (sindex < startIndex) { - startIndex = sindex; +void Tool_phrase::analyzeSpineByPhrase(int index) { + HTp start = m_starts[index]; + HTp current = start; + HTp pstart = NULL; // phrase start; + HumNum dur; + stringstream ss; + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - } - if (startIndex < 0) { - // no group/part/staff lines in file, so just print it: - m_humdrum_text << infile; - return; - } - - for (int i=0; i= 0) { - m_humdrum_text << infile[sindex] << endl; - } - if (pindex >= 0) { - m_humdrum_text << infile[pindex] << endl; - } - if (gindex >= 0) { - m_humdrum_text << infile[gindex] << endl; - } - } else { - if (gindex >= 0) { - m_humdrum_text << infile[gindex] << endl; - } - if (pindex >= 0) { - m_humdrum_text << infile[pindex] << endl; - } - if (sindex >= 0) { - m_humdrum_text << infile[sindex] << endl; - } - } - } else if (i == gindex) { + if (current->isNull()) { + current = current->getNextToken(); continue; - } else if (i == pindex) { + } + if (current->find("{") != std::string::npos) { + pstart = current; + current = current->getNextToken(); continue; - } else if (i == sindex) { + } + if (current->find("}") != std::string::npos) { + if (pstart) { + dur = current->getDurationFromStart() + current->getDuration() + - pstart->getDurationFromStart(); + ss.str(""); + ss.clear(); + ss << dur.getFloat(); + m_psum[index] += dur; + m_pcount[index]++; + m_results[index][pstart->getLineIndex()] = ss.str(); + } + current = current->getNextToken(); continue; - } else { - m_humdrum_text << infile[i] << endl; } + current = current->getNextToken(); } } @@ -112848,69 +117402,80 @@ void Tool_ordergps::printFile(HumdrumFile& infile, int gindex, int pindex, int s ////////////////////////////// // -// Tool_ordergps::printStaffLine -- Add a *staff at the start of the -// data since none was detected. Does not label staff-like spines -// other than **kern (such as **kernyy, **kern-mod, **mens). +// Tool_phrase::removePhraseMarks -- Remvoe { and } characters from **kern data. +// -void Tool_ordergps::printStaffLine(HumdrumFile& infile) { - for (int i=0; iisData()) { + current = current->getNextToken(); continue; } - m_humdrum_text << infile[i] << endl; - vector staffLine(infile[i].getFieldCount(), "*"); - int counter = 0; - for (int j=infile[i].getFieldCount() - 1; j>=0; j--) { - HTp token = infile.token(i, j); - if (token->isKern()) { - counter++; - string text = "*staff" + to_string(counter); - staffLine.at(j) = text; - } + if (current->isNull()) { + current = current->getNextToken(); + continue; } - for (int j=0; j<(int)staffLine.size(); j++) { - m_humdrum_text << staffLine[j]; - if (j < (int)staffLine.size() - 1) { - m_humdrum_text << '\t'; - } + if (current->find("{") != std::string::npos) { + string data = *current; + hre.replaceDestructive(data, "", "\\{", "g"); + current->setText(data); } - m_humdrum_text << endl; + if (current->find("}") != std::string::npos) { + string data = *current; + hre.replaceDestructive(data, "", "\\}", "g"); + current->setText(data); + } + current = current->getNextToken(); } } - -///////////////////////////////// +////////////////////////////// // -// Tool_pbar::Tool_pbar -- Set the recognized options for the tool. +// Tool_phrase::hasPhraseMarks -- True if **kern data spine (primary layer), has +// "{" (or "}", but this is not checked) characters (phrase markers). // -Tool_pbar::Tool_pbar(void) { - define("i|invisible-barlines=b", "make barlines invisible"); +bool Tool_phrase::hasPhraseMarks(HTp start) { + HTp current = start; + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if (current->find("{") != std::string::npos) { + return true; + } + current = current->getNextToken(); + } + return false; } -////////////////////////////// + + +///////////////////////////////// // -// Tool_pbar::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_pline::Tool_pline -- Set the recognized options for the tool. // -void Tool_pbar::initialize(void) { - m_invisibleQ = getBoolean("invisible-barlines"); +Tool_pline::Tool_pline(void) { + define("c|color=b", "color poetic lines (currently only by notes)"); + define("o|overlap=b", "do overlap analysis/markup"); } ///////////////////////////////// // -// Tool_pbar::run -- Do the main work of the tool. +// Tool_pline::run -- Do the main work of the tool. // -bool Tool_pbar::run(HumdrumFileSet& infiles) { +bool Tool_pline::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i kstarts = infile.getKernSpineStartList(); - for (int i=0; i<(int)kstarts.size(); i++) { - processSpine(kstarts[i]); - } +void Tool_pline::initialize(void) { + m_colors.resize(14); - for (int i=0; i spinestops; + infile.getSpineStopList(spinestops); + for (int i=0; i<(int)spinestops.size(); i++) { + if (!spinestops[i]->isKern()) { + continue; + } + markSpineRests(spinestops[i]); + } +} -/////////////////////////////// +////////////////////////////// // -// Tool_pbar::printBarLine -- Add *bar line. +// Tool_pline::markSpineRests -- // -void Tool_pbar::printBarLine(HumdrumFile& infile, int index) { - bool hasBarline = false; - for (int j=0; jgetValue("auto", "pbar"); - if (value == "true") { - hasBarline = true; - break; +void Tool_pline::markSpineRests(HTp spineStop) { + string marker = "😀"; + int track = spineStop->getTrack(); + int lastValue = -1; + HTp current = spineStop->getPreviousToken(); + int line; + int cvalue; + while (current) { + if (!current->isData()) { + current = current->getPreviousToken(); + continue; + } + if (current->isNull()) { + current = current->getPreviousToken(); + continue; } - } - if (hasBarline) { - for (int j=0; jgetValue("auto", "pbar"); - if (value == "true") { - m_humdrum_text << "*bar"; - } else { - m_humdrum_text << "*"; - } - if (j < infile[index].getFieldCount() - 1) { - m_humdrum_text << "\t"; - } + line = current->getLineIndex(); + cvalue = m_lineInfo.at(line).at(track); + + if (current->isRest() && (cvalue != lastValue)) { + string text = *current; + text += marker; + current->setText(text); + } else { + lastValue = cvalue; + string text = *current; + text += "@" + to_string(cvalue); + current->setText(text); } - m_humdrum_text << "\n"; + current = current->getPreviousToken(); } } -/////////////////////////////// +////////////////////////////// // -// Tool_pbar::printLocalCommentLine -- +// Tool_pline::fillLineInfo -- // -void Tool_pbar::printLocalCommentLine(HumdrumFile& infile, int index) { +void Tool_pline::fillLineInfo(HumdrumFile& infile, vector>& lineinfo) { + lineinfo.clear(); + lineinfo.resize(infile.getLineCount()); + int maxtrack = infile.getMaxTrack(); HumRegex hre; - bool hasKp = false; - bool hasOther = false; - for (int i=0; igetTrack(); + lineinfo[i][track] = digit; + } } } - if (!hasKp) { - m_humdrum_text << infile[index] << endl; - return; - } - - if (hasOther) { - for (int i=0; i& tokens) { HumRegex hre; - while (current) { - if (!current->isLocalComment()) { - current = current->getNextToken(); + markRests(infile); + for (int i=0; i<(int)tokens.size(); i++) { + if (!hre.search(tokens[i], "^\\*pline:\\s*(\\d+)")) { continue; } - if (hre.search(current, "kreska\\s*pseudotaktowa")) { - addBarLineToFollowingNoteOrRest(current); - } - current = current->getNextToken(); + int lineNum = hre.getMatchInt(1); + int colorIndex = (lineNum - 1) % m_colors.size(); + string color = m_colors.at(colorIndex); + string text = "*color:"; + text += color; + tokens[i]->setText(text); } } @@ -113125,62 +117703,54 @@ void Tool_pbar::processSpine(HTp spineStart) { ////////////////////////////// // -// Tool_pbar::addBarLineToFollowingNoteOrRest -- +// Tool_pline::getPlineInterpretations -- // -void Tool_pbar::addBarLineToFollowingNoteOrRest(HTp token) { - HTp current = token->getNextToken(); - int counter = 0; - while (current) { - if (!current->isBarline()) { - if (!current->isData() || current->isNull()) { - current = current->getNextToken(); +void Tool_pline::getPlineInterpretations(HumdrumFile& infile, vector& tokens) { + HumRegex hre; + for (int i=0; iisKern()) { continue; } + if (hre.search(token, "^\\*pline:\\s*(\\d+)")) { + tokens.push_back(token); + } } - counter++; - if (counter == 2) { - current->setValue("auto", "pbar", "true"); - break; - } - current = current->getNextToken(); } } + ///////////////////////////////// // -// Tool_gridtest::Tool_pccount -- Set the recognized options for the tool. +// Tool_gridtest::Tool_pnum -- Set the recognized options for the tool. // -Tool_pccount::Tool_pccount(void) { - define("a|attacks=b", "count attacks instead of durations"); - define("d|data|vega-data=b", "display the vega-lite template."); - define("f|full=b", "full count attacks all single sharps and flats."); - define("ff|double-full=b", "full count attacks all double sharps and flats."); - define("h|html=b", "generate vega-lite HTML content"); - define("i|id=s:id", "ID for use as variable and in plot title"); - define("K|no-key|no-final=b", "do not label key tonic or final"); - define("m|maximum=b", "normalize by maximum count"); - define("n|normalize=b", "normalize counts"); - define("p|page=b", "generate vega-lite stand-alone HTML page"); - define("r|ratio|aspect-ratio=d:0.67", "width*ratio=height of vega-lite plot"); - define("s|script|vega-script=b", "generate vega-lite javascript content"); - define("title=s", "title for plot"); - define("t|template|vega-template=b", "display the vega-lite template."); - define("w|width=i:400", "width of vega-lite plot"); +Tool_pnum::Tool_pnum(void) { + define("b|base=i:midi", "numeric base of pitch to extract"); + define("D|no-duration=b", "do not include duration"); + define("c|pitch-class=b", "give numeric pitch-class rather than pitch"); + define("o|octave=b", "give octave rather than pitch"); + define("r|rest=s:0", "representation string for rests"); + define("R|no-rests=b", "do not include rests in conversion"); + define("x|attacks-only=b", "only mark lines with note attacks"); } /////////////////////////////// // -// Tool_pccount::run -- Primary interfaces to the tool. +// Tool_pnum::run -- Primary interfaces to the tool. // -bool Tool_pccount::run(HumdrumFileSet& infiles) { +bool Tool_pnum::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i kex; - if (m_attack) { - datavar = "data_" + m_id + "_count"; - target = "id_" + m_id + "_count"; - jsonvar = "vega_" + m_id + "_count"; - } else { - datavar = "data_" + m_id + "_dur"; - target = "id_" + m_id + "_dur"; - jsonvar = "vega_" + m_id + "_dur"; + for (int i=0; iisKern()) { + continue; + } + if (*token == "**kern") { + kex.push_back(token); + continue; + } + if (!token->isData()) { + continue; + } + if (token->isNull()) { + continue; + } + convertTokenToBase(token); + } } - if (m_template) { - printVegaLiteJsonTemplate(datavar, infile); - } else if (m_data) { - printVegaLiteJsonData(); - } else if (m_script) { - printVegaLiteScript(jsonvar, target, datavar, infile); - } else if (m_html) { - printVegaLiteHtml(jsonvar, target, datavar, infile); - } else if (m_page) { - printVegaLitePage(jsonvar, target, datavar, infile); - } else { - printHumdrumTable(); + string newex; + for (int i=0; i<(int)kex.size(); i++) { + if (m_midiQ) { + newex = "**pmid"; + } else { + newex = "**b" + to_string(m_base); + } + kex[i]->setText(newex); } } @@ -113373,561 +117852,553 @@ void Tool_pccount::processFile(HumdrumFile& infile) { ////////////////////////////// // -// Tool_pccount::printVegaLitePage -- -// - -void Tool_pccount::printVegaLitePage(const string& jsonvar, - const string& target, const string& datavar, HumdrumFile& infile) { - stringstream& out = m_free_text; - - out << "\n"; - out << "\n"; - out << " \n"; - out << " Vega-Lite Bar Chart\n"; - out << " \n"; - out << "\n"; - out << " \n"; - out << " \n"; - out << " \n"; - out << "\n"; - out << " \n"; - out << " \n"; - out << " \n"; - out << "

    Pitch-class histogram

    \n"; - printVegaLiteHtml(jsonvar, target, datavar, infile); - out << "\n"; - out << "\n"; -} - - - -////////////////////////////// -// -// Tool_pccount::printVegaLiteHtml -- +// Tool_pnum::convertTokenToBase -- // -void Tool_pccount::printVegaLiteHtml(const string& jsonvar, - const string& target, const string& datavar, HumdrumFile& infile) { - stringstream& out = m_free_text; - - out << "
    \n"; - out << "\n"; - out << "\n"; +void Tool_pnum::convertTokenToBase(HTp token) { + string output; + int scount = token->getSubtokenCount(); + for (int i=0; igetSubtoken(i); + output += convertSubtokenToBase(subtok); + if (i < scount - 1) { + output += " "; + } + } + token->setText(output); } ////////////////////////////// // -// Tool_pccount::printVegaLiteScript -- +// Tool_pnum::convertSubtokenToBase -- // -void Tool_pccount::printVegaLiteScript(const string& jsonvar, - const string& target, const string& datavar, HumdrumFile& infile) { - stringstream& out = m_free_text; - - out << "var " << datavar << " =\n"; - printVegaLiteJsonData(); - out << ";\n"; - out << "\n"; - out << "var " << jsonvar << " =\n"; - printVegaLiteJsonTemplate(datavar, infile); - out << ";\n"; - out << "vegaEmbed('#" << target << "', " << jsonvar << ");\n"; -} +string Tool_pnum::convertSubtokenToBase(const string& text) { + int pitch = 0; + if (text.find("r") == string::npos) { + switch (m_base) { + case 7: + pitch = Convert::kernToBase7(text); + break; + case 40: + pitch = Convert::kernToBase40(text); + break; + default: + pitch = Convert::kernToBase12(text); + } + } else if (!m_restQ) { + return "."; + } + string recip; + if (m_durationQ) { + HumRegex hre; + if (hre.search(text, "(\\d+%?\\d*\\.*)")) { + recip = hre.getMatch(1); + } + } + string output; + int pc = pitch % m_base; + int oct = pitch / m_base; -////////////////////////////// -// -// Tool_pccount::printVegaLiteJsonData -- -// + if (m_midiQ) { + // MIDI numbers use 5 for middle-C octave. + pitch += 12; + } -void Tool_pccount::printVegaLiteJsonData(void) { - stringstream& out = m_free_text; + int tie = 1; + if (text.find("_") != string::npos) { + tie = -1; + } + if (text.find("]") != string::npos) { + tie = -1; + } + pitch *= tie; + if (m_attacksQ && pitch < 0) { + return "."; + } - m_maxpc = 0; - for (int i=0; i<(int)m_counts[0].size(); i++) { - if (m_counts[0][i] > m_maxpc) { - m_maxpc = m_counts[0][i]; - } + if (m_durationQ) { + output += recip; + output += "/"; } - out << "[\n"; - int commacounter = 0; - double percent = 100.0; - for (int i=1; i<(int)m_counts.size(); i++) { - for (int j=0; j<(int)m_counts[i].size(); j++) { - if (m_counts[i][j] == 0.0) { - continue; + + if (text.find("r") != string::npos) { + output += m_rest; + } else { + if (!m_octaveQ && !m_classQ) { + output += to_string(pitch); + } else { + if (m_classQ) { + if (pitch < 0) { + output += "-"; + } + output += to_string(pc); } - if (commacounter > 0) { - out << ",\n\t"; - } else { - out << "\t"; + if (m_classQ && m_octaveQ) { + output += ":"; } - commacounter++; - if (m_attack) { - out << "{\"count\":" << m_counts[i][j] << ", "; - } else { - out << "{\"percent\":" << m_counts[i][j]/m_maxpc*percent << ", "; + if (m_octaveQ) { + output += to_string(oct); } - out << "\"pitch class\":\"" << getPitchClassString(j) << "\", "; - out << "\"voice\":\"" << m_names[i] << "\""; - out << "}"; } } - out << "\n]\n"; + + return output; } -////////////////////////////// -// -// Tool_pccount::setFactorMaximum -- normalize by the maximum pitch-class value. -// -void Tool_pccount::setFactorMaximum(void) { - m_factor = 0.0; - for (int i=0; i<(int)m_counts[0].size(); i++) { - if (m_counts[0][i] > m_factor) { - m_factor = m_counts[0][i]; - } - } -} +#define OBJTAB "\t\t\t\t\t\t" +#define SVGTAG "_99%svg%"; + +#define SVGTEXT(out, text) \ + if (m_defineQ) { \ + out << "SVG "; \ + } else { \ + out << "t 1 1\n"; \ + out << SVGTAG; \ + } \ + printScoreEncodedText((out), (text)); \ + out << "\n"; ////////////////////////////// // -// Tool_pccount::setFactorNormalize -- normalize by the sum of all pitch class values. +// _VoiceInfo::_VoiceInfo -- // -void Tool_pccount::setFactorNormalize(void) { - m_factor = 0.0; - for (int i=0; i<(int)m_counts[0].size(); i++) { - m_factor += m_counts[0][i]; - } +_VoiceInfo::_VoiceInfo(void) { + clear(); } ////////////////////////////// // -// Tool_pccount::printHumdrumTable -- +// _VoiceInfo::clear -- // -void Tool_pccount::printHumdrumTable(void) { - - double factor = 0.0; - - if (m_maximum) { - setFactorMaximum(); - m_free_text << "!!!MAX: " << m_factor << endl; - } else if (m_normalize) { - setFactorNormalize(); - m_free_text << "!!!TOTAL: " << factor << endl; - } - - // exclusive interpretation - m_free_text << "**kern"; - m_free_text << "\t**all"; - for (int i=0; i<(int)m_counts.size() - 1; i++) { - m_free_text << "\t**part"; +void _VoiceInfo::clear(void) { + name = ""; + abbr = ""; + midibins.resize(128); + fill(midibins.begin(), midibins.end(), 0.0); + diatonic.resize(7 * 12); + for (int i=0; i<(int)diatonic.size(); i++) { + diatonic[i].resize(6); + fill(diatonic[i].begin(), diatonic[i].end(), 0.0); } - m_free_text << endl; + track = -1; + kernQ = false; + diafinal.clear(); + accfinal.clear(); + namfinal.clear(); + index = -1; +} - // part names - m_free_text << "*"; - for (int i=0; i<(int)m_counts.size(); i++) { - if (i < (int)m_names.size()) { - m_free_text << "\t*I\"" << m_names.at(i); - } else { - m_free_text << "\t*"; - } - } - m_free_text << endl; - if (!m_abbreviations.empty()) { +////////////////////////////// +// +// _VoiceInfo::print -- +// - // part abbreviation - m_free_text << "*"; - for (int i=0; i<(int)m_counts.size(); i++) { - if (i < (int)m_abbreviations.size()) { - m_free_text << "\t*I\'" << m_abbreviations.at(i); - } else { - m_free_text << "\t*"; - } - } - m_free_text << endl; +ostream& _VoiceInfo::print(ostream& out) { + out << "==================================" << endl; + out << "track: " << track << endl; + out << " name: " << name << endl; + out << " abbr: " << abbr << endl; + out << " kern: " << kernQ << endl; + out << " final:"; + for (int i=0; i<(int)diafinal.size(); i++) { + out << " " << diafinal.at(i) << "/" << accfinal.at(i); } - - for (int i=0; i<(int)m_counts[0].size(); i++) { - if (m_counts[0][i] == 0) { - continue; - } - if ((i == 5) || (i == 11) || (i == 22) || (i == 28) || (i == 34)) { - continue; - } - string pitch = Convert::base40ToKern(i + 4*40); - m_free_text << pitch; - for (int j=0; j<(int)m_counts.size(); j++) { - if (m_normalize) { - m_free_text << "\t" << m_counts[j][i] / m_factor; - } else if (m_maximum) { - m_free_text << "\t" << m_counts[j][i] / m_factor; - } else { - m_free_text << "\t" << m_counts[j][i]; - } + out << endl; + out << " midi: "; + for (int i=0; i<(int)midibins.size(); i++) { + if (midibins.at(i) > 0.0) { + out << " " << i << ":" << midibins.at(i); } - m_free_text << endl; } - - int columns = (int)m_counts.size() + 1; - for (int i=0; i 0.0) { + out << " " << i << ":" << diatonic.at(i).at(0); } } - m_free_text << endl; + out << endl; + out << "==================================" << endl; + return out; } -////////////////////////////// +///////////////////////////////// // -// Tool_pccount::countPitches -- +// Tool_prange::Tool_prange -- Set the recognized options for the tool. // -void Tool_pccount::countPitches(HumdrumFile& infile) { - if (m_parttracks.size() == 0) { - return; - } - m_counts.clear(); - m_counts.resize(m_parttracks.size()); - for (int i=0; i<(int)m_parttracks.size(); i++) { - m_counts[i].resize(40); - fill(m_counts[i].begin(), m_counts[i].end(), 0.0); - } - for (int i=0; iisKern()) { - return; + return status; +} + + +bool Tool_prange::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } - int track = sstart->getTrack(); - int kindex = m_rkern[track]; - HTp current = sstart; - while (current && (current != send)) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull() || current->isRest()) { - current = current->getNextToken(); - continue; - } - vector subtokens = current->getSubtokens(); - for (int i=0; i<(int)subtokens.size(); i++) { - if (m_attack) { - // ignore sustained parts of notes when counting attacks - if (subtokens[i].find("_") != string::npos) { - continue; - } - if (subtokens[i].find("]") != string::npos) { - continue; - } - } - int b40 = Convert::kernToBase40(subtokens[i]); - if (m_attack) { - m_counts[kindex][b40%40]++; - } else { - double duration = Convert::recipToDuration(subtokens[i]).getFloat(); - m_counts[kindex][b40%40] += duration; - } - } - current = current->getNextToken(); + return status; +} + + +bool Tool_prange::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } + return status; } +bool Tool_prange::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; +} + ////////////////////////////// // -// Tool_pccount::initializePartInfo -- +// Tool_prange::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -void Tool_pccount::initializePartInfo(HumdrumFile& infile) { - m_names.clear(); - m_abbreviations.clear(); - m_parttracks.clear(); - m_rkern.clear(); - - m_rkern.resize(infile.getTrackCount() + 1); - fill(m_rkern.begin(), m_rkern.end(), -1); - - m_parttracks.push_back(-1); - m_names.push_back("all"); - m_abbreviations.push_back("all"); +void Tool_prange::initialize(void) { + m_accQ = getBoolean("color-accidentals"); + m_addFractionQ = getBoolean("fraction"); + m_allQ = getBoolean("all"); + m_debugQ = getBoolean("debug"); + m_defineQ = false; + m_diatonicQ = getBoolean("diatonic"); + m_durationQ = getBoolean("duration"); + m_fillOnlyQ = getBoolean("fill"); + m_finalisQ = getBoolean("finalis"); + m_hoverQ = getBoolean("hover"); + m_instrumentQ = getBoolean("instrument"); + m_keyQ = !getBoolean("no-key"); + m_listQ = false; + m_localQ = getBoolean("local-maximum"); + m_normQ = getBoolean("norm"); + m_notitleQ = getBoolean("no-title"); + m_percentile = getDouble("percentile"); + m_percentileQ = getBoolean("percentile"); + m_pitchQ = getBoolean("pitch"); + m_printQ = getBoolean("print"); + m_quartileQ = getBoolean("quartile"); + m_rangeQ = getBoolean("range"); + m_reverseQ = !getBoolean("reverse"); + m_scoreQ = getBoolean("score"); + m_title = getString("title"); + m_titleQ = getBoolean("title"); + m_embedQ = getBoolean("embed"); + m_extremaQ = getBoolean("extrema"); - vector starts = infile.getKernSpineStartList(); + getRange(m_rangeL, m_rangeH, getString("range")); - int foundpart = false; - int foundabbr = false; + if (getBoolean("jrp")) { + // default style settings for JRP range displays: + m_scoreQ = true; + m_allQ = true; + m_hoverQ = true; + m_accQ = true; + m_finalisQ = true; + m_notitleQ = true; + } - int track = 0; - for (int i=0; i<(int)starts.size(); i++) { - track = starts[i]->getTrack(); - m_rkern[track] = i+1; - m_parttracks.push_back(track); - HTp current = starts[i]; - foundpart = false; - foundabbr = false; - if (!current->isKern()) { - continue; - } - while (current) { - if (current->isData()) { - break; - } - if ((!foundpart) && (current->compare(0, 3, "*I\"") == 0)) { - m_names.emplace_back(current->substr(3)); - foundpart = true; - } else if ((!foundabbr) && (current->compare(0, 3, "*I\'") == 0)) { - m_abbreviations.emplace_back(current->substr(3)); - foundabbr = true; - } - current = current->getNextToken(); - } - //if (!foundpart) { - // m_names.emplace_back(""); - //} - //if (!foundabbr) { - // m_names.emplace_back(""); - //} + // The percentile is a fraction from 0.0 to 1.0. + // if the percentile is above 1.0, then it is assumed + // to be a percentage, in which case the value will be + // divided by 100 to get it in the range from 0 to 1. + if (m_percentile > 1) { + m_percentile = m_percentile / 100.0; } + #ifdef __EMSCRIPTEN__ + // Default styling for JavaScript version of program: + m_accQ = !getBoolean("color-accidentals"); + m_scoreQ = !getBoolean("score"); + m_embedQ = !getBoolean("embed"); + m_hoverQ = !getBoolean("hover"); + m_notitleQ = !getBoolean("no-title"); + #endif } + ////////////////////////////// // -// printVegaLiteJsonTemplate -- +// Tool_prange::processFile -- // -void Tool_pccount::printVegaLiteJsonTemplate(const string& datavariable, HumdrumFile& infile) { - stringstream& out = m_free_text; +void Tool_prange::processFile(HumdrumFile& infile) { + prepareRefmap(infile); + vector<_VoiceInfo> voiceInfo; + infile.fillMidiInfo(m_trackMidi); + getVoiceInfo(voiceInfo, infile); + fillHistograms(voiceInfo, infile); - string idinfo; - if (m_id.empty() || m_id == "id") { - // do nothing - } else { - idinfo = "for " + m_id; - } - out << "{\n"; - out << " \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.0.0-beta.1.json\",\n"; - out << " \"data\": {\"values\": " << datavariable << "},\n"; - if (getBoolean("title")) { - out << " \"title\": \"" << m_title << "\",\n"; - } else { - if (m_attack) { - out << " \"title\": \"Note-count pitch-class distribution " << idinfo <<" \",\n"; - } else { - out << " \"title\": \"Duration-weighted pitch-class distribution " << idinfo <<" \",\n"; + if (m_debugQ) { + for (int i=0; i<(int)voiceInfo.size(); i++) { + voiceInfo[i].print(cerr); } } - out << " \"width\": " << m_width << ",\n"; - out << " \"height\": " << int(m_width * m_ratio) << ",\n"; - out << " \"encoding\": {\n"; - out << " \"y\": {\n"; - if (m_attack) { - out << " \"field\": \"count\",\n"; - out << " \"title\": \"Number of note attacks\",\n"; - } else { - out << " \"field\": \"percent\",\n"; - out << " \"title\": \"Percent of maximum pitch class\",\n"; - } - out << " \"type\": \"quantitative\",\n"; - if (m_attack) { - out << " \"scale\": {\"domain\": [0, " << m_maxpc << "]},\n"; - } else { - out << " \"scale\": {\"domain\": [0, 100]},\n"; - } - out << " \"aggregate\": \"sum\"\n"; - out << " },\n"; - out << " \"x\": {\n"; - out << " \"field\": \"pitch class\",\n"; - out << " \"type\": \"nominal\",\n"; - out << " \"scale\": {\n"; - out << " \"domain\": ["; - printPitchClassList(); - out << "]\n"; - out << " },\n"; - out << " \"axis\": {\n"; - out << " \"labelAngle\": 0\n"; - out << " }\n"; - out << " },\n"; - out << " \"order\": {\"type\": \"quantitative\"},\n"; - out << " \"color\": {\n"; - out << " \"field\": \"voice\",\n"; - out << " \"type\": \"nominal\",\n"; - if (m_counts.size() == 2) { - out << " \"legend\": {\"title\": \"Voice\"},\n"; - } else { - out << " \"legend\": {\"title\": \"Voices\"},\n"; - } - out << " \"scale\": {\n"; - out << " \"domain\": ["; - printVoiceList(); - out << "],\n"; - out << " \"range\": ["; - printColorList(); - out << "],\n"; - out << " }\n"; - out << " }\n"; - out << " },\n"; - - out << " \"layer\": [\n"; - out << " {\"mark\": \"bar\"}"; - string final = getFinal(infile); - if (m_key && !final.empty()) { - out << ",\n"; - out << " {\n"; - out << " \"mark\": {\"type\":\"text\", \"align\":\"center\", \"fill\":\"black\", \"baseline\":\"bottom\"},\n"; - if (m_attack) { - int count = getCount(final); - out << " \"data\": {\"values\": [ {\"pitch class\":\"" << final << "\", \"count\":" << count << "}]},\n"; + if (m_scoreQ) { + stringstream scoreout; + printScoreFile(scoreout, voiceInfo, infile); + if (m_embedQ) { + if (m_extremaQ) { + doExtremaMarkup(infile); + } + m_humdrum_text << infile; + printEmbeddedScore(m_humdrum_text, scoreout, infile); } else { - double percent = getPercent(final); - out << " \"data\": {\"values\": [ {\"pitch class\":\"" << final << "\", \"percent\":" << percent << "}]},\n"; + if (m_extremaQ) { + doExtremaMarkup(infile); + } + m_humdrum_text << scoreout.str(); } - out << " \"encoding\": {\"text\": {\"value\":\"final\"}}\n"; - out << " }\n"; + } else { + printAnalysis(m_humdrum_text, voiceInfo[0].midibins); } - - out << " ]\n"; - out << "}\n"; - } ////////////////////////////// // -// Tool_pccount::getCount -- +// Tool_prange::doExtremaMarkup -- Mark highest and lowest note +// in each **kern spine. +// // -int Tool_pccount::getCount(const string& pitchclass) { - int b40 = Convert::kernToBase40(pitchclass); - int index = b40 % 40; - int output = (int)m_counts[0][index]; - return output; +void Tool_prange::doExtremaMarkup(HumdrumFile& infile) { + bool highQ = false; + bool lowQ = false; + for (int i=0; i<(int)m_trackMidi.size(); i++) { + int maxindex = -1; + int minindex = -1; + + for (int j=(int)m_trackMidi[i].size()-1; j>=0; j--) { + if (m_trackMidi[i][j].empty()) { + continue; + } + if (maxindex < 0) { + maxindex = j; + break; + } + } + + for (int j=1; j<(int)m_trackMidi[i].size(); j++) { + if (m_trackMidi[i][j].empty()) { + continue; + } + if (minindex < 0) { + minindex = j; + break; + } + } + + if ((maxindex < 0) || (minindex < 0)) { + continue; + } + applyMarkup(m_trackMidi[i][maxindex], m_highMark); + applyMarkup(m_trackMidi[i][minindex], m_lowMark); + highQ = true; + lowQ = true; + } + if (highQ) { + string highRdf = "!!!RDF**kern: " + m_highMark + " = marked note, color=\"hotpink\", highest note"; + infile.appendLine(highRdf); + } + if (lowQ) { + string lowRdf = "!!!RDF**kern: " + m_lowMark + " = marked note, color=\"limegreen\", lowest note"; + infile.appendLine(lowRdf); + } + if (highQ || lowQ) { + infile.createLinesFromTokens(); + } } ////////////////////////////// // -// Tool_pccount::getPercent -- +// Tool_prange::applyMarkup -- // -double Tool_pccount::getPercent(const string& pitchclass) { - setFactorMaximum(); - int b40 = Convert::kernToBase40(pitchclass); - int index = b40 % 40; - double output = m_counts[0][index] / m_factor * 100.0; - return output; +void Tool_prange::applyMarkup(vector>& notelist, const string& mark) { + for (int i=0; i<(int)notelist.size(); i++) { + HTp token = notelist[i].first; + int subtoken = notelist[i].second; + int tokenCount = token->getSubtokenCount(); + if (tokenCount == 1) { + string text = *token; + text += mark; + token->setText(text); + } else { + string stok = token->getSubtoken(subtoken); + stok = mark + stok; + token->replaceSubtoken(subtoken, stok); + } + } } ////////////////////////////// // -// Tool_pccount::printColorList -- +// Tool_prange::printEmbeddedScore -- // -void Tool_pccount::printColorList(void) { - stringstream& out = m_free_text; - for (int i=(int)m_names.size() - 1; i>0; i--) { - string color = m_vcolor[m_names[i]]; - out << "\""; - if (color.empty()) { - out << "black"; - } else { - out << color; - } - out << "\""; - if (i > 1) { - out << ", "; - } +void Tool_prange::printEmbeddedScore(ostream& out, stringstream& scoredata, HumdrumFile& infile) { + int id = getPrangeId(infile); + + out << "!!@@BEGIN: PREHTML\n"; + out << "!!@CONTENT:
    \n"; + out << "!!@@END: PREHTML\n"; + out << "!!@@BEGIN: SCORE\n"; + out << "!!@ID: prange-" << id << "\n"; + out << "!!@OUTPUTFORMAT: svg\n"; + out << "!!@CROP: yes\n"; + out << "!!@PADDING: 10\n"; + out << "!!@SCALING: 1.5\n"; + out << "!!@SVGFORMAT: yes\n"; + out << "!!@TRANSPARENT: yes\n"; + out << "!!@ANTIALIAS: no\n"; + out << "!!@EMBEDPMX: yes\n"; + out << "!!@ANNOTATE: no\n"; + out << "!!@CONTENTS:\n"; + string line; + while(getline(scoredata, line)) { + out << "!!" << line << endl; } + out << "!!@@END: SCORE\n"; } ////////////////////////////// // -// Tool_pccount::printVoiceList -- +// Tool_prange::getPrangeId -- Find a line in this form +// ^!!@ID: prange-(\d+)$ +// and return $1+1. Searching backwards since the HTML section +// will likely be at the bottom. Assuming that the prange +// SVG images are stored in sequence, with the highest ID last +// in the file if there are more than one. // -void Tool_pccount::printVoiceList(void) { - stringstream& out = m_free_text; - for (int i=(int)m_names.size() - 1; i>0; i--) { - out << "\""; - out << m_names[i]; - out << "\""; - if (i > 1) { - out << ", "; +int Tool_prange::getPrangeId(HumdrumFile& infile) { + string search = "!!@ID: prange-"; + int length = (int)search.length(); + for (int i=infile.getLineCount() - 1; i>=0; i--) { + HTp token = infile.token(i, 0); + if (token->compare(0, length, search) == 0) { + HumRegex hre; + if (hre.search(token, "prange-(\\d+)")) { + return hre.getMatchInt(1) + 1; + } } } + return 1; } ////////////////////////////// // -// Tool_pccount::printReverseVoiceList -- +// Tool_prange::mergeAllVoiceInfo -- // -void Tool_pccount::printReverseVoiceList(void) { - stringstream& out = m_free_text; - for (int i=1; i<(int)m_names.size(); i++) { - out << "\""; - out << m_names[i]; - out << "\""; - if (i < (int)m_names.size() - 1) { - out << ", "; +void Tool_prange::mergeAllVoiceInfo(vector<_VoiceInfo>& voiceInfo) { + voiceInfo.at(0).diafinal.clear(); + voiceInfo.at(0).accfinal.clear(); + + for (int i=1; i<(int)voiceInfo.size(); i++) { + if (!voiceInfo[i].kernQ) { + continue; + } + for (int j=0; j<(int)voiceInfo.at(i).diafinal.size(); j++) { + voiceInfo.at(0).diafinal.push_back(voiceInfo.at(i).diafinal.at(j)); + voiceInfo.at(0).accfinal.push_back(voiceInfo.at(i).accfinal.at(j)); + voiceInfo.at(0).namfinal.push_back(voiceInfo.at(i).name); + } + + for (int j=0; j<(int)voiceInfo[i].midibins.size(); j++) { + voiceInfo[0].midibins[j] += voiceInfo[i].midibins[j]; + } + + for (int j=0; j<(int)voiceInfo.at(i).diatonic.size(); j++) { + for (int k=0; k<(int)voiceInfo.at(i).diatonic.at(k).size(); k++) { + voiceInfo[0].diatonic.at(j).at(k) += voiceInfo.at(i).diatonic.at(j).at(k); + } } } } @@ -113936,234 +118407,308 @@ void Tool_pccount::printReverseVoiceList(void) { ////////////////////////////// // -// Tool_pccount::printPitchClassList -- +// Tool_prange::getVoiceInfo -- get names and track info for **kern spines. // -void Tool_pccount::printPitchClassList(void) { - stringstream& out = m_free_text; - - if (m_counts[0][0] > 0.0) { out << "\"C♭♭\", "; } - if (m_counts[0][1] > 0.0) { out << "\"C♭\", "; } - out << "\"C\""; - if (m_counts[0][3] > 0.0) { out << ", \"C♯\""; } - if (m_counts[0][4] > 0.0) { out << ", \"C♯♯\""; } - // 5 is empty - - if (m_counts[0][6] > 0.0) { out << ", \"D♭♭\""; } - if (m_counts[0][7] > 0.0) { out << ", \"D♭\""; } - out << ", \"D\""; - if (m_counts[0][9] > 0.0) { out << ", \"D♯\""; } - if (m_counts[0][10] > 0.0) { out << ", \"D♯♯\""; } - // 11 is empty +void Tool_prange::getVoiceInfo(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { + voiceInfo.clear(); + voiceInfo.resize(infile.getMaxTracks() + 1); + for (int i=0; i<(int)voiceInfo.size(); i++) { + voiceInfo.at(i).index = i; + } - if (m_counts[0][12] > 0.0) { out << ", \"E♭♭\""; } - if (m_counts[0][13] > 0.0) { out << ", \"E♭\""; } - out << ", \"E\""; - if (m_counts[0][15] > 0.0) { out << ", \"E♯\""; } - if (m_counts[0][16] > 0.0) { out << ", \"E♯♯\""; } + vector kstarts = infile.getKernSpineStartList(); - if (m_counts[0][17] > 0.0) { out << ", \"F♭♭\""; } - if (m_counts[0][18] > 0.0) { out << ", \"F♭\""; } - out << ", \"F\""; - if (m_counts[0][20] > 0.0) { out << ", \"F♯\""; } - if (m_counts[0][21] > 0.0) { out << ", \"F♯♯\""; } - // 22 is empty + if (kstarts.size() == 2) { + voiceInfo[0].name = "both"; + voiceInfo[0].abbr = "both"; + voiceInfo[0].track = 0; + } else { + voiceInfo[0].name = "all"; + voiceInfo[0].abbr = "all"; + voiceInfo[0].track = 0; + } - if (m_counts[0][23] > 0.0) { out << ", \"G♭♭\""; } - if (m_counts[0][24] > 0.0) { out << ", \"G♭\""; } - out << ", \"G\""; - if (m_counts[0][26] > 0.0) { out << ", \"G♯\""; } - if (m_counts[0][27] > 0.0) { out << ", \"G♯♯\""; } - // 28 is empty - if (m_counts[0][29] > 0.0) { out << ", \"A♭♭\""; } - if (m_counts[0][30] > 0.0) { out << ", \"A♭\""; } - out << ", \"A\""; - if (m_counts[0][32] > 0.0) { out << ", \"A♯\""; } - if (m_counts[0][33] > 0.0) { out << ", \"A♯♯\""; } - // 34 is empty + for (int i=0; igetTrack(); + voiceInfo[track].track = track; + if (token->isKern()) { + voiceInfo[track].kernQ = true; + } + if (!voiceInfo[track].kernQ) { + continue; + } + if (token->isInstrumentName()) { + voiceInfo[track].name = token->getInstrumentName(); + } + if (token->isInstrumentAbbreviation()) { + voiceInfo[track].abbr = token->getInstrumentAbbreviation(); + } + } + } - if (m_counts[0][35] > 0.0) { out << ", \"B♭♭\""; } - if (m_counts[0][36] > 0.0) { out << ", \"B♭\""; } - out << ", \"B\""; - if (m_counts[0][38] > 0.0) { out << ", \"B♯\""; } - if (m_counts[0][39] > 0.0) { out << ", \"B♯♯\""; } + // Check for piano/Grand Staff parts with LH/RH encoding. + if (kstarts.size() == 2) { + string bottomStaff = getHand(kstarts[0]); + string topStaff = getHand(kstarts[1]); + if (!bottomStaff.empty() && !topStaff.empty()) { + int track = kstarts[0]->getTrack(); + voiceInfo[track].name = "left hand"; + track = kstarts[1]->getTrack(); + voiceInfo[track].name = "right hand"; + } + } } + ////////////////////////////// // -// Tool_pccount::getPitchClassString -- +// Tool_prange::getHand -- // -string Tool_pccount::getPitchClassString(int b40) { - switch (b40%40) { - case 0: return "C♭♭"; - case 1: return "C♭"; - case 2: return "C"; - case 3: return "C♯"; - case 4: return "C♯♯"; - // 5 is empty - case 6: return "D♭♭"; - case 7: return "D♭"; - case 8: return "D"; - case 9: return "D♯"; - case 10: return "D♯♯"; - // 11 is empty - case 12: return "E♭♭"; - case 13: return "E♭"; - case 14: return "E"; - case 15: return "E♯"; - case 16: return "E♯♯"; - case 17: return "F♭♭"; - case 18: return "F♭"; - case 19: return "F"; - case 20: return "F♯"; - case 21: return "F♯♯"; - // 22 is empty - case 23: return "G♭♭"; - case 24: return "G♭"; - case 25: return "G"; - case 26: return "G♯"; - case 27: return "G♯♯"; - // 28 is empty - case 29: return "A♭♭"; - case 30: return "A♭"; - case 31: return "A"; - case 32: return "A♯"; - case 33: return "A♯♯"; - // 34 is empty - case 35: return "B♭♭"; - case 36: return "B♭"; - case 37: return "B"; - case 38: return "B♯"; - case 39: return "B♯♯"; +string Tool_prange::getHand(HTp sstart) { + HTp current = sstart->getNextToken(); + HTp target = NULL; + while (current) { + if (current->isData()) { + break; + } + if (*current == "*LH") { + target = current; + break; + } + if (*current == "*RH") { + target = current; + break; + } + current = current->getNextToken(); } - return "?"; + if (target) { + if (*current == "*LH") { + return "LH"; + } else if (*current == "*RH") { + return "RH"; + } else { + return ""; + } + } else { + return ""; + } } - - - -///////////////////////////////// +////////////////////////////// // -// Tool_periodicity::Tool_periodicity -- Set the recognized options for the tool. +// Tool_prange::getInstrumentNames -- Find any instrument names which are listed +// before the first data line. Instrument names are in the form: +// +// *I"name // -Tool_periodicity::Tool_periodicity(void) { - define("m|min=b", "minimum time unit (other than grace notes)"); - define("n|max-rows=i:-1", "maxumum number of rows in svg analysis display"); - define("t|track=i:0", "track to analyze"); - define("attacks=b", "extract attack grid)"); - define("raw=b", "show only raw period data"); - define("s|svg=b", "output svg image"); - define("p|power=d:2.0", "scaling power for visual display"); - define("1|one=b", "composite rhythms are not weighted by attack"); +void Tool_prange::getInstrumentNames(vector& nameByTrack, vector& kernSpines, + HumdrumFile& infile) { + HumRegex hre; + + int track; + string name; + // nameByTrack.resize(kernSpines.size()); + nameByTrack.resize(infile.getMaxTrack() + 1); + fill(nameByTrack.begin(), nameByTrack.end(), ""); + vector kspines = infile.getKernSpineStartList(); + if (kspines.size() == 2) { + nameByTrack.at(0) = "both"; + } else { + nameByTrack.at(0) = "all"; + } + + for (int i=0; igetTrack(); + for (int k=0; k<(int)kernSpines.size(); k++) { + if (track == kernSpines[k]) { + nameByTrack[k] = name; + } + } + } + } + } } -///////////////////////////////// +////////////////////////////// // -// Tool_periodicity::run -- Primary interfaces to the tool. +// Tool_prange::fillHistograms -- Store notes in score by MIDI note number. // -bool Tool_periodicity::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i& voiceInfo, HumdrumFile& infile) { + // storage for finals info: + vector> diafinal; + vector> accfinal; + diafinal.resize(infile.getMaxTracks() + 1); + accfinal.resize(infile.getMaxTracks() + 1); + + for (int i=0; iisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + int track = token->getTrack(); + diafinal.at(track).clear(); + accfinal.at(track).clear(); -bool Tool_periodicity::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status; - status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} + vector tokens = token->getSubtokens(); + for (int k=0; k<(int)tokens.size(); k++) { + if (tokens[k].find("r") != string::npos) { + continue; + } + if (tokens[k].find("R") != string::npos) { + // non-pitched note + continue; + } + bool hasPitch = false; + for (int m=0; m<(int)tokens[k].size(); m++) { + char test = tokens[k].at(m); + if (!isalpha(test)) { + continue; + } + test = tolower(test); + if ((test >= 'a') && (test <= 'g')) { + hasPitch = true; + break; + } + } + if (!hasPitch) { + continue; + } + int octave = Convert::kernToOctaveNumber(tokens[k]) + 3; + if (octave < 0) { + cerr << "Note too low: " << tokens[k] << endl; + continue; + } + if (octave >= 12) { + cerr << "Note too high: " << tokens[k] << endl; + continue; + } + int dpc = Convert::kernToDiatonicPC(tokens[k]); + int acc = Convert::kernToAccidentalCount(tokens[k]); + if (acc < -2) { + cerr << "Accidental too flat: " << tokens[k] << endl; + continue; + } + if (acc > +2) { + cerr << "Accidental too sharp: " << tokens[k] << endl; + continue; + } + int diatonic = dpc + 7 * octave; + int realdiatonic = dpc + 7 * (octave-3); + diafinal.at(track).push_back(realdiatonic); + accfinal.at(track).push_back(acc); -bool Tool_periodicity::run(HumdrumFile& infile, ostream& out) { - bool status; - status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + acc += 3; + int midi = Convert::kernToMidiNoteNumber(tokens[k]); + if (midi < 0) { + cerr << "MIDI pitch too low: " << tokens[k] << endl; + } + if (midi > 127) { + cerr << "MIDI pitch too high: " << tokens[k] << endl; + } + if (m_durationQ) { + double duration = Convert::kernToDuration(tokens[k]).getFloat(); + voiceInfo[track].diatonic.at(diatonic).at(0) += duration; + voiceInfo[track].diatonic.at(diatonic).at(acc) += duration; + voiceInfo[track].midibins.at(midi) += duration; + } else { + if (tokens[k].find("]") != string::npos) { + continue; + } + if (tokens[k].find("_") != string::npos) { + continue; + } + voiceInfo[track].diatonic.at(diatonic).at(0)++; + voiceInfo[track].diatonic.at(diatonic).at(acc)++; + voiceInfo[track].midibins.at(midi)++; + } + } + } } - return status; -} -// -// In-place processing of file: -// + mergeFinals(voiceInfo, diafinal, accfinal); -bool Tool_periodicity::run(HumdrumFile& infile) { - processFile(infile); - return true; + // Sum all voices into midibins and diatonic arrays of vector position 0: + mergeAllVoiceInfo(voiceInfo); } ////////////////////////////// // -// Tool_periodicity::processFile -- +// Tool_prange::mergeFinals -- // -void Tool_periodicity::processFile(HumdrumFile& infile) { - HumNum minrhy = infile.tpq() * 4; - if (getBoolean("min")) { - m_free_text << minrhy << endl; - return; - } - - vector> attackgrids; - attackgrids.resize(infile.getTrackCount()+1); - fillAttackGrids(infile, attackgrids, minrhy); - if (getBoolean("attacks")) { - printAttackGrid(m_free_text, infile, attackgrids, minrhy); - return; - } - - int atrack = getInteger("track"); - vector> analysis; - doPeriodicityAnalysis(analysis, attackgrids[atrack], minrhy); - - if (getBoolean("raw")) { - printPeriodicityAnalysis(m_free_text, analysis); - return; +void Tool_prange::mergeFinals(vector<_VoiceInfo>& voiceInfo, vector>& diafinal, + vector>& accfinal) { + for (int i=0; i<(int)voiceInfo.size(); i++) { + voiceInfo.at(i).diafinal = diafinal.at(i); + voiceInfo.at(i).accfinal = accfinal.at(i); } - - printSvgAnalysis(m_free_text, analysis, minrhy); } ////////////////////////////// // -// Tool_periodicity::printPeriodicityAnalysis -- +// Tool_prange::printFilenameBase -- // -void Tool_periodicity::printPeriodicityAnalysis(ostream& out, vector>& analysis) { - for (int i=0; i<(int)analysis.size(); i++) { - for (int j=0; j<(int)analysis[i].size(); j++) { - out << analysis[i][j]; - if (j < (int)analysis[i].size() - 1) { - out << "\t"; +void Tool_prange::printFilenameBase(ostream& out, const string& filename) { + HumRegex hre; + if (hre.search(filename, "([^/]+)\\.([^.]*)", "")) { + if (hre.getMatch(1).size() <= 8) { + printXmlEncodedText(out, hre.getMatch(1)); + } else { + // problem with too long a name (MS-DOS will have problems). + // optimize to chop off everything after the dash in the + // name (for Josquin catalog numbers). + string shortname = hre.getMatch(1); + if (hre.search(shortname, "-.*")) { + hre.replaceDestructive(shortname, "", "-.*"); + printXmlEncodedText(out, shortname); + } else { + printXmlEncodedText(out, shortname); } } - out << "\n"; } } @@ -114171,13 +118716,19 @@ void Tool_periodicity::printPeriodicityAnalysis(ostream& out, vector> &analysis, vector& grid, HumNum minrhy) { - analysis.resize(minrhy.getNumerator()); - for (int i=0; i<(int)analysis.size(); i++) { - doAnalysis(analysis, i, grid); +void Tool_prange::printReferenceRecords(ostream& out, HumdrumFile& infile) { + for (int i=0; i\n"; } } @@ -114185,1272 +118736,1381 @@ void Tool_periodicity::doPeriodicityAnalysis(vector> &analysis, v ////////////////////////////// // -// Tool_periodicity::doAnalysis -- +// Tool_prange::printScoreEncodedText -- print SCORE text string +// See SCORE 3.1 manual additions (page 19) for more. // -void Tool_periodicity::doAnalysis(vector>& analysis, int level, vector& grid) { - int period = level + 1; - analysis[level].resize(period); - std::fill(analysis[level].begin(), analysis[level].end(), 0.0); - for (int i=0; i>$1", "&([aeiou])grave;", "gi"); + hre.replaceDestructive(newstring, ">>$1", "([àèìòù])", "gi"); -////////////////////////////// -// -// Tool_periodicity::printAttackGrid -- -// + hre.replaceDestructive(newstring, "%%$1", "&([aeiou])uml;", "gi"); + hre.replaceDestructive(newstring, "%%$1", "([äëïöü])", "gi"); -void Tool_periodicity::printAttackGrid(ostream& out, HumdrumFile& infile, vector>& grids, HumNum minrhy) { - out << "!!!minrhy: " << minrhy << endl; - out << "**all"; - for (int i=1; i<(int)grids.size(); i++) { - out << "\t**track"; - } - out << "\n"; - for (int j=0; j<(int)grids[0].size(); j++) { - for (int i=0; i<(int)grids.size(); i++) { - out << grids[i][j]; - if (i < (int)grids.size() - 1) { - out << "\t"; - } - } - out << "\n"; - } - for (int i=0; i<(int)grids.size(); i++) { - out << "*-"; - if (i < (int)grids.size() - 1) { - out << "\t"; - } - } - out << "\n"; + hre.replaceDestructive(newstring, "^^$1", "&([aeiou])circ;", "gi"); + hre.replaceDestructive(newstring, "^^$1", "([âêîôû])", "gi"); + + hre.replaceDestructive(newstring, "##c", "ç", "g"); + hre.replaceDestructive(newstring, "##C", "Ç", "g"); + hre.replaceDestructive(newstring, "?\\|", "\\|", "g"); + hre.replaceDestructive(newstring, "?\\", "\\\\", "g"); + hre.replaceDestructive(newstring, "?m", "---", "g"); + hre.replaceDestructive(newstring, "?n", "--", "g"); + hre.replaceDestructive(newstring, "?2", "-sharp", "g"); + hre.replaceDestructive(newstring, "?1", "-flat", "g"); + hre.replaceDestructive(newstring, "?3", "-natural", "g"); + hre.replaceDestructive(newstring, "\\", "/", "g"); + hre.replaceDestructive(newstring, "?[", "\\[", "g"); + hre.replaceDestructive(newstring, "?]", "\\]", "g"); + out << newstring; } ////////////////////////////// // -// Tool_periodicity::fillAttackGrids -- +// Tool_prange::printXmlEncodedText -- convert +// & to & +// " to " +// ' to &spos; +// < to < +// > to > // -void Tool_periodicity::fillAttackGrids(HumdrumFile& infile, vector>& grids, HumNum minrhy) { - HumNum elements = minrhy * infile.getScoreDuration() / 4; - - for (int t=0; t<(int)grids.size(); t++) { - grids[t].resize(elements.getNumerator()); - } +void Tool_prange::printXmlEncodedText(ostream& out, const string& strang) { + HumRegex hre; + string astring = strang; - for (int i=0; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - if (!token->isNoteAttack()) { - continue; - } - int track = token->getTrack(); - grids.at(track).at(position.getNumerator()) += 1; - } - } + hre.replaceDestructive(astring, "&", "&", "g"); + hre.replaceDestructive(astring, "'", "'", "g"); + hre.replaceDestructive(astring, "\"", """, "g"); + hre.replaceDestructive(astring, "<", "<", "g"); + hre.replaceDestructive(astring, ">", ">", "g"); - bool oneQ = getBoolean("one"); - for (int j=0; j<(int)grids.at(0).size(); j++) { - grids.at(0).at(j) = 0; - for (int i=0; i<(int)grids.size(); i++) { - if (!grids.at(i).at(j)) { - continue; - } - if (oneQ) { - grids.at(0).at(j) = 1; - } else { - grids.at(0).at(j) += grids.at(i).at(j); - } - } - } + out << astring; } ////////////////////////////// // -// Tool_periodicity::printSvgAnalysis -- +// Tool_prange::printScoreFile -- // -void Tool_periodicity::printSvgAnalysis(ostream& out, vector>& analysis, HumNum minrhy) { - pugi::xml_document image; - auto declaration = image.prepend_child(pugi::node_declaration); - declaration.append_attribute("version") = "1.0"; - declaration.append_attribute("encoding") = "UTF-8"; - declaration.append_attribute("standalone") = "no"; - - auto svgnode = image.append_child("svg"); - svgnode.append_attribute("version") = "1.1"; - svgnode.append_attribute("xmlns") = "http://www.w3.org/2000/svg"; - svgnode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink"; - svgnode.append_attribute("overflow") = "visible"; - svgnode.append_attribute("viewBox") = "0 0 1000 1000"; - svgnode.append_attribute("width") = "1000px"; - svgnode.append_attribute("height") = "1000px"; - - auto style = svgnode.append_child("style"); - style.text().set(".label { font: 14px sans-serif; alignment-baseline: middle; text-anchor: left; }"); +void Tool_prange::printScoreFile(ostream& out, vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { + string titlestring = getTitle(); - auto grid = svgnode.append_child("g"); - grid.append_attribute("id") = "grid"; + if (m_defineQ) { + out << "#define SVG t 1 1 \\n_99%svg%\n"; + } - auto labels = svgnode.append_child("g"); + string acctext = "g.bar.doubleflat path{color:darkorange;stroke:darkorange;}g.bar.flat path{color:brown;stroke:brown;}g.bar.sharp path{color:royalblue;stroke:royalblue;}g.bar.doublesharp path{color:aquamarine;stroke:aquamarine;}"; + string hovertext = ".bar:hover path{fill:red;color:red;stroke:red !important}"; + string hoverfilltext = hovertext; - double hue = 0.0; - double saturation = 100; - double lightness = 75; + string text1 = ""; + string text2 = text1; - pugi::xml_node crect; - double width; - double height; - stringstream ss; - stringstream ssl; - //stringstream css; - double x; - double y; + // print CSS style information if requested + if (m_hoverQ) { + SVGTEXT(out, text1); + } - double imagewidth = 1000.0; - double imageheight = 1000.0; + int maxStaffPosition = getMaxStaffPosition(voiceInfo); - int maxrow = getInteger("max-rows"); - if (maxrow <= 0) { - maxrow = (int)analysis.back().size(); + if (!titlestring.empty()) { + // print title + int vpos = 54; + if (maxStaffPosition > 12) { + vpos = maxStaffPosition + 3; + } + out << "t 2 10 "; + out << vpos; + out << " 1 1 0 0 0 0 -1.35\n"; + // out << "_03"; + printScoreEncodedText(out, titlestring); + out << "\n"; } + // print duration label if duration weighting is being used + SVGTEXT(out, ""); + if (m_durationQ) { + out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n"; + out << "_00(durations)\n"; + } else { + out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n"; + out << "_00(attacks)\n"; + } + SVGTEXT(out, ""); - // double sdur = (double)analysis.back().size(); - double sdur = (double)maxrow; + // print staff lines + out << "8 1 0 0 0 200\n"; // staff 1 + out << "8 2 0 -6 0 200\n"; // staff 2 - double maxscore = 0.0; - for (int i=0; i maxvalue) { + maxvalue = tempvalue; } + } + for (int i=(int)voiceInfo.size()-1; i>0; i--) { + if (voiceInfo.at(i).kernQ) { + printScoreVoice(out, voiceInfo.at(i), maxvalue); + } + } + if (m_allQ) { + printScoreVoice(out, voiceInfo.at(0), maxvalue); + } +} - pugi::xml_node label = labels.append_child("text"); - label.append_attribute("class") = "label"; - - HumNum rval = (i+1); - rval /= minrhy; - rval *= 4; - std::string rhythm = Convert::durationToRecip(rval); - rhythm += " (" + to_string(i+1) + ")"; - label.text().set(rhythm.c_str()); - x = (i+1+0.5)/sdur * imageheight; - y = (i+0.5)/sdur * imagewidth; - label.append_attribute("x") = to_string(x).c_str(); - label.append_attribute("y") = to_string(y).c_str(); - } +////////////////////////////// +// +// Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceinfo) { +// - image.save(out); +int Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceInfo) { + int maxi = getMaxDiatonicIndex(voiceInfo[0].diatonic); + int maxdiatonic = maxi - 3 * 7; + int staffline = maxdiatonic - 27; + return staffline; } + ////////////////////////////// // -// Tool_periodicity::getColorMapping -- +// Tool_prange::printKeySigCompression -- // -void Tool_periodicity::getColorMapping(double input, double& hue, - double& saturation, double& lightness) { - double maxhue = 0.75 * 360.0; - hue = input; - if (hue < 0.0) { - hue = 0.0; - } - hue = hue * hue; - if (hue != 1.0) { - hue *= 0.95; - } - - hue = (1.0 - hue) * 360.0; - if (hue == 0.0) { - // avoid -0.0; - hue = 0.0; - } - - if (hue > maxhue) { - hue = maxhue; +void Tool_prange::printKeySigCompression(ostream& out, int keysig, int extra) { + double compression = 0.0; + switch (abs(keysig)) { + case 0: compression = 0.0; break; + case 1: compression = 0.0; break; + case 2: compression = 0.0; break; + case 3: compression = 0.0; break; + case 4: compression = 0.9; break; + case 5: compression = 0.8; break; + case 6: compression = 0.7; break; + case 7: compression = 0.6; break; } - if (hue < 0.0) { - hue = maxhue; + if (compression <= 0.0) { + return; } - - saturation = 100.0; - lightness = 50.0; - - if (hue > 60) { - lightness = lightness - (hue-60) / (maxhue-60) * lightness / 1.5; + for (int i=0; i& voiceInfo, int minval, int maxval) { + int count = 0; + for (int i=1; i<(int)voiceInfo.size(); i++) { + if (voiceInfo[i].kernQ) { + count++; + } + } + if (m_allQ) { + count++; + } + vector hpos(count, 0); + hpos[0] = maxval; + hpos.back() = minval; -/////////////////////////////// -// -// Tool_phrase::run -- Primary interfaces to the tool. -// + if (hpos.size() > 2) { + for (int i=1; i<(int)hpos.size()-1; i++) { + int ii = hpos.size() - i - 1; + hpos[i] = (double)ii / (hpos.size()-1) * (maxval - minval) + minval; + } + } -bool Tool_phrase::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisKeySignature()) { + return Convert::kernKeyToNumber(*token); + } } } - if (!m_remove2Q) { - prepareAnalysis(infile); - } - infile.createLinesFromTokens(); - return true; + + return 0; // C major key signature } ////////////////////////////// // -// Tool_phrase::prepareAnalysis -- +// Tool_prange::printScoreVoice -- print the range information for a particular voice (in SCORE format). // -void Tool_phrase::prepareAnalysis(HumdrumFile& infile) { - string exinterp = "**cdata"; - infile.appendDataSpine(m_results.back(), "", exinterp); - for (int i = (int)m_results.size()-1; i>0; i--) { - int track = m_starts[i]->getTrack(); - infile.insertDataSpineBefore(track, m_results[i-1], "", exinterp); +void Tool_prange::printScoreVoice(ostream& out, _VoiceInfo& voiceInfo, double maxvalue) { + int mini = getMinDiatonicIndex(voiceInfo.diatonic); + int maxi = getMaxDiatonicIndex(voiceInfo.diatonic); + + if ((mini < 0) || (maxi < 0)) { + // no data for voice so skip + return; } - if (m_averageQ) { - addAverageLines(infile); + + // int minacci = getMinDiatonicAcc(voiceInfo.diatonic, mini); + // int maxacci = getMaxDiatonicAcc(voiceInfo.diatonic, maxi); + int mindiatonic = mini - 3 * 7; + int maxdiatonic = maxi - 3 * 7; + // int minacc = minacci - 3; + // int maxacc = maxacci - 3; + + int staff; + double vpos; + + int voicevpos = -3; + staff = getStaffBase7(mindiatonic); + int lowestvpos = getVpos(mindiatonic); + if ((staff == 1) && (lowestvpos <= 0)) { + voicevpos += lowestvpos - 2; } - if (!m_color.empty()) { - int insertline = -1; - for (int i=0; i 0) { - stringstream ss; - int fsize = infile[insertline].getFieldCount(); - for (int j=0; jgetDataType(); - if (dt.empty() || (dt == "**cdata")) { - ss << "color:" << m_color; - } - if (j < fsize - 1) { - ss << "\t"; - } + base7 = i - 3 * 7; + staff = getStaffBase7(base7); + vpos = getVpos(base7); + + // staring positions of accidentals: + vector starthpos(6, 0.0); + for (int j=1; j<(int)starthpos.size(); j++) { + double width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue; + starthpos[j] = starthpos[j-1] + width; + } + for (int j=(int)starthpos.size() - 1; j>0; j--) { + starthpos[j] = starthpos[j-1]; + } + + // print chromatic alterations + for (int j=(int)voiceInfo.diatonic.at(i).size()-1; j>0; j--) { + if (voiceInfo.diatonic.at(i).at(j) <= 0.0) { + continue; + } + int acc = 0; + switch (j) { + case 1: acc = -2; break; + case 2: acc = -1; break; + case 3: acc = 0; break; + case 4: acc = +1; break; + case 5: acc = +2; break; + } + + width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue + hoffset; + if (m_hoverQ) { + string title = getNoteTitle((int)voiceInfo.diatonic.at(i).at(j), base7, acc); + SVGTEXT(out, title); + } + out << "1 " << staff << " " << (voiceInfo.hpos + starthpos.at(j) + hoffset) << " " << vpos; + out << " 0 -1 4 0 0 0 99 0 0 "; + out << width << "\n"; + if (m_hoverQ) { + SVGTEXT(out, "
    "); } - string output = ss.str(); - infile.insertLine(insertline, output); } } -} + string voicestring = voiceInfo.name; + if (voicestring.empty()) { + voicestring = voiceInfo.abbr; + } + if (!voicestring.empty()) { + HumRegex hre; + hre.replaceDestructive(voicestring, "", "(\\\\n)+$"); + vector pieces; + hre.split(pieces, voicestring, "\\\\n"); + if (pieces.size() > 1) { + voicestring = ""; + for (int i=0; i<(int)pieces.size(); i++) { + voicestring += pieces[i]; + if (i < (int)pieces.size() - 1) { + voicestring += "/"; + } + } + } -/////////////////////////////// -// -// Tool_pharse::addAverageLines -- -// + double increment = 4.0; + for (int i=0; i<(int)pieces.size(); i++) { + // print voice name + double tvoffset = -4.0; + out << "t 1 " << voiceInfo.hpos << " " + << (voicevpos - increment * i) + << " 1 1 0 0 0 0 " << tvoffset; + out << "\n"; -void Tool_phrase::addAverageLines(HumdrumFile& infile) { - vector averages; - averages.resize(m_starts.size()+1); - int tcount = 0; - HumNum tsum = 0; - double average; - stringstream ss; - for (int i=0; i<(int)m_starts.size(); i++) { - if (m_pcount[i] > 0) { - average = m_psum[i].getFloat() / m_pcount[i]; - } else { - average = 0.0; + if (pieces[i] == "all") { + out << "_02"; + } else if (pieces[i] == "both") { + out << "_02"; + } else { + out << "_00"; + } + printScoreEncodedText(out, pieces[i]); + out << "\n"; } - ss.str(""); - ss.clear(); - ss << "!!average-phrase-length-k" << i+1 << ":\t" << average; - averages[i+1] = ss.str(); - tcount += m_pcount[i]; - tsum += m_psum[i]; } - average = tsum.getFloat() / tcount; - ss.str(""); - ss.clear(); - ss << "!!average-phrase-length:\t" << average; - averages[0] = ss.str(); - for (int i=0; i<(int)averages.size(); i++) { - infile.appendLine(averages[i]); + // print the lowest pitch in range + staff = getStaffBase7(mindiatonic); + vpos = getVpos(mindiatonic); + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(mindiatonic, 0); + content += ": lowest note"; + if (!voicestring.empty()) { + content += " of "; + content += voicestring; + content += "'s range"; + } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos + << " 0 0 4 0 0 -2\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); + } + + // print the highest pitch in range + staff = getStaffBase7(maxdiatonic); + vpos = getVpos(maxdiatonic); + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(maxdiatonic, 0); + content += ": highest note"; + if (!voicestring.empty()) { + content += " of "; + content += voicestring; + content += "'s range"; + } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos + << " 0 0 4 0 0 -2\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); } -} + double goffset = -1.66; + double toffset = 1.5; + double median12 = getMedian12(voiceInfo.midibins); + double median40 = Convert::base12ToBase40(median12); + double median7 = Convert::base40ToDiatonic(median40); + // int acc = Convert::base40ToAccidental(median40); + staff = getStaffBase7(median7); + vpos = getVpos(median7); -/////////////////////////////// -// -// Tool_phrase::initialize -- -// + // these offsets are useful when the quartile pitches are not shown... + int vvpos = maxdiatonic - median7 + 1; + int vvpos2 = median7 - mindiatonic + 1; + double offset = goffset; + if (vvpos <= 2) { + offset += toffset; + } else if (vvpos2 <= 2) { + offset -= toffset; + } -void Tool_phrase::initialize(HumdrumFile& infile) { - m_starts = infile.getKernSpineStartList(); - m_results.resize(m_starts.size()); - int lines = infile.getLineCount(); - for (int i=0; i<(int)m_results.size(); i++) { - m_results[i].resize(lines); + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(median7, 0); + content += ": median note"; + if (!voicestring.empty()) { + content += " of "; + content += voicestring; + content += "'s range"; + } + content += ""; + SVGTEXT(out, content); } - m_pcount.resize(m_starts.size()); - m_psum.resize(m_starts.size()); - std::fill(m_pcount.begin(), m_pcount.end(), 0); - std::fill(m_psum.begin(), m_psum.end(), 0); - m_markQ = getBoolean("mark"); - m_removeQ = getBoolean("remove"); - m_averageQ = !getBoolean("no-average"); - m_remove2Q = getBoolean("remove2"); - if (getBoolean("color")) { - m_color = getString("color"); + out << "1 " << staff << " " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 1 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); } -} - + if (m_finalisQ) { + for (int f=0; f<(int)voiceInfo.diafinal.size(); f++) { + int diafinalis = voiceInfo.diafinal.at(f); + int accfinalis = voiceInfo.accfinal.at(f); + int staff = getStaffBase7(diafinalis); + int vpos = getVpos(diafinalis); + double goffset = -1.66; + double toffset = 3.5; -/////////////////////////////// -// -// Tool_phrase::analyzeSpineByRests -- -// + // these offsets are useful when the quartile pitches are not shown... + double offset = goffset; + offset += toffset; -void Tool_phrase::analyzeSpineByRests(int index) { - HTp start = m_starts[index]; - HTp current = start; - HTp lastnote = NULL; // last note to be processed - HTp pstart = NULL; // phrase start; - HumNum dur; - stringstream ss; - while (current) { - if (current->isBarline()) { - if (current->find("||") != std::string::npos) { - if (pstart) { - dur = current->getDurationFromStart() - - pstart->getDurationFromStart(); - ss.str(""); - ss.clear(); - ss << dur.getFloat(); - m_psum[index] += dur; - m_pcount[index]++; - m_results[index][pstart->getLineIndex()] = ss.str(); - pstart = NULL; - if (m_markQ && lastnote) { - lastnote->setText(lastnote->getText() + "}"); + if (m_hoverQ) { + string content = ""; + content += getDiatonicPitchName(diafinalis, accfinalis); + content += ": last note"; + if (!voicestring.empty()) { + content += " of "; + if (voiceInfo.index == 0) { + content += voiceInfo.namfinal.at(f); + } else { + content += voicestring; } } + content += ""; + SVGTEXT(out, content); + } + out << "1 " << staff << " " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 0 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); } } - if (!current->isData()) { - current = current->getNextToken(); - continue; + } + + /* Needs fixing + int topquartile; + if (m_quartileQ) { + // print top quartile + topquartile = getTopQuartile(voiceInfo.midibins); + if (m_diatonicQ) { + topquartile = Convert::base7ToBase12(topquartile); } - if (current->isNull()) { - current = current->getNextToken(); - continue; + staff = getStaffBase7(topquartile); + vpos = getVpos(topquartile); + vvpos = median7 - topquartile + 1; + if (vvpos <= 2) { + offset = goffset + toffset; + } else { + offset = goffset; } - if (pstart && current->isRest()) { - if (lastnote) { - dur = current->getDurationFromStart() - - pstart->getDurationFromStart(); - ss.str(""); - ss.clear(); - ss << dur.getFloat(); - m_psum[index] += dur; - m_pcount[index]++; - m_results[index][pstart->getLineIndex()] = ss.str(); - if (m_markQ) { - lastnote->setText(lastnote->getText() + "}"); - } + vvpos = maxdiatonic - topquartile + 1; + if (vvpos <= 2) { + offset = goffset + toffset; + } + + if (m_hoverQ) { + if (m_defineQ) { + out << "SVG "; + } else { + out << "t 1 1\n"; + out << SVGTAG; } - pstart = NULL; - lastnote = NULL; - current = current->getNextToken(); - continue; + printScoreEncodedText(out, ""); + printDiatonicPitchName(out, topquartile, 0); + out << ": top quartile note"; + if (voicestring.size() > 0) { + out << " of " << voicestring << "\'s range"; + } + printScoreEncodedText(out, "\n"); } - if (current->isRest()) { - current = current->getNextToken(); - continue; + out << "1 " << staff << " " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; } - if (current->isNote()) { - lastnote = current; + out << " 0 0 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); } - if (pstart && current->isNote() && (current->find(";") != std::string::npos)) { - // fermata at end of phrase. - dur = current->getDurationFromStart() + current->getDuration() - - pstart->getDurationFromStart(); - ss.str(""); - ss.clear(); - ss << dur.getFloat(); - m_psum[index] += dur; - m_pcount[index]++; - m_results[index][pstart->getLineIndex()] = ss.str(); - if (m_markQ) { - current->setText(current->getText() + "}"); - } - current = current->getNextToken(); - pstart = NULL; - continue; + } + + // print bottom quartile + if (m_quartileQ) { + int bottomquartile = getBottomQuartile(voiceInfo.midibins); + if (m_diatonicQ) { + bottomquartile = Convert::base7ToBase12(bottomquartile); } - if (current->isNote() && pstart == NULL) { - pstart = current; - if (m_markQ) { - current->setText("{" + current->getText()); - } + staff = getStaffBase7(bottomquartile); + vpos = getVpos(bottomquartile); + vvpos = median7 - bottomquartile + 1; + if (vvpos <= 2) { + offset = goffset + toffset; + } else { + offset = goffset; } - current = current->getNextToken(); - } - if (pstart) { - dur = start->getOwner()->getOwner()->getScoreDuration() - - pstart->getDurationFromStart(); - ss.str(""); - ss.clear(); - ss << dur.getFloat(); - m_psum[index] += dur; - m_pcount[index]++; - m_results[index][pstart->getLineIndex()] = ss.str(); - if (m_markQ && lastnote) { - lastnote->setText(lastnote->getText() + "}"); + vvpos = bottomquartile - mindiatonic + 1; + if (vvpos <= 2) { + offset = goffset - toffset; + } + if (m_hoverQ) { + if (m_defineQ) { + out << "SVG "; + } else { + out << "t 1 1\n"; + out << SVGTAG; + } + printScoreEncodedText(out, ""); + printDiatonicPitchName(out, bottomquartile, 0); + out << ": bottom quartile note"; + if (voicestring.size() > 0) { + out << " of " << voicestring << "\'s range"; + } + printScoreEncodedText(out, "\n"); + } + out << "1.0 " << staff << ".0 " << voiceInfo.hpos << " "; + if (vpos > 0) { + out << vpos + 100; + } else { + out << vpos - 100; + } + out << " 0 0 4 0 0 " << offset << "\n"; + if (m_hoverQ) { + SVGTEXT(out, ""); } } + */ + } -/////////////////////////////// +////////////////////////////// // -// Tool_phrase::analyzeSpineByPhrase -- +// Tool_prange::printDiatonicPitchName -- // -void Tool_phrase::analyzeSpineByPhrase(int index) { - HTp start = m_starts[index]; - HTp current = start; - HTp pstart = NULL; // phrase start; - HumNum dur; - stringstream ss; - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->find("{") != std::string::npos) { - pstart = current; - current = current->getNextToken(); - continue; - } - if (current->find("}") != std::string::npos) { - if (pstart) { - dur = current->getDurationFromStart() + current->getDuration() - - pstart->getDurationFromStart(); - ss.str(""); - ss.clear(); - ss << dur.getFloat(); - m_psum[index] += dur; - m_pcount[index]++; - m_results[index][pstart->getLineIndex()] = ss.str(); - } - current = current->getNextToken(); - continue; - } - current = current->getNextToken(); +void Tool_prange::printDiatonicPitchName(ostream& out, int base7, int acc) { + out << getDiatonicPitchName(base7, acc); +} + + + +////////////////////////////// +// +// Tool_prange::getDiatonicPitchName -- +// + +string Tool_prange::getDiatonicPitchName(int base7, int acc) { + string output; + int dpc = base7 % 7; + char letter = (dpc + 2) % 7 + 'A'; + output += letter; + switch (acc) { + case -1: output += "♭"; break; + case +1: output += "♯"; break; + case -2: output += "𝄫"; break; + case +2: output += "𝄪"; break; } + int octave = base7 / 7; + output += to_string(octave); + return output; } ////////////////////////////// // -// Tool_phrase::removePhraseMarks -- Remvoe { and } characters from **kern data. +// Tool_prange::printHtmlStringEncodeSimple -- // -void Tool_phrase::removePhraseMarks(HTp start) { - HTp current = start; +void Tool_prange::printHtmlStringEncodeSimple(ostream& out, const string& strang) { + string newstring = strang; HumRegex hre; - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->find("{") != std::string::npos) { - string data = *current; - hre.replaceDestructive(data, "", "\\{", "g"); - current->setText(data); - } - if (current->find("}") != std::string::npos) { - string data = *current; - hre.replaceDestructive(data, "", "\\}", "g"); - current->setText(data); - } - current = current->getNextToken(); - } + hre.replaceDestructive(newstring, "&", "&", "g"); + hre.replaceDestructive(newstring, "<", "<", "g"); + hre.replaceDestructive(newstring, ">", "<", "g"); + out << newstring; } ////////////////////////////// // -// Tool_phrase::hasPhraseMarks -- True if **kern data spine (primary layer), has -// "{" (or "}", but this is not checked) characters (phrase markers). +// Tool_prange::getNoteTitle -- return the title of the histogram bar. +// value = duration or count of notes +// diatonic = base7 value for note +// acc = accidental for diatonic note. // -bool Tool_phrase::hasPhraseMarks(HTp start) { - HTp current = start; - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; +string Tool_prange::getNoteTitle(double value, int diatonic, int acc) { + stringstream output; + output << ""; + if (m_durationQ) { + output << value / 8.0; + if (value/8.0 == 1.0) { + output << " long on "; + } else { + output << " longs on "; } - if (current->find("{") != std::string::npos) { - return true; + output << getDiatonicPitchName(diatonic, acc); + } else { + output << value; + output << " "; + output << getDiatonicPitchName(diatonic, acc); + if (value != 1.0) { + output << "s"; } - current = current->getNextToken(); } - return false; + output << ""; + return output.str(); } - - -///////////////////////////////// +////////////////////////////// // -// Tool_pline::Tool_pline -- Set the recognized options for the tool. +// Tool_prange::getDiatonicInterval -- // -Tool_pline::Tool_pline(void) { - define("c|color=b", "color poetic lines (currently only by notes)"); - define("o|overlap=b", "do overlap analysis/markup"); +int Tool_prange::getDiatonicInterval(int note1, int note2) { + int vpos1 = getVpos(note1); + int vpos2 = getVpos(note2); + return abs(vpos1 - vpos2) + 1; } -///////////////////////////////// +////////////////////////////// // -// Tool_pline::run -- Do the main work of the tool. +// Tool_prange::getTopQuartile -- // -bool Tool_pline::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i& midibins) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + + double cumsum = 0.0; + int i; + for (i=midibins.size()-1; i>=0; i--) { + if (midibins[i] <= 0.0) { + continue; + } + cumsum += midibins[i]/sum; + if (cumsum >= 0.25) { + return i; + } } - return status; + + return -1; } -bool Tool_pline::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} +////////////////////////////// +// +// Tool_prange::getBottomQuartile -- +// -bool Tool_pline::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} +int Tool_prange::getBottomQuartile(vector& midibins) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + double cumsum = 0.0; + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + cumsum += midibins[i]/sum; + if (cumsum >= 0.25) { + return i; + } + } -bool Tool_pline::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; + return -1; } + ////////////////////////////// // -// Tool_pline::initialize -- +// Tool_prange::getMaxValue -- // -void Tool_pline::initialize(void) { - m_colors.resize(14); +double Tool_prange::getMaxValue(vector>& bins) { + double maxi = 0; + for (int i=1; i<(int)bins.size(); i++) { + if (bins.at(i).at(0) > bins.at(maxi).at(0)) { + maxi = i; + } + } + return bins.at(maxi).at(0); +} - m_colors[0] = "red"; // red - m_colors[1] = "darkorange"; // orange - m_colors[2] = "gold"; // yellow - m_colors[3] = "limegreen"; // green - m_colors[4] = "skyblue"; // light blue - m_colors[5] = "mediumblue"; // dark blue - m_colors[6] = "purple"; // purple - m_colors[7] = "darkred"; // red - m_colors[8] = "lightsalmon"; // orange - m_colors[9] = "darkgoldenrod"; // yellow - m_colors[10] = "olivedrab"; // green - m_colors[11] = "darkturquoise"; // light blue - m_colors[12] = "darkblue"; // dark blue - m_colors[13] = "indigo"; // purple - // lighter colors for staff highlighting: - //m_colors[0] = "#ffaaaa"; // red - //m_colors[1] = "#ffbb00"; // orange - //m_colors[2] = "#eeee00"; // yellow - //m_colors[3] = "#99cc01"; // green - //m_colors[4] = "#bbddff"; // light blue - //m_colors[5] = "#88aaff"; // dark blue - //m_colors[6] = "#cc88ff"; // purple +////////////////////////////// +// +// Tool_prange::getVpos == return the position on the staff given the diatonic pitch. +// and the staff. 1=bass, 2=treble. +// 3 = bottom line of clef, 0 = space below first ledger line. +// - m_colorQ = getBoolean("color"); - m_colorQ = true; // default behavior for now. +double Tool_prange::getVpos(double base7) { + double output = 0; + if (base7 < 4 * 7) { + // bass clef + output = base7 - (1 + 2*7); // D2 + } else { + // treble clef + output = base7 - (6 + 3*7); // B3 + } + return output; } ////////////////////////////// // -// Tool_pline::processFile -- +// Tool_prange::getStaffBase7 -- return 1 if less than middle C; otherwise return 2. // -void Tool_pline::processFile(HumdrumFile& infile) { - getPlineInterpretations(infile, m_ptokens); - fillLineInfo(infile, m_lineInfo); - if (m_colorQ) { - plineToColor(infile, m_ptokens); - } - infile.createLinesFromTokens(); - m_humdrum_text << infile; - if (m_colorQ) { - m_humdrum_text << "!!!RDF**kern: 😀 = marked note, color=black" << endl; +int Tool_prange::getStaffBase7(int base7) { + if (base7 < 4 * 7) { + return 1; + } else { + return 2; } } - ////////////////////////////// // -// Tool_pline::markRests -- +// Tool_prange::getMaxDiatonicIndex -- return the highest non-zero content. // -void Tool_pline::markRests(HumdrumFile& infile) { - vector spinestops; - infile.getSpineStopList(spinestops); - for (int i=0; i<(int)spinestops.size(); i++) { - if (!spinestops[i]->isKern()) { - continue; +int Tool_prange::getMaxDiatonicIndex(vector>& diatonic) { + for (int i=diatonic.size()-1; i>=0; i--) { + if (diatonic.at(i).at(0) != 0.0) { + return i; } - markSpineRests(spinestops[i]); } + return -1; } + ////////////////////////////// // -// Tool_pline::markSpineRests -- +// Tool_prange::getMinDiatonicIndex -- return the lowest non-zero content. // -void Tool_pline::markSpineRests(HTp spineStop) { - string marker = "😀"; - int track = spineStop->getTrack(); - int lastValue = -1; - HTp current = spineStop->getPreviousToken(); - int line; - int cvalue; - while (current) { - if (!current->isData()) { - current = current->getPreviousToken(); - continue; - } - if (current->isNull()) { - current = current->getPreviousToken(); - continue; - } - - line = current->getLineIndex(); - cvalue = m_lineInfo.at(line).at(track); - - if (current->isRest() && (cvalue != lastValue)) { - string text = *current; - text += marker; - current->setText(text); - } else { - lastValue = cvalue; - string text = *current; - text += "@" + to_string(cvalue); - current->setText(text); +int Tool_prange::getMinDiatonicIndex(vector>& diatonic) { + for (int i=0; i<(int)diatonic.size(); i++) { + if (diatonic.at(i).at(0) != 0.0) { + return i; } - current = current->getPreviousToken(); } + return -1; } ////////////////////////////// // -// Tool_pline::fillLineInfo -- +// Tool_prange::getMinDiatonicAcc -- return the lowest accidental. // -void Tool_pline::fillLineInfo(HumdrumFile& infile, vector>& lineinfo) { - lineinfo.clear(); - lineinfo.resize(infile.getLineCount()); - int maxtrack = infile.getMaxTrack(); - HumRegex hre; - for (int i=0; igetTrack(); - lineinfo[i][track] = digit; - } - } - } - - for (int i=1; i<(int)lineinfo.size() - 1; i++) { - for (int j=1; j<=maxtrack; j++) { - if (lineinfo.at(i).at(j)) { - continue; - } else { - lineinfo.at(i).at(j) = lineinfo.at(i-1).at(j); - } +int Tool_prange::getMinDiatonicAcc(vector>& diatonic, int index) { + for (int i=1; i<(int)diatonic.at(index).size(); i++) { + if (diatonic.at(index).at(i) != 0.0) { + return i; } } - - // for (int i=0; i<(int)lineinfo.size() - 1; i++) { - // for (int j=1; j<=maxtrack; j++) { - // cerr << lineinfo[i][j] << "\t"; - // } - // cerr << endl; - // } - + return -1; } ////////////////////////////// // -// Tool_pline::plineToColor -- +// Tool_prange::getMaxDiatonicAcc -- return the highest accidental. // -void Tool_pline::plineToColor(HumdrumFile& infile, vector& tokens) { - HumRegex hre; - markRests(infile); - for (int i=0; i<(int)tokens.size(); i++) { - if (!hre.search(tokens[i], "^\\*pline:\\s*(\\d+)")) { - continue; +int Tool_prange::getMaxDiatonicAcc(vector>& diatonic, int index) { + for (int i=(int)diatonic.at(index).size() - 1; i>0; i--) { + if (diatonic.at(index).at(i) != 0.0) { + return i; } - int lineNum = hre.getMatchInt(1); - int colorIndex = (lineNum - 1) % m_colors.size(); - string color = m_colors.at(colorIndex); - string text = "*color:"; - text += color; - tokens[i]->setText(text); } + return -1; } ////////////////////////////// // -// Tool_pline::getPlineInterpretations -- +// Tool_prange::prepareRefmap -- // -void Tool_pline::getPlineInterpretations(HumdrumFile& infile, vector& tokens) { +void Tool_prange::prepareRefmap(HumdrumFile& infile) { + vector refrecords = infile.getGlobalReferenceRecords(); + m_refmap.clear(); HumRegex hre; - for (int i=0; iisKern()) { - continue; - } - if (hre.search(token, "^\\*pline:\\s*(\\d+)")) { - tokens.push_back(token); + for (int i = (int)refrecords.size()-1; i>=0; i--) { + string key = refrecords[i]->getReferenceKey(); + string value = refrecords[i]->getReferenceValue(); + m_refmap[key] = value; + if (key.find("@") != string::npos) { + // create default value + hre.replaceDestructive(key, "", "@.*"); + if (m_refmap[key].empty()) { + m_refmap[key] = value; } } } -} - - - + // fill in @{} templates (mostly for !!!title:) + int counter = 0; // prevent recursions + for (auto& entry : m_refmap) { + if (entry.second.find("@") != string::npos) { + while (hre.search(entry.second, "@\\{(.*?)\\}")) { + string key = hre.getMatch(1); + string value = m_refmap[key]; + hre.replaceDestructive(entry.second, value, "@\\{" + key + "\\}", "g"); + counter++; + if (counter > 1000) { + break; + } + } + } -///////////////////////////////// -// -// Tool_gridtest::Tool_pnum -- Set the recognized options for the tool. -// + } -Tool_pnum::Tool_pnum(void) { - define("b|base=i:midi", "numeric base of pitch to extract"); - define("D|no-duration=b", "do not include duration"); - define("c|pitch-class=b", "give numeric pitch-class rather than pitch"); - define("o|octave=b", "give octave rather than pitch"); - define("r|rest=s:0", "representation string for rests"); - define("R|no-rests=b", "do not include rests in conversion"); - define("x|attacks-only=b", "only mark lines with note attacks"); + // prepare title + if (m_refmap["title"].empty()) { + m_refmap["title"] = m_refmap["OTL"]; + } } -/////////////////////////////// +////////////////////////////// // -// Tool_pnum::run -- Primary interfaces to the tool. +// Tool_prange::getTitle -- // -bool Tool_pnum::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i 1000) { + break; + } + } + } + if (!titlestring.empty()) { + titlestring = "_00" + titlestring; + } + } else { + titlestring = m_refmap["title"]; + if (!titlestring.empty()) { + titlestring = "_00" + titlestring; + } } - return status; + return titlestring; } -bool Tool_pnum::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - return run(infile, out); -} +////////////////////////////// +// +// Tool_prange::clearHistograms -- +// -bool Tool_pnum::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - out << infile; - return status; +void Tool_prange::clearHistograms(vector >& bins, int start) { + int i; + for (i=start; i<(int)bins.size(); i++) { + bins[i].resize(40*11); + fill(bins[i].begin(), bins[i].end(), 0.0); + // bins[i].allowGrowth(0); + } + for (int i=0; i<(int)bins.size(); i++) { + if (bins[i].size() == 0) { + bins[i].resize(40*11); + fill(bins[i].begin(), bins[i].end(), 0.0); + } + } } -bool Tool_pnum::run(HumdrumFile& infile) { - initialize(infile); - processFile(infile); - infile.createLinesFromTokens(); - return true; -} - ////////////////////////////// // -// Tool_pnum::initialize -- +// Tool_prange::printAnalysis -- // -void Tool_pnum::initialize(HumdrumFile& infile) { - m_midiQ = false; - if (getString("base") == "midi") { - m_base = 12; - m_midiQ = true; - } else { - // check base for valid numbers, but for now default to 12 if unknown - m_base = getInteger("base"); +void Tool_prange::printAnalysis(ostream& out, vector& midibins) { + if (m_percentileQ) { + printPercentile(out, midibins, m_percentile); + return; + } else if (m_rangeQ) { + double notesinrange = countNotesInRange(midibins, m_rangeL, m_rangeH); + out << notesinrange << endl; + return; } - m_durationQ = !getBoolean("no-duration"); - m_classQ = getBoolean("pitch-class"); - m_octaveQ = getBoolean("octave"); - m_attacksQ = getBoolean("attacks-only"); - m_rest = getString("rest"); - m_restQ = !getBoolean("no-rests"); -} + int i; + double normval = 1.0; + + // print the pitch histogram + + double fracL = 0.0; + double fracH = 0.0; + double fracA = 0.0; + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + if (m_normQ) { + normval = sum; + } + double runningtotal = 0.0; + out << "**keyno\t"; + if (m_pitchQ) { + out << "**pitch"; + } else { + out << "**kern"; + } + out << "\t**count"; + if (m_addFractionQ) { + out << "\t**fracL"; + out << "\t**fracA"; + out << "\t**fracH"; + } + out << "\n"; -////////////////////////////// -// -// Tool_pnum::processFile -- -// -void Tool_pnum::processFile(HumdrumFile& infile) { - vector kex; + int base12; - for (int i=0; iisKern()) { + if (!m_reverseQ) { + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { continue; } - if (*token == "**kern") { - kex.push_back(token); - continue; + if (m_diatonicQ) { + base12 = Convert::base7ToBase12(i); + } else { + base12 = i; } - if (!token->isData()) { - continue; + out << base12 << "\t"; + if (m_pitchQ) { + out << Convert::base12ToPitch(base12); + } else { + out << Convert::base12ToKern(base12); } - if (token->isNull()) { + out << "\t"; + out << midibins[i] / normval; + fracL = runningtotal/sum; + runningtotal += midibins[i]; + fracH = runningtotal/sum; + fracA = (fracH + fracL)/2.0; + fracL = (int)(fracL * 10000.0 + 0.5)/10000.0; + fracH = (int)(fracH * 10000.0 + 0.5)/10000.0; + fracA = (int)(fracA * 10000.0 + 0.5)/10000.0; + if (m_addFractionQ) { + out << "\t" << fracL; + out << "\t" << fracA; + out << "\t" << fracH; + } + out << "\n"; + } + } else { + for (i=(int)midibins.size()-1; i>=0; i--) { + if (midibins[i] <= 0.0) { continue; } - convertTokenToBase(token); + if (m_diatonicQ) { + base12 = Convert::base7ToBase12(i); + } else { + base12 = i; + } + out << base12 << "\t"; + if (m_pitchQ) { + out << Convert::base12ToPitch(base12); + } else { + out << Convert::base12ToKern(base12); + } + out << "\t"; + out << midibins[i] / normval; + fracL = runningtotal/sum; + runningtotal += midibins[i]; + fracH = runningtotal/sum; + fracA = (fracH + fracL)/2.0; + fracL = (int)(fracL * 10000.0 + 0.5)/10000.0; + fracH = (int)(fracH * 10000.0 + 0.5)/10000.0; + fracA = (int)(fracA * 10000.0 + 0.5)/10000.0; + if (m_addFractionQ) { + out << "\t" << fracL; + out << "\t" << fracA; + out << "\t" << fracH; + } + out << "\n"; } } - string newex; - for (int i=0; i<(int)kex.size(); i++) { - if (m_midiQ) { - newex = "**pmid"; - } else { - newex = "**b" + to_string(m_base); - } - kex[i]->setText(newex); + out << "*-\t*-\t*-"; + if (m_addFractionQ) { + out << "\t*-"; + out << "\t*-"; + out << "\t*-"; } -} - + out << "\n"; + out << "!!tessitura:\t" << getTessitura(midibins) << " semitones\n"; -////////////////////////////// -// -// Tool_pnum::convertTokenToBase -- -// + double mean = getMean12(midibins); + if (m_diatonicQ && (mean > 0)) { + mean = Convert::base7ToBase12(mean); + } + out << "!!mean:\t\t" << mean; + out << " ("; + if (mean < 0) { + out << "unpitched"; + } else { + out << Convert::base12ToKern(int(mean+0.5)); + } + out << ")" << "\n"; -void Tool_pnum::convertTokenToBase(HTp token) { - string output; - int scount = token->getSubtokenCount(); - for (int i=0; igetSubtoken(i); - output += convertSubtokenToBase(subtok); - if (i < scount - 1) { - output += " "; - } + int median12 = getMedian12(midibins); + out << "!!median:\t" << median12; + out << " ("; + if (median12 < 0) { + out << "unpitched"; + } else { + out << Convert::base12ToKern(median12); } - token->setText(output); + out << ")" << "\n"; + } ////////////////////////////// // -// Tool_pnum::convertSubtokenToBase -- +// Tool_prange::getMedian12 -- return the pitch on which half of pitches are above +// and half are below. // -string Tool_pnum::convertSubtokenToBase(const string& text) { - int pitch = 0; - if (text.find("r") == string::npos) { - switch (m_base) { - case 7: - pitch = Convert::kernToBase7(text); - break; - case 40: - pitch = Convert::kernToBase40(text); - break; - default: - pitch = Convert::kernToBase12(text); +int Tool_prange::getMedian12(vector& midibins) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + + double cumsum = 0.0; + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; } - } else if (!m_restQ) { - return "."; - } - string recip; - if (m_durationQ) { - HumRegex hre; - if (hre.search(text, "(\\d+%?\\d*\\.*)")) { - recip = hre.getMatch(1); + cumsum += midibins[i]/sum; + if (cumsum >= 0.50) { + return i; } } - string output; + return -1000; +} - int pc = pitch % m_base; - int oct = pitch / m_base; - if (m_midiQ) { - // MIDI numbers use 5 for middle-C octave. - pitch += 12; - } - int tie = 1; - if (text.find("_") != string::npos) { - tie = -1; - } - if (text.find("]") != string::npos) { - tie = -1; - } - pitch *= tie; - if (m_attacksQ && pitch < 0) { - return "."; - } +////////////////////////////// +// +// Tool_prange::getMean12 -- return the interval between the highest and lowest +// pitch in terms if semitones. +// - if (m_durationQ) { - output += recip; - output += "/"; - } +double Tool_prange::getMean12(vector& midibins) { + double top = 0.0; + double bottom = 0.0; - if (text.find("r") != string::npos) { - output += m_rest; - } else { - if (!m_octaveQ && !m_classQ) { - output += to_string(pitch); - } else { - if (m_classQ) { - if (pitch < 0) { - output += "-"; - } - output += to_string(pc); - } - if (m_classQ && m_octaveQ) { - output += ":"; - } - if (m_octaveQ) { - output += to_string(oct); - } + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; } + top += midibins[i] * i; + bottom += midibins[i]; + } - return output; + if (bottom == 0) { + return -1000; + } + return top / bottom; } +////////////////////////////// +// +// Tool_prange::getTessitura -- return the interval between the highest and lowest +// pitch in terms if semitones. +// +int Tool_prange::getTessitura(vector& midibins) { + int minn = -1000; + int maxx = -1000; + int i; -#define OBJTAB "\t\t\t\t\t\t" -#define SVGTAG "_99%svg%"; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0.0) { + continue; + } + if (minn < 0) { + minn = i; + } + if (maxx < 0) { + maxx = i; + } + if (minn > i) { + minn = i; + } + if (maxx < i) { + maxx = i; + } + } + if (m_diatonicQ) { + maxx = Convert::base7ToBase12(maxx); + minn = Convert::base7ToBase12(minn); + } + + return maxx - minn + 1; +} -#define SVGTEXT(out, text) \ - if (m_defineQ) { \ - out << "SVG "; \ - } else { \ - out << "t 1 1\n"; \ - out << SVGTAG; \ - } \ - printScoreEncodedText((out), (text)); \ - out << "\n"; ////////////////////////////// // -// _VoiceInfo::_VoiceInfo -- +// Tool_prange::countNotesInRange -- // -_VoiceInfo::_VoiceInfo(void) { - clear(); +double Tool_prange::countNotesInRange(vector& midibins, int low, int high) { + int i; + double sum = 0; + for (i=low; i<=high; i++) { + sum += midibins[i]; + } + return sum; } ////////////////////////////// // -// _VoiceInfo::clear -- +// Tool_prange::printPercentile -- // -void _VoiceInfo::clear(void) { - name = ""; - abbr = ""; - midibins.resize(128); - fill(midibins.begin(), midibins.end(), 0.0); - diatonic.resize(7 * 12); - for (int i=0; i<(int)diatonic.size(); i++) { - diatonic[i].resize(6); - fill(diatonic[i].begin(), diatonic[i].end(), 0.0); +void Tool_prange::printPercentile(ostream& out, vector& midibins, double m_percentile) { + double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + double runningtotal = 0.0; + int i; + for (i=0; i<(int)midibins.size(); i++) { + if (midibins[i] <= 0) { + continue; + } + runningtotal += midibins[i] / sum; + if (runningtotal >= m_percentile) { + out << i << endl; + return; + } } - track = -1; - kernQ = false; - diafinal.clear(); - accfinal.clear(); - namfinal.clear(); - index = -1; + + out << "unknown" << endl; } + ////////////////////////////// // -// _VoiceInfo::print -- +// Tool_prange::getRange -- // -ostream& _VoiceInfo::print(ostream& out) { - out << "==================================" << endl; - out << "track: " << track << endl; - out << " name: " << name << endl; - out << " abbr: " << abbr << endl; - out << " kern: " << kernQ << endl; - out << " final:"; - for (int i=0; i<(int)diafinal.size(); i++) { - out << " " << diafinal.at(i) << "/" << accfinal.at(i); +void Tool_prange::getRange(int& rangeL, int& rangeH, const string& rangestring) { + rangeL = -1; rangeH = -1; + if (rangestring.empty()) { + return; } - out << endl; - out << " midi: "; - for (int i=0; i<(int)midibins.size(); i++) { - if (midibins.at(i) > 0.0) { - out << " " << i << ":" << midibins.at(i); + int length = (int)rangestring.length(); + char* buffer = new char[length+1]; + strcpy(buffer, rangestring.c_str()); + char* ptr; + if (std::isdigit(buffer[0])) { + ptr = strtok(buffer, " \t\n:-"); + sscanf(ptr, "%d", &rangeL); + ptr = strtok(NULL, " \t\n:-"); + if (ptr != NULL) { + sscanf(ptr, "%d", &rangeH); } - } - out << endl; - out << " diat: "; - for (int i=0; i<(int)diatonic.size(); i++) { - if (diatonic.at(i).at(0) > 0.0) { - out << " " << i << ":" << diatonic.at(i).at(0); + } else { + ptr = strtok(buffer, " :"); + if (ptr != NULL) { + rangeL = Convert::kernToMidiNoteNumber(ptr); + ptr = strtok(NULL, " :"); + if (ptr != NULL) { + rangeH = Convert::kernToMidiNoteNumber(ptr); + } } } - out << endl; - out << "==================================" << endl; - return out; + + if (rangeH < 0) { + rangeH = rangeL; + } + + if (rangeL < 0) { rangeL = 0; } + if (rangeH < 0) { rangeH = 0; } + if (rangeL > 127) { rangeL = 127; } + if (rangeH > 127) { rangeH = 127; } + if (rangeL > rangeH) { + int temp = rangeL; + rangeL = rangeH; + rangeH = temp; + } + } + + ///////////////////////////////// // -// Tool_prange::Tool_prange -- Set the recognized options for the tool. +// Tool_gridtest::Tool_recip -- Set the recognized options for the tool. // -Tool_prange::Tool_prange(void) { - - define("A|acc|color-accidentals=b", "add color to accidentals in histogram"); - define("D|diatonic=b", "diatonic counts ignore chormatic alteration"); - define("K|no-key=b", "do not display key signature"); - define("N|norm=b", "normalize pitch counts"); - define("S|score=b", "convert range info to SCORE"); - define("T|no-title=b", "do not display a title"); - define("a|all=b", "generate all-voice analysis"); - define("c|range|count=s:60-71", "count notes in a particular MIDI note number range (inclusive)"); - define("debug=b", "trace input parsing"); - define("d|duration=b", "weight pitches by duration"); - define("e|embed=b", "embed SCORE data in input Humdrum data"); - define("fill=b", "change color of fill only"); - define("finalis|final|last=b", "include finalis note by voice"); - define("f|fraction=b", "display histogram fractions"); - define("h|hover=b", "include svg hover capabilities"); - define("i|instrument=b", "categorize multiple inputs by instrument"); - define("j|jrp=b", "set options for JRP style"); - define("l|local|local-maximum|local-maxima=b", "use maximum values by voice rather than all voices"); - define("no-define=b", "do not use defines in output SCORE data"); - define("pitch=b", "display pitch info in **pitch format"); - define("print=b", "count printed notes rather than sounding"); - define("p|percentile=d:0.0", "display the xth percentile pitch"); - define("q|quartile=b", "display quartile notes"); - define("r|reverse=b", "reverse list of notes in analysis from high to low"); - define("x|extrema=b", "highlight extrema notes in each part"); - define("sx|scorexml|score-xml|ScoreXML|scoreXML=b", "output ScoreXML format"); - define("title=s:", "title for SCORE display"); - +Tool_recip::Tool_recip(void) { + define("c|composite=b", "do composite rhythm analysis"); + define("a|append=b", "append composite analysis to input"); + define("p|prepend=b", "prepend composite analysis to input"); + define("r|replace=b", "replace **kern data with **recip data"); + define("x|attacks-only=b", "only mark lines with note attacks"); + define("G|ignore-grace-notes=b", "ignore grace notes"); + define("k|kern-spine=i:1", "analyze only given kern spine"); + define("K|all-spines=b", "analyze each kern spine separately"); + define("e|exinterp=s:**recip", "use the given exinterp for data output"); + define("n|kern-pitch=s:e", "note to add for '-e kern' option"); + define("kern=b", "equivalent to '-e kern' option"); } -///////////////////////////////// + +/////////////////////////////// // -// Tool_prange::run -- Do the main work of the tool. +// Tool_recip::run -- Primary interfaces to the tool. // -bool Tool_prange::run(HumdrumFileSet& infiles) { +bool Tool_recip::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i 1) { - m_percentile = m_percentile / 100.0; +void Tool_recip::insertAnalysisSpines(HumdrumFile& infile, HumdrumFile& cfile) { + for (int i=0; i=0; k--) { + int fcount = infile[i].getFieldCount(); + int ktrack = m_kernspines[k]->getTrack(); + int insertj = -1; + for (int j=fcount-1; j>=0; j--) { + if (!infile.token(i, j)->isKern()) { + continue; + } + int track = infile.token(i, j)->getTrack(); + if (track != ktrack) { + continue; + } + if (insertj < 0) { + insertj = j; + } + infile[i].appendToken(insertj, cfile.token(i, j)->getText()); + // infile.token(i, insertj+1)->setTrack(remapping[k]); + } + } } - - #ifdef __EMSCRIPTEN__ - // Default styling for JavaScript version of program: - m_accQ = !getBoolean("color-accidentals"); - m_scoreQ = !getBoolean("score"); - m_embedQ = !getBoolean("embed"); - m_hoverQ = !getBoolean("hover"); - m_notitleQ = !getBoolean("no-title"); - #endif } ////////////////////////////// // -// Tool_prange::processFile -- +// Tool_recip::doCompositeAnalysis -- // -void Tool_prange::processFile(HumdrumFile& infile) { - prepareRefmap(infile); - vector<_VoiceInfo> voiceInfo; - infile.fillMidiInfo(m_trackMidi); - getVoiceInfo(voiceInfo, infile); - fillHistograms(voiceInfo, infile); +void Tool_recip::doCompositeAnalysis(HumdrumFile& infile) { - if (m_debugQ) { - for (int i=0; i<(int)voiceInfo.size(); i++) { - voiceInfo[i].print(cerr); - } + // Calculate composite rhythm **recip spine: + + vector composite(infile.getLineCount()); + for (int i=0; i<(int)composite.size(); i++) { + composite[i] = infile[i].getDuration(); } - if (m_scoreQ) { - stringstream scoreout; - printScoreFile(scoreout, voiceInfo, infile); - if (m_embedQ) { - if (m_extremaQ) { - doExtremaMarkup(infile); - } - m_humdrum_text << infile; - printEmbeddedScore(m_humdrum_text, scoreout, infile); - } else { - if (m_extremaQ) { - doExtremaMarkup(infile); - } - m_humdrum_text << scoreout.str(); + int kernQ = false; + if (m_exinterp.find("kern") != std::string::npos) { + kernQ = true; +// cerr << "KERN ON" << endl; + } + + // convert durations to **recip strings + vector recips(composite.size()); + for (int i=0; i<(int)recips.size(); i++) { + if ((!m_graceQ) && (composite[i] == 0)) { + continue; + } + recips[i] = Convert::durationToRecip(composite[i]); + if (kernQ) { + recips[i] += m_kernpitch; +// cerr << "ADDING PITCH " << m_kernpitch << endl; } + } + + if (getBoolean("append")) { + infile.appendDataSpine(recips, "", m_exinterp); + return; + } else if (getBoolean("prepend")) { + infile.prependDataSpine(recips, "", m_exinterp); + return; } else { - printAnalysis(m_humdrum_text, voiceInfo[0].midibins); + infile.prependDataSpine(recips, "", m_exinterp); + infile.printFieldIndex(0, m_humdrum_text); + infile.clear(); + infile.readString(m_humdrum_text.str()); } } @@ -115600,174 +120248,187 @@ void Tool_prange::processFile(HumdrumFile& infile) { ////////////////////////////// // -// Tool_prange::doExtremaMarkup -- Mark highest and lowest note -// in each **kern spine. -// +// Tool_recip::replaceKernWithRecip -- // -void Tool_prange::doExtremaMarkup(HumdrumFile& infile) { - bool highQ = false; - bool lowQ = false; - for (int i=0; i<(int)m_trackMidi.size(); i++) { - int maxindex = -1; - int minindex = -1; - - for (int j=(int)m_trackMidi[i].size()-1; j>=0; j--) { - if (m_trackMidi[i][j].empty()) { +void Tool_recip::replaceKernWithRecip(HumdrumFile& infile) { + vector kspines = infile.getKernSpineStartList(); + HumRegex hre; + string expression = "[^q\\d.%\\]\\[]+"; + for (int i=0; iisKern()) { + continue; + } + HTp etok = infile.getStrandEnd(i); + HTp tok = stok; + while (tok && (tok != etok)) { + if (!tok->isData()) { + tok = tok->getNextToken(); continue; } - if (maxindex < 0) { - maxindex = j; - break; - } - } - - for (int j=1; j<(int)m_trackMidi[i].size(); j++) { - if (m_trackMidi[i][j].empty()) { + if (tok->isNull()) { + tok = tok->getNextToken(); continue; } - if (minindex < 0) { - minindex = j; - break; + if (tok->find('q') != string::npos) { + if (m_graceQ) { + tok->setText("q"); + } else { + tok->setText("."); + } + } else { + hre.replaceDestructive(*tok, "", expression, "g"); } + tok = tok->getNextToken(); } + } - if ((maxindex < 0) || (minindex < 0)) { - continue; - } - applyMarkup(m_trackMidi[i][maxindex], m_highMark); - applyMarkup(m_trackMidi[i][minindex], m_lowMark); - highQ = true; - lowQ = true; + for (int i=0; i<(int)kspines.size(); i++) { + kspines[i]->setText(m_exinterp); } - if (highQ) { - string highRdf = "!!!RDF**kern: " + m_highMark + " = marked note, color=\"hotpink\", highest note"; - infile.appendLine(highRdf); + +} + + + + +////////////////////////////// +// +// Tool_recip::initialize -- +// + +void Tool_recip::initialize(HumdrumFile& infile) { + m_kernspines = infile.getKernSpineStartList(); + m_graceQ = !getBoolean("ignore-grace-notes"); + + m_exinterp = getString("exinterp"); + if (m_exinterp.empty()) { + m_exinterp = "**recip"; + } else if (m_exinterp[0] != '*') { + m_exinterp.insert(0, "*"); } - if (lowQ) { - string lowRdf = "!!!RDF**kern: " + m_lowMark + " = marked note, color=\"limegreen\", lowest note"; - infile.appendLine(lowRdf); + if (m_exinterp[1] != '*') { + m_exinterp.insert(0, "*"); } - if (highQ || lowQ) { - infile.createLinesFromTokens(); + + m_kernpitch = getString("kern-pitch"); + + if (getBoolean("kern")) { + m_exinterp = "**kern"; } + } -////////////////////////////// + + +///////////////////////////////// // -// Tool_prange::applyMarkup -- +// Tool_restfill::Tool_restfill -- Set the recognized options for the tool. // -void Tool_prange::applyMarkup(vector>& notelist, const string& mark) { - for (int i=0; i<(int)notelist.size(); i++) { - HTp token = notelist[i].first; - int subtoken = notelist[i].second; - int tokenCount = token->getSubtokenCount(); - if (tokenCount == 1) { - string text = *token; - text += mark; - token->setText(text); - } else { - string stok = token->getSubtoken(subtoken); - stok = mark + stok; - token->replaceSubtoken(subtoken, stok); - } - } +Tool_restfill::Tool_restfill(void) { + define("y|hidden-rests=b", "hide inserted rests"); + define("i|exinterp=s:kern", "type of spine to fill with rests"); } -////////////////////////////// +///////////////////////////////// // -// Tool_prange::printEmbeddedScore -- +// Tool_restfill::run -- Do the main work of the tool. // -void Tool_prange::printEmbeddedScore(ostream& out, stringstream& scoredata, HumdrumFile& infile) { - int id = getPrangeId(infile); +bool Tool_restfill::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i\n"; - out << "!!@@END: PREHTML\n"; - out << "!!@@BEGIN: SCORE\n"; - out << "!!@ID: prange-" << id << "\n"; - out << "!!@OUTPUTFORMAT: svg\n"; - out << "!!@CROP: yes\n"; - out << "!!@PADDING: 10\n"; - out << "!!@SCALING: 1.5\n"; - out << "!!@SVGFORMAT: yes\n"; - out << "!!@TRANSPARENT: yes\n"; - out << "!!@ANTIALIAS: no\n"; - out << "!!@EMBEDPMX: yes\n"; - out << "!!@ANNOTATE: no\n"; - out << "!!@CONTENTS:\n"; - string line; - while(getline(scoredata, line)) { - out << "!!" << line << endl; + +bool Tool_restfill::run(const string& indata, ostream& out) { + HumdrumFile infile; + infile.readStringNoRhythm(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } - out << "!!@@END: SCORE\n"; + return status; +} + + +bool Tool_restfill::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; + } + return status; +} + + +bool Tool_restfill::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + infile.createLinesFromTokens(); + return true; } ////////////////////////////// // -// Tool_prange::getPrangeId -- Find a line in this form -// ^!!@ID: prange-(\d+)$ -// and return $1+1. Searching backwards since the HTML section -// will likely be at the bottom. Assuming that the prange -// SVG images are stored in sequence, with the highest ID last -// in the file if there are more than one. +// Tool_restfill::initialize -- // -int Tool_prange::getPrangeId(HumdrumFile& infile) { - string search = "!!@ID: prange-"; - int length = (int)search.length(); - for (int i=infile.getLineCount() - 1; i>=0; i--) { - HTp token = infile.token(i, 0); - if (token->compare(0, length, search) == 0) { - HumRegex hre; - if (hre.search(token, "prange-(\\d+)")) { - return hre.getMatchInt(1) + 1; - } +void Tool_restfill::initialize(void) { + m_hiddenQ = getBoolean("hidden-rests"); + m_exinterp = getString("exinterp"); + if (m_exinterp.empty()) { + m_exinterp = "**kern"; + } + if (m_exinterp.compare(0, 2, "**") != 0) { + if (m_exinterp.compare(0, 1, "*") != 0) { + m_exinterp = "**" + m_exinterp; + } else { + m_exinterp = "*" + m_exinterp; } } - return 1; + } ////////////////////////////// // -// Tool_prange::mergeAllVoiceInfo -- +// Tool_restfill::processFile -- // -void Tool_prange::mergeAllVoiceInfo(vector<_VoiceInfo>& voiceInfo) { - voiceInfo.at(0).diafinal.clear(); - voiceInfo.at(0).accfinal.clear(); - - for (int i=1; i<(int)voiceInfo.size(); i++) { - if (!voiceInfo[i].kernQ) { - continue; - } - for (int j=0; j<(int)voiceInfo.at(i).diafinal.size(); j++) { - voiceInfo.at(0).diafinal.push_back(voiceInfo.at(i).diafinal.at(j)); - voiceInfo.at(0).accfinal.push_back(voiceInfo.at(i).accfinal.at(j)); - voiceInfo.at(0).namfinal.push_back(voiceInfo.at(i).name); - } +void Tool_restfill::processFile(HumdrumFile& infile) { - for (int j=0; j<(int)voiceInfo[i].midibins.size(); j++) { - voiceInfo[0].midibins[j] += voiceInfo[i].midibins[j]; + vector starts; + infile.getSpineStartList(starts, m_exinterp); + vector process(starts.size(), false); + for (int i=0; i<(int)starts.size(); i++) { + process[i] = hasBlankMeasure(starts[i]); + if (process[i]) { + starts[i]->setText("**temp-kern"); } - - for (int j=0; j<(int)voiceInfo.at(i).diatonic.size(); j++) { - for (int k=0; k<(int)voiceInfo.at(i).diatonic.at(k).size(); k++) { - voiceInfo[0].diatonic.at(j).at(k) += voiceInfo.at(i).diatonic.at(j).at(k); - } + } + infile.analyzeStructure(); + for (int i=0; i<(int)starts.size(); i++) { + if (!process[i]) { + continue; } + starts[i]->setText("**kern"); + fillInRests(starts[i]); } } @@ -115775,107 +120436,88 @@ void Tool_prange::mergeAllVoiceInfo(vector<_VoiceInfo>& voiceInfo) { ////////////////////////////// // -// Tool_prange::getVoiceInfo -- get names and track info for **kern spines. +// Tool_restfill::hasBlankMeasure -- // -void Tool_prange::getVoiceInfo(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { - voiceInfo.clear(); - voiceInfo.resize(infile.getMaxTracks() + 1); - for (int i=0; i<(int)voiceInfo.size(); i++) { - voiceInfo.at(i).index = i; - } - - vector kstarts = infile.getKernSpineStartList(); - - if (kstarts.size() == 2) { - voiceInfo[0].name = "both"; - voiceInfo[0].abbr = "both"; - voiceInfo[0].track = 0; - } else { - voiceInfo[0].name = "all"; - voiceInfo[0].abbr = "all"; - voiceInfo[0].track = 0; - } - +bool Tool_restfill::hasBlankMeasure(HTp start) { + bool foundcontent = false; + HTp current = start; + int founddata = false; + while (current) { - for (int i=0; igetTrack(); - voiceInfo[track].track = track; - if (token->isKern()) { - voiceInfo[track].kernQ = true; - } - if (!voiceInfo[track].kernQ) { - continue; - } - if (token->isInstrumentName()) { - voiceInfo[track].name = token->getInstrumentName(); - } - if (token->isInstrumentAbbreviation()) { - voiceInfo[track].abbr = token->getInstrumentAbbreviation(); + if (current->isBarline()) { + if (founddata && !foundcontent) { + return true; } + foundcontent = false; + founddata = false; + current = current->getNextToken(); + continue; } + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + founddata = true; + if (!current->isNull()) { + foundcontent = true; + } + current = current->getNextToken(); + } - - - // Check for piano/Grand Staff parts with LH/RH encoding. - if (kstarts.size() == 2) { - string bottomStaff = getHand(kstarts[0]); - string topStaff = getHand(kstarts[1]); - if (!bottomStaff.empty() && !topStaff.empty()) { - int track = kstarts[0]->getTrack(); - voiceInfo[track].name = "left hand"; - track = kstarts[1]->getTrack(); - voiceInfo[track].name = "right hand"; - } - } + return false; } ////////////////////////////// // -// Tool_prange::getHand -- +// Tool_restfill::fillInRests -- +// Also deal with cases where the last measure does not end in a barline. // -string Tool_prange::getHand(HTp sstart) { - HTp current = sstart->getNextToken(); - HTp target = NULL; +void Tool_restfill::fillInRests(HTp start) { + HTp current = start; + HTp firstcell = NULL; + int founddata = false; + bool foundcontent = false; + HumNum lasttime = 0; + HumNum currtime = 0; + HumNum duration = 0; while (current) { - if (current->isData()) { - break; + if (current->isBarline()) { + if (firstcell) { + lasttime = firstcell->getDurationFromStart(); + } + currtime = getNextTime(current); + if (firstcell && founddata && !foundcontent) { + duration = currtime - lasttime; + addRest(firstcell, duration); + } + firstcell = NULL; + founddata = false; + foundcontent = false; + current = current->getNextToken(); + lasttime = currtime; + continue; } - if (*current == "*LH") { - target = current; - break; + if (!current->isData()) { + current = current->getNextToken(); + continue; } - if (*current == "*RH") { - target = current; - break; + if (current->getDuration() == 0) { + // grace-note line, so ignore + current = current->getNextToken(); + continue; } - current = current->getNextToken(); - } - - if (target) { - if (*current == "*LH") { - return "LH"; - } else if (*current == "*RH") { - return "RH"; - } else { - return ""; + founddata = true; + if (!current->isNull()) { + foundcontent = true; } - } else { - return ""; + if (!firstcell) { + firstcell = current; + } + current = current->getNextToken(); } } @@ -115883,1173 +120525,1344 @@ string Tool_prange::getHand(HTp sstart) { ////////////////////////////// // -// Tool_prange::getInstrumentNames -- Find any instrument names which are listed -// before the first data line. Instrument names are in the form: -// -// *I"name +// Tool_restfill::addRest -- // -void Tool_prange::getInstrumentNames(vector& nameByTrack, vector& kernSpines, - HumdrumFile& infile) { - HumRegex hre; - - int track; - string name; - // nameByTrack.resize(kernSpines.size()); - nameByTrack.resize(infile.getMaxTrack() + 1); - fill(nameByTrack.begin(), nameByTrack.end(), ""); - vector kspines = infile.getKernSpineStartList(); - if (kspines.size() == 2) { - nameByTrack.at(0) = "both"; - } else { - nameByTrack.at(0) = "all"; +void Tool_restfill::addRest(HTp cell, HumNum duration) { + if (!cell) { + return; } - - for (int i=0; igetTrack(); - for (int k=0; k<(int)kernSpines.size(); k++) { - if (track == kernSpines[k]) { - nameByTrack[k] = name; - } - } - } - } + string text = Convert::durationToRecip(duration); + text += "r"; + if (m_hiddenQ) { + text += "yy"; } + cell->setText(text); } ////////////////////////////// // -// Tool_prange::fillHistograms -- Store notes in score by MIDI note number. +// Tool_restfill::getNextTime -- // -void Tool_prange::fillHistograms(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) { - // storage for finals info: - vector> diafinal; - vector> accfinal; - diafinal.resize(infile.getMaxTracks() + 1); - accfinal.resize(infile.getMaxTracks() + 1); - - for (int i=0; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - int track = token->getTrack(); - - diafinal.at(track).clear(); - accfinal.at(track).clear(); - - vector tokens = token->getSubtokens(); - for (int k=0; k<(int)tokens.size(); k++) { - if (tokens[k].find("r") != string::npos) { - continue; - } - if (tokens[k].find("R") != string::npos) { - // non-pitched note - continue; - } - bool hasPitch = false; - for (int m=0; m<(int)tokens[k].size(); m++) { - char test = tokens[k].at(m); - if (!isalpha(test)) { - continue; - } - test = tolower(test); - if ((test >= 'a') && (test <= 'g')) { - hasPitch = true; - break; - } - } - if (!hasPitch) { - continue; - } - int octave = Convert::kernToOctaveNumber(tokens[k]) + 3; - if (octave < 0) { - cerr << "Note too low: " << tokens[k] << endl; - continue; - } - if (octave >= 12) { - cerr << "Note too high: " << tokens[k] << endl; - continue; - } - int dpc = Convert::kernToDiatonicPC(tokens[k]); - int acc = Convert::kernToAccidentalCount(tokens[k]); - if (acc < -2) { - cerr << "Accidental too flat: " << tokens[k] << endl; - continue; - } - if (acc > +2) { - cerr << "Accidental too sharp: " << tokens[k] << endl; - continue; - } - int diatonic = dpc + 7 * octave; - int realdiatonic = dpc + 7 * (octave-3); - - diafinal.at(track).push_back(realdiatonic); - accfinal.at(track).push_back(acc); - - acc += 3; - int midi = Convert::kernToMidiNoteNumber(tokens[k]); - if (midi < 0) { - cerr << "MIDI pitch too low: " << tokens[k] << endl; - } - if (midi > 127) { - cerr << "MIDI pitch too high: " << tokens[k] << endl; - } - if (m_durationQ) { - double duration = Convert::kernToDuration(tokens[k]).getFloat(); - voiceInfo[track].diatonic.at(diatonic).at(0) += duration; - voiceInfo[track].diatonic.at(diatonic).at(acc) += duration; - voiceInfo[track].midibins.at(midi) += duration; - } else { - if (tokens[k].find("]") != string::npos) { - continue; - } - if (tokens[k].find("_") != string::npos) { - continue; - } - voiceInfo[track].diatonic.at(diatonic).at(0)++; - voiceInfo[track].diatonic.at(diatonic).at(acc)++; - voiceInfo[track].midibins.at(midi)++; - } - } +HumNum Tool_restfill::getNextTime(HTp token) { + HTp current = token; + while (current) { + if (current->isData()) { + return current->getDurationFromStart(); } + current = current->getNextToken(); } + return token->getOwner()->getOwner()->getScoreDuration(); +} - mergeFinals(voiceInfo, diafinal, accfinal); - // Sum all voices into midibins and diatonic arrays of vector position 0: - mergeAllVoiceInfo(voiceInfo); -} -////////////////////////////// + +///////////////////////////////// // -// Tool_prange::mergeFinals -- +// Tool_rid::Tool_rid -- Set the recognized options for the tool. // -void Tool_prange::mergeFinals(vector<_VoiceInfo>& voiceInfo, vector>& diafinal, - vector>& accfinal) { - for (int i=0; i<(int)voiceInfo.size(); i++) { - voiceInfo.at(i).diafinal = diafinal.at(i); - voiceInfo.at(i).accfinal = accfinal.at(i); - } +Tool_rid::Tool_rid(void) { + // Humdrum Toolkit classic rid options: + define("D|all-data=b", "remove all data records"); + define("d|null-data=b", "remove null data records"); + define("G|all-global=b", "remove all global comments"); + define("g|null-global=b", "remove null global comments"); + define("I|all-interpretation=b", "remove all interpretation records"); + define("i|null-interpretation=b", "remove null interpretation records"); + define("L|all-local-comment=b", "remove all local comments"); + define("l|1|null-local-comment=b", "remove null local comments"); + define("T|all-tandem-interpretation=b", "remove all tandem interpretations"); + define("U|u=b", "remove unnecessary (duplicate ex. interps."); + define("k|consider-kern-only=b", "for -d, only consider **kern spines."); + define("V=b", "negate filtering effect of program."); + define("H|no-humdrum-syntax=b", "equivalent to -GLIMd."); + + // additional options + define("M|all-barlines=b", "remove measure lines"); + define("C|all-comments=b", "remove all comment lines"); + define("c=b", "remove global and local comment lines"); } -////////////////////////////// +///////////////////////////////// // -// Tool_prange::printFilenameBase -- +// Tool_rid::run -- Do the main work of the tool. // -void Tool_prange::printFilenameBase(ostream& out, const string& filename) { - HumRegex hre; - if (hre.search(filename, "([^/]+)\\.([^.]*)", "")) { - if (hre.getMatch(1).size() <= 8) { - printXmlEncodedText(out, hre.getMatch(1)); - } else { - // problem with too long a name (MS-DOS will have problems). - // optimize to chop off everything after the dash in the - // name (for Josquin catalog numbers). - string shortname = hre.getMatch(1); - if (hre.search(shortname, "-.*")) { - hre.replaceDestructive(shortname, "", "-.*"); - printXmlEncodedText(out, shortname); - } else { - printXmlEncodedText(out, shortname); - } - } +bool Tool_rid::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i\n"; +bool Tool_rid::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } + return status; +} + + +bool Tool_rid::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_prange::printScoreEncodedText -- print SCORE text string -// See SCORE 3.1 manual additions (page 19) for more. +// Tool_rid::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -void Tool_prange::printScoreEncodedText(ostream& out, const string& strang) { - string newstring = strang; - HumRegex hre; - - hre.replaceDestructive(newstring, "<<$1", "&([aeiou])acute;", "gi"); - hre.replaceDestructive(newstring, "<<$1", "([áéíóú])", "gi"); - - hre.replaceDestructive(newstring, ">>$1", "&([aeiou])grave;", "gi"); - hre.replaceDestructive(newstring, ">>$1", "([àèìòù])", "gi"); - - hre.replaceDestructive(newstring, "%%$1", "&([aeiou])uml;", "gi"); - hre.replaceDestructive(newstring, "%%$1", "([äëïöü])", "gi"); - - hre.replaceDestructive(newstring, "^^$1", "&([aeiou])circ;", "gi"); - hre.replaceDestructive(newstring, "^^$1", "([âêîôû])", "gi"); - - hre.replaceDestructive(newstring, "##c", "ç", "g"); - hre.replaceDestructive(newstring, "##C", "Ç", "g"); - hre.replaceDestructive(newstring, "?\\|", "\\|", "g"); - hre.replaceDestructive(newstring, "?\\", "\\\\", "g"); - hre.replaceDestructive(newstring, "?m", "---", "g"); - hre.replaceDestructive(newstring, "?n", "--", "g"); - hre.replaceDestructive(newstring, "?2", "-sharp", "g"); - hre.replaceDestructive(newstring, "?1", "-flat", "g"); - hre.replaceDestructive(newstring, "?3", "-natural", "g"); - hre.replaceDestructive(newstring, "\\", "/", "g"); - hre.replaceDestructive(newstring, "?[", "\\[", "g"); - hre.replaceDestructive(newstring, "?]", "\\]", "g"); +void Tool_rid::initialize(void) { + option_D = getBoolean("D"); + option_d = getBoolean("d"); + option_G = getBoolean("G"); + option_g = getBoolean("g"); + option_I = getBoolean("I"); + option_i = getBoolean("i"); + option_L = getBoolean("L"); + option_l = getBoolean("l"); + option_T = getBoolean("T"); + option_U = getBoolean("U"); + option_M = getBoolean("M"); + option_C = getBoolean("C"); + option_c = getBoolean("c"); + option_k = getBoolean("k"); + option_V = getBoolean("V"); - out << newstring; + if (getBoolean("no-humdrum-syntax")) { + // remove all Humdrum file structure + option_G = option_L = option_I = option_M = option_d = 1; + } } ////////////////////////////// // -// Tool_prange::printXmlEncodedText -- convert -// & to & -// " to " -// ' to &spos; -// < to < -// > to > +// Tool_rid::processFile -- // -void Tool_prange::printXmlEncodedText(ostream& out, const string& strang) { - HumRegex hre; - string astring = strang; +void Tool_rid::processFile(HumdrumFile& infile) { + int setcount = 1; // disabled for now. - hre.replaceDestructive(astring, "&", "&", "g"); - hre.replaceDestructive(astring, "'", "'", "g"); - hre.replaceDestructive(astring, "\"", """, "g"); - hre.replaceDestructive(astring, "<", "<", "g"); - hre.replaceDestructive(astring, ">", ">", "g"); + HumRegex hre; + int revQ = option_V; + + // if bibliographic/reference records are not suppressed + // print the !!!!SEGMENT: marker if present. + if ((setcount > 1) && (!option_G)) { + infile.printNonemptySegmentLabel(m_humdrum_text); + } + + for (int i=0; i& voiceInfo, HumdrumFile& infile) { - string titlestring = getTitle(); - - if (m_defineQ) { - out << "#define SVG t 1 1 \\n_99%svg%\n"; - } +Tool_rphrase::Tool_rphrase(void) { + define("a|average=b", "calculate average length of rest-phrases by score"); + define("A|all-average=b", "calculate average length of rest-phrases for all scores"); + define("B|no-breath=b", "ignore breath interpretations"); + define("c|composite|collapse=b", "collapse all voices into single part"); + define("d|duration-unit=d:2.0", "duration units, default: 2.0 (minims/half notes)"); + define("f|filename=b", "include filename in output analysis"); + define("F|full-filename=b", "include full filename location in output analysis"); + define("I|no-info=b", "do not display summary info"); + define("l|longa=b", "display minim length of longas"); + define("m|b|measure|barline=b", "include barline numbers in output analysis"); + define("mark=b", "mark starts of phrases in score"); + define("s|sort=b", "sort phrases by short to long length"); + define("S|reverse-sort=b", "sort phrases by long to short length"); + define("u|url-type=s", "URL type (jrp, 1520s) for hyperlink"); + define("z|squeeze=b", "squeeze notation"); + define("close=b", "close details element initially"); +} - string acctext = "g.bar.doubleflat path{color:darkorange;stroke:darkorange;}g.bar.flat path{color:brown;stroke:brown;}g.bar.sharp path{color:royalblue;stroke:royalblue;}g.bar.doublesharp path{color:aquamarine;stroke:aquamarine;}"; - string hovertext = ".bar:hover path{fill:red;color:red;stroke:red !important}"; - string hoverfilltext = hovertext; - string text1 = ""; - string text2 = text1; +///////////////////////////////// +// +// Tool_rphrase::run -- Do the main work of the tool. +// - // print CSS style information if requested - if (m_hoverQ) { - SVGTEXT(out, text1); +bool Tool_rphrase::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i 12) { - vpos = maxStaffPosition + 3; - } - out << "t 2 10 "; - out << vpos; - out << " 1 1 0 0 0 0 -1.35\n"; - // out << "_03"; - printScoreEncodedText(out, titlestring); - out << "\n"; - } - // print duration label if duration weighting is being used - SVGTEXT(out, ""); - if (m_durationQ) { - out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n"; - out << "_00(durations)\n"; +bool Tool_rphrase::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); } else { - out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n"; - out << "_00(attacks)\n"; + out << infile; } - SVGTEXT(out, ""); + return status; +} - // print staff lines - out << "8 1 0 0 0 200\n"; // staff 1 - out << "8 2 0 -6 0 200\n"; // staff 2 - int keysig = getKeySignature(infile); - // print key signature - if (keysig) { - out << "17 1 10 0 " << keysig << " 101.0"; - printKeySigCompression(out, keysig, 0); - out << endl; - out << "17 2 10 0 " << keysig; - printKeySigCompression(out, keysig, 1); - out << endl; +bool Tool_rphrase::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } + return status; +} - // print barlines - out << "14 1 0 2\n"; // starting barline - out << "14 1 200 2\n"; // ending barline - out << "14 1 0 2 8\n"; // curly brace at start - - // print clefs - out << "3 2 2\n"; // treble clef - out << "3 1 2 0 1\n"; // bass clef - - assignHorizontalPosition(voiceInfo, 25.0, 170.0); - double maxvalue = 0.0; - for (int i=1; i<(int)voiceInfo.size(); i++) { - double tempvalue = getMaxValue(voiceInfo.at(i).diatonic); - if (tempvalue > maxvalue) { - maxvalue = tempvalue; - } - } - for (int i=(int)voiceInfo.size()-1; i>0; i--) { - if (voiceInfo.at(i).kernQ) { - printScoreVoice(out, voiceInfo.at(i), maxvalue); - } - } - if (m_allQ) { - printScoreVoice(out, voiceInfo.at(0), maxvalue); - } +bool Tool_rphrase::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; } + ////////////////////////////// // -// Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceinfo) { +// Tool_rphrase::finally -- // -int Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceInfo) { - int maxi = getMaxDiatonicIndex(voiceInfo[0].diatonic); - int maxdiatonic = maxi - 3 * 7; - int staffline = maxdiatonic - 27; - return staffline; +void Tool_rphrase::finally(void) { + if (!m_markQ) { + if (m_allAverageQ) { + if (m_compositeQ) { + double average = m_sumComposite / m_pcountComposite; + m_free_text << "Composite average phrase length: " << average << " minims" << endl; + } else { + double average = m_sum / m_pcount; + m_free_text << "All average phrase length: " << average << " minims" << endl; + } + } + } } - ////////////////////////////// // -// Tool_prange::printKeySigCompression -- +// Tool_rphrase::initialize -- // -void Tool_prange::printKeySigCompression(ostream& out, int keysig, int extra) { - double compression = 0.0; - switch (abs(keysig)) { - case 0: compression = 0.0; break; - case 1: compression = 0.0; break; - case 2: compression = 0.0; break; - case 3: compression = 0.0; break; - case 4: compression = 0.9; break; - case 5: compression = 0.8; break; - case 6: compression = 0.7; break; - case 7: compression = 0.6; break; - } - if (compression <= 0.0) { - return; - } - for (int i=0; i& voiceInfo, int minval, int maxval) { - int count = 0; - for (int i=1; i<(int)voiceInfo.size(); i++) { - if (voiceInfo[i].kernQ) { - count++; - } +void Tool_rphrase::processFile(HumdrumFile& infile) { + if (m_filenameQ) { + m_filename = infile.getFilename(); + HumRegex hre; + hre.replaceDestructive(m_filename, "", ".*\\/"); + hre.replaceDestructive(m_filename, "", "\\.krn$"); + } else if (m_fullFilenameQ) { + m_filename = infile.getFilename(); } - if (m_allQ) { - count++; + vector kernStarts = infile.getKernSpineStartList(); + vector voiceInfo(kernStarts.size()); + Tool_rphrase::VoiceInfo compositeInfo; + + if (m_compositeQ) { + fillCompositeInfo(compositeInfo, infile); + } else { + fillVoiceInfo(voiceInfo, kernStarts, infile); } - vector hpos(count, 0); - hpos[0] = maxval; - hpos.back() = minval; + if (m_longaQ) { + markLongaDurations(infile); + } - if (hpos.size() > 2) { - for (int i=1; i<(int)hpos.size()-1; i++) { - int ii = hpos.size() - i - 1; - hpos[i] = (double)ii / (hpos.size()-1) * (maxval - minval) + minval; + if ((!m_allAverageQ) && (!m_markQ)) { + if (m_line == 1) { + if (m_compositeQ) { + m_free_text << "Filename"; + if (!m_urlType.empty()) { + m_free_text << "\tVHV"; + } + m_free_text << "\tVoice"; + m_free_text << "\tComp seg count"; + if (m_averageQ) { + m_free_text << "\tAvg comp seg dur"; + } + m_free_text << "\tComposite seg durs"; + m_free_text << endl; + } else { + m_free_text << "Filename"; + if (!m_urlType.empty()) { + m_free_text << "\tVHV"; + } + m_free_text << "\tVoice"; + m_free_text << "\tSounding dur"; + m_free_text << "\tResting dur"; + m_free_text << "\tTotal dur"; + m_free_text << "\tSeg count"; + if (m_averageQ) { + m_free_text << "\tSeg dur average"; + } + m_free_text << "\tSegment durs"; + m_free_text << endl; + } + } + if (m_compositeQ) { + if (m_compositeQ) { + m_line++; + } + printVoiceInfo(compositeInfo); + } else { + printVoiceInfo(voiceInfo); } } - int position = 0; - if (m_allQ) { - position = 1; - voiceInfo[0].hpos = hpos[0]; - } - for (int i=0; i<(int)voiceInfo.size(); i++) { - if (voiceInfo.at(i).kernQ) { - voiceInfo.at(i).hpos = hpos.at(position++); + if (m_markQ) { + outputMarkedFile(infile, voiceInfo, compositeInfo); + if (m_squeezeQ) { + m_humdrum_text << "!!!verovio: evenNoteSpacing" << endl; } } + } -////////////////////////////// +////////////////////////// // -// Tool_prange::getKeySignature -- find first key signature in file. +// Tool_rphrase::markLongaDuratios -- // -int Tool_prange::getKeySignature(HumdrumFile& infile) { +void Tool_rphrase::markLongaDurations(HumdrumFile& infile) { + string longrdf; for (int i=0; iisKeySignature()) { - return Convert::kernKeyToNumber(*token); - } + if (!infile[i].isReferenceRecord()) { + continue; + } + string key = infile[i].getReferenceKey(); + if (key != "RDF**kern") { + continue; + } + string value = infile[i].getReferenceValue(); + HumRegex hre; + if (hre.search(value, "^\\s*([^\\s=]+)\\s*=.*long")) { + longrdf = hre.getMatch(1); + break; } } - return 0; // C major key signature -} - - - -////////////////////////////// -// -// Tool_prange::printScoreVoice -- print the range information for a particular voice (in SCORE format). -// - -void Tool_prange::printScoreVoice(ostream& out, _VoiceInfo& voiceInfo, double maxvalue) { - int mini = getMinDiatonicIndex(voiceInfo.diatonic); - int maxi = getMaxDiatonicIndex(voiceInfo.diatonic); - - if ((mini < 0) || (maxi < 0)) { - // no data for voice so skip + if (longrdf.empty()) { return; } - // int minacci = getMinDiatonicAcc(voiceInfo.diatonic, mini); - // int maxacci = getMaxDiatonicAcc(voiceInfo.diatonic, maxi); - int mindiatonic = mini - 3 * 7; - int maxdiatonic = maxi - 3 * 7; - // int minacc = minacci - 3; - // int maxacc = maxacci - 3; - - int staff; - double vpos; - - int voicevpos = -3; - staff = getStaffBase7(mindiatonic); - int lowestvpos = getVpos(mindiatonic); - if ((staff == 1) && (lowestvpos <= 0)) { - voicevpos += lowestvpos - 2; - } - - if (m_localQ || (voiceInfo.index == 0)) { - double localmaxvalue = getMaxValue(voiceInfo.diatonic); - maxvalue = localmaxvalue; - } - double width; - double hoffset = 2.3333; - double maxhist = 17.6; - int i; - int base7; - - // print histogram bars - for (i=mini; i<=maxi; i++) { - if (voiceInfo.diatonic.at(i).at(0) <= 0.0) { + for (int i=0; i starthpos(6, 0.0); - for (int j=1; j<(int)starthpos.size(); j++) { - double width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue; - starthpos[j] = starthpos[j-1] + width; - } - for (int j=(int)starthpos.size() - 1; j>0; j--) { - starthpos[j] = starthpos[j-1]; + if (!infile[i].isData()) { + continue; } - - // print chromatic alterations - for (int j=(int)voiceInfo.diatonic.at(i).size()-1; j>0; j--) { - if (voiceInfo.diatonic.at(i).at(j) <= 0.0) { + for (int j=0; jisKern()) { continue; } - int acc = 0; - switch (j) { - case 1: acc = -2; break; - case 2: acc = -1; break; - case 3: acc = 0; break; - case 4: acc = +1; break; - case 5: acc = +2; break; - } - - width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue + hoffset; - if (m_hoverQ) { - string title = getNoteTitle((int)voiceInfo.diatonic.at(i).at(j), base7, acc); - SVGTEXT(out, title); - } - out << "1 " << staff << " " << (voiceInfo.hpos + starthpos.at(j) + hoffset) << " " << vpos; - out << " 0 -1 4 0 0 0 99 0 0 "; - out << width << "\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); - } - } - } - - string voicestring = voiceInfo.name; - if (voicestring.empty()) { - voicestring = voiceInfo.abbr; - } - if (!voicestring.empty()) { - HumRegex hre; - hre.replaceDestructive(voicestring, "", "(\\\\n)+$"); - vector pieces; - hre.split(pieces, voicestring, "\\\\n"); - - if (pieces.size() > 1) { - voicestring = ""; - for (int i=0; i<(int)pieces.size(); i++) { - voicestring += pieces[i]; - if (i < (int)pieces.size() - 1) { - voicestring += "/"; - } - } - } - - double increment = 4.0; - for (int i=0; i<(int)pieces.size(); i++) { - // print voice name - double tvoffset = -4.0; - out << "t 1 " << voiceInfo.hpos << " " - << (voicevpos - increment * i) - << " 1 1 0 0 0 0 " << tvoffset; - out << "\n"; - - if (pieces[i] == "all") { - out << "_02"; - } else if (pieces[i] == "both") { - out << "_02"; - } else { - out << "_00"; + if (token->find(longrdf) != string::npos) { + HumNum duration = token->getTiedDuration(); + stringstream value; + value.str(""); + value << duration.getFloat() / m_durUnit; + token->setValue("auto", "rphrase-longa", value.str()); } - printScoreEncodedText(out, pieces[i]); - out << "\n"; - } - } - - // print the lowest pitch in range - staff = getStaffBase7(mindiatonic); - vpos = getVpos(mindiatonic); - if (m_hoverQ) { - string content = ""; - content += getDiatonicPitchName(mindiatonic, 0); - content += ": lowest note"; - if (!voicestring.empty()) { - content += " of "; - content += voicestring; - content += "'s range"; - } - content += ""; - SVGTEXT(out, content); - } - out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos - << " 0 0 4 0 0 -2\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); - } - - // print the highest pitch in range - staff = getStaffBase7(maxdiatonic); - vpos = getVpos(maxdiatonic); - if (m_hoverQ) { - string content = ""; - content += getDiatonicPitchName(maxdiatonic, 0); - content += ": highest note"; - if (!voicestring.empty()) { - content += " of "; - content += voicestring; - content += "'s range"; - } - content += ""; - SVGTEXT(out, content); - } - out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos - << " 0 0 4 0 0 -2\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); - } - - double goffset = -1.66; - double toffset = 1.5; - double median12 = getMedian12(voiceInfo.midibins); - double median40 = Convert::base12ToBase40(median12); - double median7 = Convert::base40ToDiatonic(median40); - // int acc = Convert::base40ToAccidental(median40); + } + } +} - staff = getStaffBase7(median7); - vpos = getVpos(median7); - // these offsets are useful when the quartile pitches are not shown... - int vvpos = maxdiatonic - median7 + 1; - int vvpos2 = median7 - mindiatonic + 1; - double offset = goffset; - if (vvpos <= 2) { - offset += toffset; - } else if (vvpos2 <= 2) { - offset -= toffset; - } - if (m_hoverQ) { - string content = ""; - content += getDiatonicPitchName(median7, 0); - content += ": median note"; - if (!voicestring.empty()) { - content += " of "; - content += voicestring; - content += "'s range"; +////////////////////////////// +// +// Tool_rphrase::outputMarkedFile -- +// + +void Tool_rphrase::outputMarkedFile(HumdrumFile& infile, vector<Tool_rphrase::VoiceInfo>& voiceInfo, + Tool_rphrase::VoiceInfo& compositeInfo) { + m_free_text.clear(); + m_free_text.str(""); + for (int i=0; i<infile.getLineCount(); i++) { + if (!infile[i].isData()) { + m_humdrum_text << infile[i] << endl; + } else { + printDataLine(infile, i); } - content += ""; - SVGTEXT(out, content); - } - out << "1 " << staff << " " << voiceInfo.hpos << " "; - if (vpos > 0) { - out << vpos + 100; - } else { - out << vpos - 100; } - out << " 0 1 4 0 0 " << offset << "\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); + + if (m_infoQ) { + printEmbeddedVoiceInfo(voiceInfo, compositeInfo, infile); } +} - if (m_finalisQ) { - for (int f=0; f<(int)voiceInfo.diafinal.size(); f++) { - int diafinalis = voiceInfo.diafinal.at(f); - int accfinalis = voiceInfo.accfinal.at(f); - int staff = getStaffBase7(diafinalis); - int vpos = getVpos(diafinalis); - double goffset = -1.66; - double toffset = 3.5; - // these offsets are useful when the quartile pitches are not shown... - double offset = goffset; - offset += toffset; - if (m_hoverQ) { - string content = ""; - content += getDiatonicPitchName(diafinalis, accfinalis); - content += ": last note"; - if (!voicestring.empty()) { - content += " of "; - if (voiceInfo.index == 0) { - content += voiceInfo.namfinal.at(f); - } else { - content += voicestring; - } - } - content += ""; - SVGTEXT(out, content); - } - out << "1 " << staff << " " << voiceInfo.hpos << " "; - if (vpos > 0) { - out << vpos + 100; - } else { - out << vpos - 100; +////////////////////////////// +// +// Tool_rphrase::printDataLine -- +// + +void Tool_rphrase::printDataLine(HumdrumFile& infile, int index) { + + bool hasLonga = false; + if (m_longaQ) { + for (int j=0; jisKern()) { + continue; } - out << " 0 0 4 0 0 " << offset << "\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); + string lotext = token->getValue("auto", "rphrase-longa"); + if (!lotext.empty()) { + hasLonga = true; + break; } } } - /* Needs fixing - int topquartile; - if (m_quartileQ) { - // print top quartile - topquartile = getTopQuartile(voiceInfo.midibins); - if (m_diatonicQ) { - topquartile = Convert::base7ToBase12(topquartile); + + bool hasLo = false; + for (int j=0; jisKern()) { + continue; } - staff = getStaffBase7(topquartile); - vpos = getVpos(topquartile); - vvpos = median7 - topquartile + 1; - if (vvpos <= 2) { - offset = goffset + toffset; - } else { - offset = goffset; + string lotext = token->getValue("auto", "rphrase-start"); + if (!lotext.empty()) { + hasLo = true; + break; } - vvpos = maxdiatonic - topquartile + 1; - if (vvpos <= 2) { - offset = goffset + toffset; + } + + // search for composite phrase info + bool hasGlo = false; + if (infile[index].isData()) { + string glotext = infile[index].getValue("auto", "rphrase-composite-start"); + if (!glotext.empty()) { + hasGlo = true; } + } - if (m_hoverQ) { - if (m_defineQ) { - out << "SVG "; + if (hasGlo) { + string glotext = infile[index].getValue("auto", "rphrase-composite-start"); + m_humdrum_text << "!!LO:TX:b:B:color=" << m_compositeLengthColor << ":t=" << glotext << endl; + } + + if (hasLonga) { + for (int j=0; jisKern()) { + m_humdrum_text << "!"; } else { - out << "t 1 1\n"; - out << SVGTAG; + string value = token->getValue("auto", "rphrase-longa"); + if (value.empty()) { + m_humdrum_text << "!"; + } else { + m_humdrum_text << "!LO:TX:a:B:color=silver:t=" << value; + } } - printScoreEncodedText(out, ""); - printDiatonicPitchName(out, topquartile, 0); - out << ": top quartile note"; - if (voicestring.size() > 0) { - out << " of " << voicestring << "\'s range"; + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; } - printScoreEncodedText(out, "\n"); - } - out << "1 " << staff << " " << voiceInfo.hpos << " "; - if (vpos > 0) { - out << vpos + 100; - } else { - out << vpos - 100; - } - out << " 0 0 4 0 0 " << offset << "\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); } + m_humdrum_text << endl; } - // print bottom quartile - if (m_quartileQ) { - int bottomquartile = getBottomQuartile(voiceInfo.midibins); - if (m_diatonicQ) { - bottomquartile = Convert::base7ToBase12(bottomquartile); - } - staff = getStaffBase7(bottomquartile); - vpos = getVpos(bottomquartile); - vvpos = median7 - bottomquartile + 1; - if (vvpos <= 2) { - offset = goffset + toffset; - } else { - offset = goffset; - } - vvpos = bottomquartile - mindiatonic + 1; - if (vvpos <= 2) { - offset = goffset - toffset; - } - if (m_hoverQ) { - if (m_defineQ) { - out << "SVG "; + if (hasLo) { + for (int j=0; jisKern()) { + m_humdrum_text << "!"; } else { - out << "t 1 1\n"; - out << SVGTAG; + string value = token->getValue("auto", "rphrase-start"); + if (value.empty()) { + m_humdrum_text << "!"; + } else { + m_humdrum_text << "!LO:TX:a:B:color=" << m_voiceLengthColor << ":t=" << value; + } } - printScoreEncodedText(out, ""); - printDiatonicPitchName(out, bottomquartile, 0); - out << ": bottom quartile note"; - if (voicestring.size() > 0) { - out << " of " << voicestring << "\'s range"; + if (j < infile[index].getFieldCount() - 1) { + m_humdrum_text << "\t"; } - printScoreEncodedText(out, "\n"); - } - out << "1.0 " << staff << ".0 " << voiceInfo.hpos << " "; - if (vpos > 0) { - out << vpos + 100; - } else { - out << vpos - 100; - } - out << " 0 0 4 0 0 " << offset << "\n"; - if (m_hoverQ) { - SVGTEXT(out, ""); } + m_humdrum_text << endl; } - */ + m_humdrum_text << infile[index] << endl; } ////////////////////////////// // -// Tool_prange::printDiatonicPitchName -- +// Tool_rphrase::getCompositeStates -- // -void Tool_prange::printDiatonicPitchName(ostream& out, int base7, int acc) { - out << getDiatonicPitchName(base7, acc); +void Tool_rphrase::getCompositeStates(vector& noteStates, HumdrumFile& infile) { + noteStates.resize(infile.getLineCount()); + fill(noteStates.begin(), noteStates.end(), -1); + for (int i=0; iisKern()) { + continue; + } + if (token->isRest()) { + continue; + } else if (token->isNull()) { + HTp resolve = token->resolveNull(); + if (!resolve) { + continue; + } else if (resolve->isRest()) { + continue; + } else { + value = 1; + break; + } + } else { + value = 1; + break; + } + } + noteStates[i] = value; + } } ////////////////////////////// // -// Tool_prange::getDiatonicPitchName -- +// Tool_rphrase::printVoiceInfo -- // -string Tool_prange::getDiatonicPitchName(int base7, int acc) { - string output; - int dpc = base7 % 7; - char letter = (dpc + 2) % 7 + 'A'; - output += letter; - switch (acc) { - case -1: output += "♭"; break; - case +1: output += "♯"; break; - case -2: output += "𝄫"; break; - case +2: output += "𝄪"; break; +void Tool_rphrase::printVoiceInfo(vector& voiceInfo) { + for (int i=(int)voiceInfo.size() - 1; i>=0; i--) { + if (!m_compositeQ) { + m_line++; + } + printVoiceInfo(voiceInfo[i]); + } +} + + +////////////////////////////// +// +// Tool_rphrase::printHyperlink -- +// + +void Tool_rphrase::printHyperlink(const string& urlType) { + string command = "rphrase"; + string options; + options += "l"; + options+= "z"; + if (m_compositeQ) { + options += "c"; + } + if (m_sortQ) { + options += "s"; + } else if (m_reverseSortQ) { + options += "S"; + } + if (!options.empty()) { + command += "%20-"; + command += options; + } + + if (urlType == "jrp") { + m_free_text << "=HYPERLINK(\"https://verovio.humdrum.org/?file=jrp/\" & "; + m_free_text << "LEFT(A" << m_line << ", 3) & \"/\" & A" << m_line << " & "; + m_free_text << "\".krn&filter=" << command << "&k=ey\", LEFT(A" << m_line; + m_free_text << ", FIND(\"-\", A" << m_line << ") - 1))"; + } else if (urlType == "1520s") { + m_free_text << "=HYPERLINK(\"https://verovio.humdrum.org/?file=1520s/\" & A"; + m_free_text << m_line << " & \".krn&filter=" << command << "&k=ey\", LEFT(A"; + m_free_text << m_line << ", FIND(\"-\", A" << m_line << ") - 1))"; } - int octave = base7 / 7; - output += to_string(octave); - return output; } ////////////////////////////// // -// Tool_prange::printHtmlStringEncodeSimple -- +// Tool_rphrase::printVoiceInfo -- // -void Tool_prange::printHtmlStringEncodeSimple(ostream& out, const string& strang) { - string newstring = strang; - HumRegex hre; - hre.replaceDestructive(newstring, "&", "&", "g"); - hre.replaceDestructive(newstring, "<", "<", "g"); - hre.replaceDestructive(newstring, ">", "<", "g"); - out << newstring; +void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) { + if (m_filenameQ) { + m_free_text << m_filename << "\t"; + } + if (!m_urlType.empty()) { + printHyperlink(m_urlType); + m_free_text << "\t"; + } + m_free_text << voiceInfo.name << "\t"; + + if (!m_compositeQ) { + double sounding = 0.0; + double resting = 0.0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.phraseDurs[i] > 0.0) { + sounding += voiceInfo.phraseDurs[i]; + } + if (voiceInfo.restsBefore[i] > 0.0) { + resting += voiceInfo.restsBefore[i]; + } + } + double total = sounding + resting; + // double sounding_percent = int (sounding/total * 100.0 + 0.5); + // double resting_percent = int (sounding/total * 100.0 + 0.5); + + m_free_text << twoDigitRound(sounding) << "\t"; + m_free_text << twoDigitRound(resting) << "\t"; + m_free_text << twoDigitRound(total) << "\t"; + } + + m_free_text << voiceInfo.phraseDurs.size() << "\t"; + + if (m_averageQ) { + double sum = 0; + int count = 0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + count++; + sum += voiceInfo.phraseDurs.at(i); + } + m_free_text << int(sum / count * 100.0 + 0.5)/100.0 << "\t"; + } + + if (m_sortQ || m_reverseSortQ) { + vector> sortList; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + sortList.emplace_back(voiceInfo.phraseDurs[i], i); + } + if (m_sortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + } else if (m_reverseSortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + } + + for (int i=0; i<(int)sortList.size(); i++) { + int ii = sortList[i].second; + if (m_barlineQ) { + m_free_text << "m" << voiceInfo.barStarts.at(ii) << ":"; + } + m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(ii)); + if (i < (int)sortList.size() - 1) { + m_free_text << " "; + } + } + } else { + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.restsBefore.at(i) > 0) { + m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } else if (i > 0) { + // force display r:0 for section boundaries. + m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } + if (m_barlineQ) { + m_free_text << "m" << voiceInfo.barStarts.at(i) << ":"; + } + m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(i)); + if (i < (int)voiceInfo.phraseDurs.size() - 1) { + m_free_text << " "; + } + } + } + + m_free_text << endl; } ////////////////////////////// // -// Tool_prange::getNoteTitle -- return the title of the histogram bar. -// value = duration or count of notes -// diatonic = base7 value for note -// acc = accidental for diatonic note. +// Tool_rphrase::printEmbeddedVoiceInfo -- // -string Tool_prange::getNoteTitle(double value, int diatonic, int acc) { - stringstream output; - output << "& voiceInfo, Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { + + m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;; + + m_humdrum_text << "!!@SCRIPT:" << endl; + m_humdrum_text << "!! function rphraseGotoMeasure(measure) {" << endl; + m_humdrum_text << "!! let target = `svg .measure.m-${measure}`;" << endl; + m_humdrum_text << "!! let element = document.querySelector(target);" << endl; + m_humdrum_text << "!! if (element) {" << endl; + m_humdrum_text << "!! element.scrollIntoViewIfNeeded({ behavior: 'smooth' });" << endl; + m_humdrum_text << "!! }" << endl; + m_humdrum_text << "!! }" << endl; + + m_humdrum_text << "!!@CONTENT:\n"; + + if (m_compositeQ) { + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + } else { + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; } - output << "\""; - output << ">"; - if (m_durationQ) { - output << value / 8.0; - if (value/8.0 == 1.0) { - output << " long on "; - } else { - output << " longs on "; + m_humdrum_text << "!!<style> .PREHTML details { position: relative; padding-left: 20px; } </style>" << endl; + m_humdrum_text << "!!<style> .PREHTML summary { font-size: 1.5rem; cursor: pointer; list-style: none; } </style>" << endl; + m_humdrum_text << "!!<style> .PREHTML summary::before { content: '▶'; display: inline-block; width: 2em; margin-left: -1.5em; text-align: center; } </style>" << endl; + m_humdrum_text << "!!<style> .PREHTML details[open] summary::before { content: '▼'; } </style>" << endl; + + if (m_compositeQ) { + m_humdrum_text << "!!<details"; + if (!m_closeQ) { + m_humdrum_text << " open"; } - output << getDiatonicPitchName(diatonic, acc); + m_humdrum_text << "><summary>Composite rest phrasing</summary>\n"; } else { - output << value; - output << " "; - output << getDiatonicPitchName(diatonic, acc); - if (value != 1.0) { - output << "s"; + m_humdrum_text << "!!<details"; + if (!m_closeQ) { + m_humdrum_text << " open"; + } + m_humdrum_text << "><summary>Voice rest phrasing</summary>\n"; + } + if (m_compositeQ) { + printEmbeddedCompositeInfo(compositeInfo, infile); + } else { + if (voiceInfo.size() > 0) { + m_humdrum_text << "!!<table class='rphrase'>" << endl; + m_humdrum_text << "!!<tr><th class='voice'>Voice</th><th class='sounding'>Sounding</th><th class='resting'>Resting</th><th class='segments'>Segments</th><th class='average'>Average</th><th class='segment-durations'>Segment durations</th></tr>" << endl; + for (int i=(int)voiceInfo.size() - 1; i>=0; i--) { + printEmbeddedIndividualVoiceInfo(voiceInfo[i], infile); + } + m_humdrum_text << "!!</table>" << endl; + printEmbeddedVoiceInfoSummary(voiceInfo, infile); } } - output << ""; - return output.str(); + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!@@END: PREHTML" << endl; } ////////////////////////////// // -// Tool_prange::getDiatonicInterval -- +// Tool_rphrase::printEmbeddedCompositeInfo -- // -int Tool_prange::getDiatonicInterval(int note1, int note2) { - int vpos1 = getVpos(note1); - int vpos2 = getVpos(note2); - return abs(vpos1 - vpos2) + 1; -} - +void Tool_rphrase::printEmbeddedCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { + m_humdrum_text << "!!
    " << endl; + m_humdrum_text << "!!
      " << endl; + m_humdrum_text << "!!
    • Composite segment count: " << compositeInfo.phraseDurs.size() << "
    • " << endl; -////////////////////////////// -// -// Tool_prange::getTopQuartile -- -// + if (!compositeInfo.phraseDurs.empty()) { + m_humdrum_text << "!!
    • Composite segment duration"; + if (compositeInfo.phraseDurs.size() != 1) { + m_humdrum_text << "s"; + } + m_humdrum_text << ": "; + if (m_sortQ || m_reverseSortQ) { + vector> sortList; + for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { + sortList.emplace_back(compositeInfo.phraseDurs[i], i); + } + if (m_sortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + } else if (m_reverseSortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + } -int Tool_prange::getTopQuartile(vector& midibins) { - double sum = accumulate(midibins.begin(), midibins.end(), 0.0); + for (int i=0; i<(int)sortList.size(); i++) { + int ii = sortList[i].second; + if (m_barlineQ) { + m_humdrum_text << "m" << compositeInfo.barStarts.at(ii) << ":"; + } + m_humdrum_text << "" << twoDigitRound(compositeInfo.phraseDurs.at(ii)) << ""; + if (i < (int)sortList.size() - 1) { + m_humdrum_text << " "; + } + } + } else { + for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { + if (compositeInfo.restsBefore.at(i) > 0) { + m_humdrum_text << "" << twoDigitRound(compositeInfo.restsBefore.at(i)) << " "; + } else if (i > 0) { + // force display r:0 for section boundaries. + m_humdrum_text << "" << twoDigitRound(compositeInfo.restsBefore.at(i)) << " "; + } + if (m_barlineQ) { + m_humdrum_text << "m" << compositeInfo.barStarts.at(i) << ":"; + } + m_humdrum_text << "" << twoDigitRound(compositeInfo.phraseDurs.at(i)) << ""; + if (i < (int)compositeInfo.phraseDurs.size() - 1) { + m_humdrum_text << " "; + } + } + } + m_humdrum_text << "
    • " << endl; - double cumsum = 0.0; - int i; - for (i=midibins.size()-1; i>=0; i--) { - if (midibins[i] <= 0.0) { - continue; + if (m_averageQ && (compositeInfo.phraseDurs.size() > 1)) { + double sum = 0; + int count = 0; + for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { + count++; + sum += compositeInfo.phraseDurs.at(i); + } + double average = int(sum / count * 100.0 + 0.5)/100.0; + m_humdrum_text << "!!
    • Average composite segment durations: " << average << "
    • " << endl; } - cumsum += midibins[i]/sum; - if (cumsum >= 0.25) { - return i; + + m_humdrum_text << "!!
    • Voices: " << getVoiceInfo(infile) << "
    • " << endl; + + if (m_durUnit != 2.0) { + m_humdrum_text << "!!
    • Duration unit: " << m_durUnit << "
    • " << endl; } } - return -1; + m_humdrum_text << "!!
    " << endl; + m_humdrum_text << "!!
    " << endl; } ////////////////////////////// // -// Tool_prange::getBottomQuartile -- +// Tool_rphrase::getVoiceInfo -- // -int Tool_prange::getBottomQuartile(vector& midibins) { - double sum = accumulate(midibins.begin(), midibins.end(), 0.0); - - double cumsum = 0.0; - int i; - for (i=0; i<(int)midibins.size(); i++) { - if (midibins[i] <= 0.0) { - continue; +string Tool_rphrase::getVoiceInfo(HumdrumFile& infile) { + vector kspines = infile.getKernSpineStartList(); + string vcount = to_string(kspines.size()); + string ocount; + for (int i=0; i= 0.25) { - return i; + if (infile[i].isReferenceRecord()) { + string key = infile[i].getReferenceKey(); + if (key == "voices") { + ocount = infile[i].getReferenceValue(); + } } } - return -1; + if (ocount.empty()) { + return vcount; + } + + if (ocount != vcount) { + string output = ocount; + output += "("; + output += vcount; + output += ")"; + return output; + } else { + return vcount; + } } ////////////////////////////// // -// Tool_prange::getMaxValue -- +// Tool_rphrase::printEmbeddedVoiceInfoSummary -- // -double Tool_prange::getMaxValue(vector>& bins) { - double maxi = 0; - for (int i=1; i<(int)bins.size(); i++) { - if (bins.at(i).at(0) > bins.at(maxi).at(0)) { - maxi = i; +void Tool_rphrase::printEmbeddedVoiceInfoSummary(vector& voiceInfo, HumdrumFile& infile) { + m_humdrum_text << "!!
      " << endl; + + double total = 0.0; + for (int i=0; i<(int)voiceInfo[0].phraseDurs.size(); i++) { + if (voiceInfo[0].phraseDurs[i] > 0.0) { + total += voiceInfo[0].phraseDurs[i]; + } + if (voiceInfo[0].restsBefore[i] > 0.0) { + total += voiceInfo[0].restsBefore[i]; } } - return bins.at(maxi).at(0); -} - + m_humdrum_text << "!!
    • Score duration: " << twoDigitRound(total) << "
    • " << endl; + int countSum = 0; + for (int i=0; i<(int)voiceInfo.size(); i++) { + countSum += (int)voiceInfo[i].phraseDurs.size(); + } + m_humdrum_text << "!!
    • Total segments: " << countSum << "
    • " << endl; -////////////////////////////// -// -// Tool_prange::getVpos == return the position on the staff given the diatonic pitch. -// and the staff. 1=bass, 2=treble. -// 3 = bottom line of clef, 0 = space below first ledger line. -// + double averageCount = countSum / (double)voiceInfo.size(); + averageCount = (int)(averageCount * 10 + 0.5) / 10.0; + m_humdrum_text << "!!
    • Average voice segments: " << averageCount << "
    • " << endl; -double Tool_prange::getVpos(double base7) { - double output = 0; - if (base7 < 4 * 7) { - // bass clef - output = base7 - (1 + 2*7); // D2 - } else { - // treble clef - output = base7 - (6 + 3*7); // B3 + double durSum = 0.0; + for (int i=0; i<(int)voiceInfo.size(); i++) { + for (int j=0; j<(int)voiceInfo[i].phraseDurs.size(); j++) { + durSum += voiceInfo[i].phraseDurs[j]; + } } - return output; + double averageDur = durSum / countSum; + averageDur = (int)(averageDur * 10 + 0.5) / 10.0; + m_humdrum_text << "!!
    • Average segment duration: " << averageDur << "
    • " << endl; + + m_humdrum_text << "!!
    • Voices: " << getVoiceInfo(infile) << "
    • " << endl; + + m_humdrum_text << "!!
    " << endl; } ////////////////////////////// // -// Tool_prange::getStaffBase7 -- return 1 if less than middle C; otherwise return 2. +// Tool_rphrase::printEmbeddedIndividualVoiceInfo -- // -int Tool_prange::getStaffBase7(int base7) { - if (base7 < 4 * 7) { - return 1; - } else { - return 2; - } -} - +void Tool_rphrase::printEmbeddedIndividualVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HumdrumFile& infile) { + m_humdrum_text << "!!" << endl; -////////////////////////////// -// -// Tool_prange::getMaxDiatonicIndex -- return the highest non-zero content. -// + m_humdrum_text << "!!" << voiceInfo.name << "" << endl; -int Tool_prange::getMaxDiatonicIndex(vector>& diatonic) { - for (int i=diatonic.size()-1; i>=0; i--) { - if (diatonic.at(i).at(0) != 0.0) { - return i; + double sounding = 0.0; + double resting = 0.0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.phraseDurs[i] > 0.0) { + sounding += voiceInfo.phraseDurs[i]; + } + if (voiceInfo.restsBefore[i] > 0.0) { + resting += voiceInfo.restsBefore[i]; } } - return -1; -} - + double total = sounding + resting; + double spercent = int(sounding/total * 100.0 + 0.5); + double rpercent = int(resting/total * 100.0 + 0.5); + m_humdrum_text << "!!" << sounding << "(" << spercent << "%)" << endl; + m_humdrum_text << "!!" << resting << "(" << rpercent << "%)" << endl; + // Segment count + m_humdrum_text << "!!"; + m_humdrum_text << voiceInfo.phraseDurs.size(); + m_humdrum_text << "" << endl; -////////////////////////////// -// -// Tool_prange::getMinDiatonicIndex -- return the lowest non-zero content. -// + // Segment duration average + m_humdrum_text << "!!"; + double sum = 0; + int count = 0; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + count++; + sum += voiceInfo.phraseDurs.at(i); + } + double average = int(sum / count * 100.0 + 0.5)/100.0; + m_humdrum_text << average; + m_humdrum_text << "" << endl; -int Tool_prange::getMinDiatonicIndex(vector>& diatonic) { - for (int i=0; i<(int)diatonic.size(); i++) { - if (diatonic.at(i).at(0) != 0.0) { - return i; + // Segments + m_humdrum_text << "!!"; + if (m_sortQ || m_reverseSortQ) { + vector> sortList; + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + sortList.emplace_back(voiceInfo.phraseDurs[i], i); + } + if (m_sortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + } else if (m_reverseSortQ) { + sort(sortList.begin(), sortList.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + } + for (int i=0; i<(int)sortList.size(); i++) { + int ii = sortList[i].second; + if (m_barlineQ) { + m_humdrum_text << "m" << voiceInfo.barStarts.at(ii) << ":"; + } + m_humdrum_text << "" << twoDigitRound(voiceInfo.phraseDurs.at(ii)) << ""; + if (i < (int)sortList.size() - 1) { + m_humdrum_text << " "; + } + } + } else { + for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { + if (voiceInfo.restsBefore.at(i) > 0) { + m_humdrum_text << "" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } else if (i > 0) { + // force display r:0 for section boundaries. + m_humdrum_text << "" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; + } + if (m_barlineQ) { + m_humdrum_text << "m" << voiceInfo.barStarts.at(i) << ":"; + } + m_humdrum_text << "" << twoDigitRound(voiceInfo.phraseDurs.at(i)) << ""; + if (i < (int)voiceInfo.phraseDurs.size() - 1) { + m_humdrum_text << " "; + } } } - return -1; + + m_humdrum_text << "" << endl; + m_humdrum_text << "!!" << endl; } ////////////////////////////// // -// Tool_prange::getMinDiatonicAcc -- return the lowest accidental. +// Tool_rphrase::fillCompositeInfo -- // -int Tool_prange::getMinDiatonicAcc(vector>& diatonic, int index) { - for (int i=1; i<(int)diatonic.at(index).size(); i++) { - if (diatonic.at(index).at(i) != 0.0) { - return i; - } - } - return -1; -} +void Tool_rphrase::fillCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { + compositeInfo.name = getCompositeLabel(infile); + vector noteStates; + getCompositeStates(noteStates, infile); + bool inPhraseQ = false; + int currentBarline = 0; + int startBarline = 1; + HumNum startTime = 0; + HumNum restBefore = 0; + HumNum startTimeRest = 0; + HumNum scoreDur = infile.getScoreDuration(); + HTp phraseStartTok = NULL; + for (int i=0; ifind("||") != string::npos) { + HumNum tdur = token->getDurationFromStart(); + if (tdur != scoreDur) { + // Only process if double barline is not at the end of the score. -int Tool_prange::getMaxDiatonicAcc(vector>& diatonic, int index) { - for (int i=(int)diatonic.at(index).size() - 1; i>0; i--) { - if (diatonic.at(index).at(i) != 0.0) { - return i; - } - } - return -1; -} + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + // ending a phrase + HumNum endTime = infile[i].getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + double value = duration.getFloat() / m_durUnit; + compositeInfo.phraseDurs.push_back(value); + compositeInfo.barStarts.push_back(startBarline); + compositeInfo.phraseStartToks.push_back(phraseStartTok); + phraseStartTok = NULL; + m_sumComposite += duration.getFloat() / m_durUnit; + m_pcountComposite++; + double rvalue = restBefore.getFloat() / m_durUnit; + compositeInfo.restsBefore.push_back(rvalue); + // record rest start + startTimeRest = endTime; + } else { + // Not in phrase, so not splitting a rest region. + // This case should be rare (starting a medial cadence + // with rests and potentially starting new section with rests. + } -////////////////////////////// -// -// Tool_prange::prepareRefmap -- -// + } + } + } -void Tool_prange::prepareRefmap(HumdrumFile& infile) { - vector refrecords = infile.getGlobalReferenceRecords(); - m_refmap.clear(); - HumRegex hre; - for (int i = (int)refrecords.size()-1; i>=0; i--) { - string key = refrecords[i]->getReferenceKey(); - string value = refrecords[i]->getReferenceValue(); - m_refmap[key] = value; - if (key.find("@") != string::npos) { - // create default value - hre.replaceDestructive(key, "", "@.*"); - if (m_refmap[key].empty()) { - m_refmap[key] = value; + if (infile[i].isBarline()) { + HTp token = infile.token(i, 0); + HumRegex hre; + if (hre.search(token, "(\\d+)")) { + currentBarline = hre.getMatchInt(1); + continue; } } - } - // fill in @{} templates (mostly for !!!title:) - int counter = 0; // prevent recursions - for (auto& entry : m_refmap) { - if (entry.second.find("@") != string::npos) { - while (hre.search(entry.second, "@\\{(.*?)\\}")) { - string key = hre.getMatch(1); - string value = m_refmap[key]; - hre.replaceDestructive(entry.second, value, "@\\{" + key + "\\}", "g"); - counter++; - if (counter > 1000) { - break; + + if (!infile[i].isData()) { + continue; + } + + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + if (noteStates[i] == 0) { + // ending a phrase + HumNum endTime = infile[i].getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + double value = duration.getFloat() / m_durUnit; + compositeInfo.phraseDurs.push_back(value); + compositeInfo.barStarts.push_back(startBarline); + compositeInfo.phraseStartToks.push_back(phraseStartTok); + phraseStartTok = NULL; + m_sumComposite += duration.getFloat() / m_durUnit; + m_pcountComposite++; + double rvalue = restBefore.getFloat() / m_durUnit; + compositeInfo.restsBefore.push_back(rvalue); + // record rest start + startTimeRest = endTime; + } else { + // continuing a phrase, so do nothing + } + } else { + // Not in phrase, so continue if rest; otherwise, + // if a note, then record a phrase start. + if (noteStates[i] == 0) { + // continuing a non-phrase, so do nothing + } else { + // starting a phrase + startTime = infile[i].getDurationFromStart(); + startBarline = currentBarline; + inPhraseQ = true; + // check if there are rests before the phrase + // The rest duration will be stored when the + // end of the next phrase is encountered. + if (startTimeRest >= 0) { + restBefore = startTime - startTimeRest; + } else { + restBefore = 0; } + phraseStartTok = infile.token(i, 0); } } } - // prepare title - if (m_refmap["title"].empty()) { - m_refmap["title"] = m_refmap["OTL"]; + if (inPhraseQ) { + // process last phrase + HumNum endTime = infile.getScoreDuration(); + HumNum duration = endTime - startTime; + double value = duration.getFloat() / m_durUnit; + compositeInfo.phraseDurs.push_back(value); + compositeInfo.barStarts.push_back(startBarline); + compositeInfo.phraseStartToks.push_back(phraseStartTok); + m_sumComposite += duration.getFloat() / m_durUnit; + m_pcountComposite++; + double rvalue = restBefore.getFloat() / m_durUnit; + compositeInfo.restsBefore.push_back(rvalue); + } + + if (m_markQ) { + markCompositePhraseStartsInScore(infile, compositeInfo); } } @@ -117057,395 +121870,403 @@ void Tool_prange::prepareRefmap(HumdrumFile& infile) { ////////////////////////////// // -// Tool_prange::getTitle -- +// Tool_rphrase::getCompositeLabel -- // -string Tool_prange::getTitle(void) { - string titlestring = "_00"; - HumRegex hre; - if (m_notitleQ) { - return ""; - } else if (m_titleQ) { - titlestring = m_title; - int counter = 0; - - if (titlestring.find("@") != string::npos) { - while (hre.search(titlestring, "@\\{(.*?)\\}")) { - string key = hre.getMatch(1); - string value = m_refmap[key]; - hre.replaceDestructive(titlestring, value, "@\\{" + key + "\\}", "g"); - counter++; - if (counter > 1000) { - break; - } - } - } - if (!titlestring.empty()) { - titlestring = "_00" + titlestring; - } - } else { - titlestring = m_refmap["title"]; - if (!titlestring.empty()) { - titlestring = "_00" + titlestring; +string Tool_rphrase::getCompositeLabel(HumdrumFile& infile) { + string voices; + for (int i=0; i kstarts = infile.getKernSpineStartList(); -////////////////////////////// -// -// Tool_prange::clearHistograms -- -// + string output = "composite "; + output += voices; -void Tool_prange::clearHistograms(vector >& bins, int start) { - int i; - for (i=start; i<(int)bins.size(); i++) { - bins[i].resize(40*11); - fill(bins[i].begin(), bins[i].end(), 0.0); - // bins[i].allowGrowth(0); - } - for (int i=0; i<(int)bins.size(); i++) { - if (bins[i].size() == 0) { - bins[i].resize(40*11); - fill(bins[i].begin(), bins[i].end(), 0.0); + + HumRegex hre; + + if (hre.search(voices, "^\\d+$")) { + int vint = stoi(voices); + if (vint != (int)kstarts.size()) { + output += "("; + output += to_string(kstarts.size()); + output += ")"; } + } else { + output += "("; + output += to_string(kstarts.size()); + output += ")"; } -} + return output; +} ////////////////////////////// // -// Tool_prange::printAnalysis -- +// Tool_rphrase::fillVoiceInfo -- // -void Tool_prange::printAnalysis(ostream& out, vector& midibins) { - if (m_percentileQ) { - printPercentile(out, midibins, m_percentile); - return; - } else if (m_rangeQ) { - double notesinrange = countNotesInRange(midibins, m_rangeL, m_rangeH); - out << notesinrange << endl; - return; +void Tool_rphrase::fillVoiceInfo(vector& voiceInfo, + vector& kstarts, HumdrumFile& infile) { + for (int i=0; i<(int)kstarts.size(); i++) { + fillVoiceInfo(voiceInfo.at(i), kstarts.at(i), infile); } +} - int i; - double normval = 1.0; - // print the pitch histogram +void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile) { + HTp current = kstart; - double fracL = 0.0; - double fracH = 0.0; - double fracA = 0.0; - double sum = accumulate(midibins.begin(), midibins.end(), 0.0); - if (m_normQ) { - normval = sum; - } - double runningtotal = 0.0; + bool inPhraseQ = false; + int currentBarline = 0; + int startBarline = 1; + HumNum startTime = 0; + HumNum restBefore = 0; + HumNum startTimeRest = 0; - out << "**keyno\t"; - if (m_pitchQ) { - out << "**pitch"; - } else { - out << "**kern"; - } - out << "\t**count"; - if (m_addFractionQ) { - out << "\t**fracL"; - out << "\t**fracA"; - out << "\t**fracH"; - } - out << "\n"; + HumNum scoreDur = infile.getScoreDuration(); + HTp phraseStartTok = NULL; + while (current) { - int base12; + // Split phrases at double barlines (medial cadences): + if (infile[current->getLineIndex()].isBarline()) { + HTp token = infile.token(current->getLineIndex(), 0); + if (token->find("||") != string::npos) { + HumNum tdur = token->getDurationFromStart(); + if (tdur != scoreDur) { + // Only process if double barline is not at the end of the score. - if (!m_reverseQ) { - for (i=0; i<(int)midibins.size(); i++) { - if (midibins[i] <= 0.0) { - continue; - } - if (m_diatonicQ) { - base12 = Convert::base7ToBase12(i); - } else { - base12 = i; - } - out << base12 << "\t"; - if (m_pitchQ) { - out << Convert::base12ToPitch(base12); - } else { - out << Convert::base12ToKern(base12); - } - out << "\t"; - out << midibins[i] / normval; - fracL = runningtotal/sum; - runningtotal += midibins[i]; - fracH = runningtotal/sum; - fracA = (fracH + fracL)/2.0; - fracL = (int)(fracL * 10000.0 + 0.5)/10000.0; - fracH = (int)(fracH * 10000.0 + 0.5)/10000.0; - fracA = (int)(fracA * 10000.0 + 0.5)/10000.0; - if (m_addFractionQ) { - out << "\t" << fracL; - out << "\t" << fracA; - out << "\t" << fracH; + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + + HumNum endTime = current->getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + double value = duration.getFloat() / m_durUnit; + voiceInfo.phraseDurs.push_back(value); + voiceInfo.barStarts.push_back(startBarline); + voiceInfo.phraseStartToks.push_back(phraseStartTok); + phraseStartTok = NULL; + m_sum += duration.getFloat() / m_durUnit; + m_pcount++; + double rvalue = restBefore.getFloat() / m_durUnit; + voiceInfo.restsBefore.push_back(rvalue); + + // record rest start + startTimeRest = endTime; + } else { + // Not in phrase, so not splitting a rest region. + // This case should be rare (starting a medial cadence + // with rests and potentially starting new section with rests. + } + + } } - out << "\n"; } - } else { - for (i=(int)midibins.size()-1; i>=0; i--) { - if (midibins[i] <= 0.0) { + + if (current->isBarline()) { + HumRegex hre; + if (hre.search(current, "(\\d+)")) { + currentBarline = hre.getMatchInt(1); + current = current->getNextToken(); continue; } - if (m_diatonicQ) { - base12 = Convert::base7ToBase12(i); + } + + if (current->isInstrumentName()) { + voiceInfo.name = current->substr(3); + } + if (!(current->isData() || (m_breathQ && (*current == "*breath")))) { + current = current->getNextToken(); + continue; + } + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + + if (inPhraseQ) { + // In phrase, so continue if still notes, otherwise + // if a rest, then record the currently active phrase + // that has ended. + if (current->isRest() || (*current == "*breath")) { + // ending a phrase + HumNum endTime = current->getDurationFromStart(); + HumNum duration = endTime - startTime; + startTime = -1; + inPhraseQ = false; + double value = duration.getFloat() / m_durUnit; + voiceInfo.phraseDurs.push_back(value); + voiceInfo.barStarts.push_back(startBarline); + voiceInfo.phraseStartToks.push_back(phraseStartTok); + phraseStartTok = NULL; + m_sum += duration.getFloat() / m_durUnit; + m_pcount++; + double rvalue = restBefore.getFloat() / m_durUnit; + voiceInfo.restsBefore.push_back(rvalue); + // record rest start + startTimeRest = endTime; } else { - base12 = i; + // continuing a phrase, so do nothing } - out << base12 << "\t"; - if (m_pitchQ) { - out << Convert::base12ToPitch(base12); + } else { + // Not in phrase, so continue if rest; otherwise, + // if a note, then record a phrase start. + if (current->isRest() || (*current == "*breath")) { + // continuing a non-phrase, so do nothing } else { - out << Convert::base12ToKern(base12); - } - out << "\t"; - out << midibins[i] / normval; - fracL = runningtotal/sum; - runningtotal += midibins[i]; - fracH = runningtotal/sum; - fracA = (fracH + fracL)/2.0; - fracL = (int)(fracL * 10000.0 + 0.5)/10000.0; - fracH = (int)(fracH * 10000.0 + 0.5)/10000.0; - fracA = (int)(fracA * 10000.0 + 0.5)/10000.0; - if (m_addFractionQ) { - out << "\t" << fracL; - out << "\t" << fracA; - out << "\t" << fracH; + // starting a phrase + startTime = current->getDurationFromStart(); + startBarline = currentBarline; + inPhraseQ = true; + // check if there are rests before the phrase + // The rest duration will be stored when the + // end of the next phrase is encountered. + if (startTimeRest >= 0) { + restBefore = startTime - startTimeRest; + } else { + restBefore = 0; + } + phraseStartTok = current; } - out << "\n"; } - } - - out << "*-\t*-\t*-"; - if (m_addFractionQ) { - out << "\t*-"; - out << "\t*-"; - out << "\t*-"; - } - out << "\n"; - out << "!!tessitura:\t" << getTessitura(midibins) << " semitones\n"; - - double mean = getMean12(midibins); - if (m_diatonicQ && (mean > 0)) { - mean = Convert::base7ToBase12(mean); + current = current->getNextToken(); } - out << "!!mean:\t\t" << mean; - out << " ("; - if (mean < 0) { - out << "unpitched"; - } else { - out << Convert::base12ToKern(int(mean+0.5)); + if (inPhraseQ) { + // process last phrase + HumNum endTime = kstart->getLine()->getOwner()->getScoreDuration(); + HumNum duration = endTime - startTime; + double value = duration.getFloat() / m_durUnit; + voiceInfo.phraseDurs.push_back(value); + voiceInfo.barStarts.push_back(startBarline); + voiceInfo.phraseStartToks.push_back(phraseStartTok); + m_sum += duration.getFloat() / m_durUnit; + m_pcount++; + double rvalue = restBefore.getFloat() / m_durUnit; + voiceInfo.restsBefore.push_back(rvalue); } - out << ")" << "\n"; - int median12 = getMedian12(midibins); - out << "!!median:\t" << median12; - out << " ("; - if (median12 < 0) { - out << "unpitched"; - } else { - out << Convert::base12ToKern(median12); + if (m_markQ) { + markPhraseStartsInScore(infile, voiceInfo); } - out << ")" << "\n"; - } ////////////////////////////// // -// Tool_prange::getMedian12 -- return the pitch on which half of pitches are above -// and half are below. +// Tool_rphrase::markCompositePhraseStartsInScore -- // -int Tool_prange::getMedian12(vector& midibins) { - double sum = accumulate(midibins.begin(), midibins.end(), 0.0); - - double cumsum = 0.0; - int i; - for (i=0; i<(int)midibins.size(); i++) { - if (midibins[i] <= 0.0) { - continue; +void Tool_rphrase::markCompositePhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& compositeInfo) { + stringstream buffer; + for (int i=0; i<(int)compositeInfo.phraseStartToks.size(); i++) { + HTp tok = compositeInfo.phraseStartToks.at(i); + string measure = ""; + if (m_barlineQ) { + measure = to_string(compositeInfo.barStarts.at(i)); } - cumsum += midibins[i]/sum; - if (cumsum >= 0.50) { - return i; + double duration = compositeInfo.phraseDurs.at(i); + buffer.str(""); + if (!measure.empty()) { + buffer << "m" << measure << ":"; } + buffer << twoDigitRound(duration); + int lineIndex = tok->getLineIndex(); + infile[lineIndex].setValue("auto", "rphrase-composite-start", buffer.str()); } - - return -1000; } ////////////////////////////// // -// Tool_prange::getMean12 -- return the interval between the highest and lowest -// pitch in terms if semitones. +// Tool_rphrase::twoDigitRound -- // -double Tool_prange::getMean12(vector& midibins) { - double top = 0.0; - double bottom = 0.0; - - int i; - for (i=0; i<(int)midibins.size(); i++) { - if (midibins[i] <= 0.0) { - continue; - } - top += midibins[i] * i; - bottom += midibins[i]; - - } - - if (bottom == 0) { - return -1000; - } - return top / bottom; +double Tool_rphrase::twoDigitRound(double input) { + return int(input * 100.0 + 0.499999) / 100.0; } ////////////////////////////// // -// Tool_prange::getTessitura -- return the interval between the highest and lowest -// pitch in terms if semitones. +// Tool_rphrase::markPhraseStartsInScore -- // -int Tool_prange::getTessitura(vector& midibins) { - int minn = -1000; - int maxx = -1000; - int i; - - for (i=0; i<(int)midibins.size(); i++) { - if (midibins[i] <= 0.0) { - continue; - } - if (minn < 0) { - minn = i; - } - if (maxx < 0) { - maxx = i; - } - if (minn > i) { - minn = i; +void Tool_rphrase::markPhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& voiceInfo) { + stringstream buffer; + for (int i=0; i<(int)voiceInfo.phraseStartToks.size(); i++) { + HTp tok = voiceInfo.phraseStartToks.at(i); + string measure = ""; + if (m_barlineQ) { + measure = to_string(voiceInfo.barStarts.at(i)); } - if (maxx < i) { - maxx = i; + double duration = voiceInfo.phraseDurs.at(i); + buffer.str(""); + if (!measure.empty()) { + buffer << "m" << measure << ":"; } + buffer << duration; + tok->setValue("auto", "rphrase-start", buffer.str()); } - if (m_diatonicQ) { - maxx = Convert::base7ToBase12(maxx); - minn = Convert::base7ToBase12(minn); - } - - return maxx - minn + 1; } -////////////////////////////// + +///////////////////////////////// // -// Tool_prange::countNotesInRange -- +// Tool_ruthfix::Tool_ruthfix -- Set the recognized options for the tool. // -double Tool_prange::countNotesInRange(vector& midibins, int low, int high) { - int i; - double sum = 0; - for (i=low; i<=high; i++) { - sum += midibins[i]; - } - return sum; +Tool_ruthfix::Tool_ruthfix(void) { + // add options here } -////////////////////////////// +///////////////////////////////// // -// Tool_prange::printPercentile -- +// Tool_ruthfix::run -- Do the main work of the tool. // -void Tool_prange::printPercentile(ostream& out, vector& midibins, double m_percentile) { - double sum = accumulate(midibins.begin(), midibins.end(), 0.0); - double runningtotal = 0.0; - int i; - for (i=0; i<(int)midibins.size(); i++) { - if (midibins[i] <= 0) { - continue; - } - runningtotal += midibins[i] / sum; - if (runningtotal >= m_percentile) { - out << i << endl; - return; - } +bool Tool_ruthfix::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; iisKern()) { + continue; } - } else { - ptr = strtok(buffer, " :"); - if (ptr != NULL) { - rangeL = Convert::kernToMidiNoteNumber(ptr); - ptr = strtok(NULL, " :"); - if (ptr != NULL) { - rangeH = Convert::kernToMidiNoteNumber(ptr); + insertCrossBarTies(infile, i); + } +} + + +void Tool_ruthfix::insertCrossBarTies(HumdrumFile& infile, int strand) { + HTp sstart = infile.getStrandStart(strand); + HTp send = infile.getStrandEnd(strand); + HTp s = sstart; + HTp lastnote = NULL; + bool barstart = true; + while (s != send) { + if (s->isBarline()) { + barstart = true; + } else if (s->isNote()) { + if (lastnote && barstart && (s->find("yy") != string::npos)) { + createTiedNote(lastnote, s); } + barstart = false; + lastnote = s; + } else if (s->isRest()) { + lastnote = NULL; + barstart = false; + } + s = s->getNextToken(); + if (!s) { + break; } } +} - if (rangeH < 0) { - rangeH = rangeL; - } - if (rangeL < 0) { rangeL = 0; } - if (rangeH < 0) { rangeH = 0; } - if (rangeL > 127) { rangeL = 127; } - if (rangeH > 127) { rangeH = 127; } - if (rangeL > rangeH) { - int temp = rangeL; - rangeL = rangeH; - rangeH = temp; - } +////////////////////////////// +// +// Tool_ruthfix::createTiedNote -- Does not work for chords. +// change 1E-X TO 2E-Xyy +// to [1E-X TO 2E-X] +// + +void Tool_ruthfix::createTiedNote(HTp left, HTp right) { + if (left->isChord() || right->isChord()) { + return; + } + auto loc = right->find("yy"); + if (loc != string::npos) { + left->insert(0, 1, '['); + right->replace(loc, 2, "]"); + } } @@ -117454,31 +122275,22 @@ void Tool_prange::getRange(int& rangeL, int& rangeH, const string& rangestring) ///////////////////////////////// // -// Tool_gridtest::Tool_recip -- Set the recognized options for the tool. +// Tool_sab2gs::Tool_sab2gs -- Set the recognized options for the tool. // -Tool_recip::Tool_recip(void) { - define("c|composite=b", "do composite rhythm analysis"); - define("a|append=b", "append composite analysis to input"); - define("p|prepend=b", "prepend composite analysis to input"); - define("r|replace=b", "replace **kern data with **recip data"); - define("x|attacks-only=b", "only mark lines with note attacks"); - define("G|ignore-grace-notes=b", "ignore grace notes"); - define("k|kern-spine=i:1", "analyze only given kern spine"); - define("K|all-spines=b", "analyze each kern spine separately"); - define("e|exinterp=s:**recip", "use the given exinterp for data output"); - define("n|kern-pitch=s:e", "note to add for '-e kern' option"); - define("kern=b", "equivalent to '-e kern' option"); +Tool_sab2gs::Tool_sab2gs(void) { + define("b|below=s:<", "Marker for displaying on next staff below"); + define("d|down=b", "Use only *down/*Xdown interpretations"); } -/////////////////////////////// +///////////////////////////////// // -// Tool_recip::run -- Primary interfaces to the tool. +// Tool_sab2gs::run -- Do the main work of the tool. // -bool Tool_recip::run(HumdrumFileSet& infiles) { +bool Tool_sab2gs::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i=0; k--) { - int fcount = infile[i].getFieldCount(); - int ktrack = m_kernspines[k]->getTrack(); - int insertj = -1; - for (int j=fcount-1; j>=0; j--) { - if (!infile.token(i, j)->isKern()) { - continue; - } - int track = infile.token(i, j)->getTrack(); - if (track != ktrack) { - continue; - } - if (insertj < 0) { - insertj = j; - } - infile[i].appendToken(insertj, cfile.token(i, j)->getText()); - // infile.token(i, insertj+1)->setTrack(remapping[k]); - } - } - } +void Tool_sab2gs::initialize(void) { + m_belowMarker = getString("below"); + m_downQ = getBoolean("down"); } ////////////////////////////// // -// Tool_recip::doCompositeAnalysis -- +// Tool_sab2gs::processFile -- // -void Tool_recip::doCompositeAnalysis(HumdrumFile& infile) { - - // Calculate composite rhythm **recip spine: +void Tool_sab2gs::processFile(HumdrumFile& infile) { - vector composite(infile.getLineCount()); - for (int i=0; i<(int)composite.size(); i++) { - composite[i] = infile[i].getDuration(); + vector spines; + infile.getSpineStartList(spines); + vector kernSpines; + for (int i=0; i<(int)spines.size(); i++) { + if (spines[i]->isKern()) { + kernSpines.push_back(spines[i]); + } } - - int kernQ = false; - if (m_exinterp.find("kern") != std::string::npos) { - kernQ = true; -// cerr << "KERN ON" << endl; + if (kernSpines.size() != 3) { + // Not valid for processing kern spines, so return original: + m_humdrum_text << infile; + return; } - // convert durations to **recip strings - vector recips(composite.size()); - for (int i=0; i<(int)recips.size(); i++) { - if ((!m_graceQ) && (composite[i] == 0)) { - continue; - } - recips[i] = Convert::durationToRecip(composite[i]); - if (kernQ) { - recips[i] += m_kernpitch; -// cerr << "ADDING PITCH " << m_kernpitch << endl; - } + string belowMarker = hasBelowMarker(infile); + if (!belowMarker.empty()) { + m_hasBelowMarker = true; + m_belowMarker = belowMarker; } - if (getBoolean("append")) { - infile.appendDataSpine(recips, "", m_exinterp); - return; - } else if (getBoolean("prepend")) { - infile.prependDataSpine(recips, "", m_exinterp); - return; - } else { - infile.prependDataSpine(recips, "", m_exinterp); - infile.printFieldIndex(0, m_humdrum_text); - infile.clear(); - infile.readString(m_humdrum_text.str()); - } + adjustMiddleVoice(kernSpines[1]); + printGrandStaff(infile, kernSpines); } -////////////////////////////// +///////////////////////////// // -// Tool_recip::replaceKernWithRecip -- +// Tool_sab2gs::hasBelowMarker -- Returns below marker if found; otherwise, +// returns empty string. // -void Tool_recip::replaceKernWithRecip(HumdrumFile& infile) { - vector kspines = infile.getKernSpineStartList(); +string Tool_sab2gs::hasBelowMarker(HumdrumFile& infile) { + string output; HumRegex hre; - string expression = "[^q\\d.%\\]\\[]+"; - for (int i=0; iisKern()) { - continue; - } - HTp etok = infile.getStrandEnd(i); - HTp tok = stok; - while (tok && (tok != etok)) { - if (!tok->isData()) { - tok = tok->getNextToken(); - continue; - } - if (tok->isNull()) { - tok = tok->getNextToken(); + if (m_hasCrossStaff) { + // Search backwards since if there is a below marker, it will be more + // likely found at the bottom of the score. + for (int i=infile.getLineCount()-1; i<=0; i--) { + if (infile[i].hasSpines()) { continue; } - if (tok->find('q') != string::npos) { - if (m_graceQ) { - tok->setText("q"); - } else { - tok->setText("."); - } - } else { - hre.replaceDestructive(*tok, "", expression, "g"); + if (hre.search(infile.token(i, 0), "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=]+)\\s*=\\s*below\\s*$")) { + output = hre.getMatch(1); + break; } - tok = tok->getNextToken(); } } - - for (int i=0; i<(int)kspines.size(); i++) { - kspines[i]->setText(m_exinterp); - } - + return output; } - -////////////////////////////// +/////////////////////////////// // -// Tool_recip::initialize -- +// Tool_sab2gs::printGrandStaff -- // -void Tool_recip::initialize(HumdrumFile& infile) { - m_kernspines = infile.getKernSpineStartList(); - m_graceQ = !getBoolean("ignore-grace-notes"); +void Tool_sab2gs::printGrandStaff(HumdrumFile& infile, vector& starts) { + bool foundData = false; - m_exinterp = getString("exinterp"); - if (m_exinterp.empty()) { - m_exinterp = "**recip"; - } else if (m_exinterp[0] != '*') { - m_exinterp.insert(0, "*"); - } - if (m_exinterp[1] != '*') { - m_exinterp.insert(0, "*"); + vector ktracks(starts.size()); + for (int i=0; i<(int)starts.size(); i++) { + ktracks.at(i) = starts.at(i)->getTrack(); } - m_kernpitch = getString("kern-pitch"); - - if (getBoolean("kern")) { - m_exinterp = "**kern"; + for (int i=0; i& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; } - return status; -} - - -bool Tool_restfill::run(const string& indata, ostream& out) { - HumdrumFile infile; - infile.readStringNoRhythm(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; } - return status; -} - - -bool Tool_restfill::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; } - return status; -} - - -bool Tool_restfill::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - infile.createLinesFromTokens(); - return true; + fcount++; + m_humdrum_text << "*"; + nextIndex++; + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << "*"; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Ignore the second kern spine as it does not exist yet in the + // output data. + nextIndex++; + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += "*"; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << "*^"; + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << "*"; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; } ////////////////////////////// // -// Tool_restfill::initialize -- +// Tool_sab2gs::printSpineMerge -- Merge second and third spines, moving non-kern spines +// after the second one to the end of the line (null interpretations); // -void Tool_restfill::initialize(void) { - m_hiddenQ = getBoolean("hidden-rests"); - m_exinterp = getString("exinterp"); - if (m_exinterp.empty()) { - m_exinterp = "**kern"; +void Tool_sab2gs::printSpineMerge(HumdrumFile& infile, int index, vector& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; + } + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << "*"; + nextIndex++; + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << "*"; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Save the second kern spine as it does not exist yet in the + // output data. + // HTp savedKernToken = infile.token(index, nextIndex); + nextIndex++; + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += "*"; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; } - if (m_exinterp.compare(0, 2, "**") != 0) { - if (m_exinterp.compare(0, 1, "*") != 0) { - m_exinterp = "**" + m_exinterp; - } else { - m_exinterp = "*" + m_exinterp; - } + fcount++; + m_humdrum_text << "*v"; + nextIndex++; + // Now printed the saved second **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; } - -} - - - -////////////////////////////// -// -// Tool_restfill::processFile -- -// - -void Tool_restfill::processFile(HumdrumFile& infile) { - - vector starts; - infile.getSpineStartList(starts, m_exinterp); - vector process(starts.size(), false); - for (int i=0; i<(int)starts.size(); i++) { - process[i] = hasBlankMeasure(starts[i]); - if (process[i]) { - starts[i]->setText("**temp-kern"); + m_humdrum_text << "*v"; + fcount++; + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; } + fcount++; + nextIndex++; + m_humdrum_text << "*"; } - infile.analyzeStructure(); - for (int i=0; i<(int)starts.size(); i++) { - if (!process[i]) { - continue; + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; } - starts[i]->setText("**kern"); - fillInRests(starts[i]); + fcount++; + m_humdrum_text << postData; } + m_humdrum_text << endl; } ////////////////////////////// // -// Tool_restfill::hasBlankMeasure -- +// Tool_sab2gs::printSwappedLine -- move the second **kern spine immediately after +// the third one, and move any non-kern spines after then end of the line. // -bool Tool_restfill::hasBlankMeasure(HTp start) { - bool foundcontent = false; - HTp current = start; - int founddata = false; - while (current) { - - if (current->isBarline()) { - if (founddata && !foundcontent) { - return true; - } - foundcontent = false; - founddata = false; - current = current->getNextToken(); - continue; - } - if (!current->isData()) { - current = current->getNextToken(); - continue; +void Tool_sab2gs::printSwappedLine(HumdrumFile& infile, int index, vector& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; } - founddata = true; - if (!current->isNull()) { - foundcontent = true; + if (fcount > 0) { + m_humdrum_text << "\t"; } - current = current->getNextToken(); - + fcount++; + m_humdrum_text << token; + nextIndex++; } - return false; -} - - - -////////////////////////////// -// -// Tool_restfill::fillInRests -- -// Also deal with cases where the last measure does not end in a barline. -// - -void Tool_restfill::fillInRests(HTp start) { - HTp current = start; - HTp firstcell = NULL; - int founddata = false; - bool foundcontent = false; - HumNum lasttime = 0; - HumNum currtime = 0; - HumNum duration = 0; - while (current) { - if (current->isBarline()) { - if (firstcell) { - lasttime = firstcell->getDurationFromStart(); - } - currtime = getNextTime(current); - if (firstcell && founddata && !foundcontent) { - duration = currtime - lasttime; - addRest(firstcell, duration); - } - firstcell = NULL; - founddata = false; - foundcontent = false; - current = current->getNextToken(); - lasttime = currtime; - continue; - } - if (!current->isData()) { - current = current->getNextToken(); - continue; + // Must be on the first **kern spine: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << infile.token(index, nextIndex); + nextIndex++; + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; } - if (current->getDuration() == 0) { - // grace-note line, so ignore - current = current->getNextToken(); - continue; + if (fcount > 0) { + m_humdrum_text << "\t"; } - founddata = true; - if (!current->isNull()) { - foundcontent = true; + m_humdrum_text << token; + nextIndex++; + } + // Second **kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Save the second kern spine as it does not exist yet in the + // output data. + HTp savedKernToken = infile.token(index, nextIndex++); + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; } - if (!firstcell) { - firstcell = current; + if (!postData.empty()) { + postData += "\t"; } - current = current->getNextToken(); + nextIndex++; + postData += *token; } -} - - - -////////////////////////////// -// -// Tool_restfill::addRest -- -// - -void Tool_restfill::addRest(HTp cell, HumNum duration) { - if (!cell) { + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; return; } - string text = Convert::durationToRecip(duration); - text += "r"; - if (m_hiddenQ) { - text += "yy"; + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; } - cell->setText(text); + fcount++; + m_humdrum_text << infile.token(index, nextIndex++); + // Now printed the saved second **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << savedKernToken; + fcount++; + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << token; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; } ////////////////////////////// // -// Tool_restfill::getNextTime -- +// Tool_sab2gs::printReducedLine -- remove the contents of the second **kern +// spine, and move any non-kernspines after it to become after the third **kern spine // -HumNum Tool_restfill::getNextTime(HTp token) { - HTp current = token; - while (current) { - if (current->isData()) { - return current->getDurationFromStart(); +void Tool_sab2gs::printReducedLine(HumdrumFile& infile, int index, vector& ktracks) { + // First print all non-kern spines at the start of the line: + int nextIndex = 0; + int fcount = 0; + for (int i=0; iisKern()) { + break; } - current = current->getNextToken(); + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << token; + nextIndex++; } - return token->getOwner()->getOwner()->getScoreDuration(); -} - - - - - - -///////////////////////////////// -// -// Tool_rid::Tool_rid -- Set the recognized options for the tool. -// - -Tool_rid::Tool_rid(void) { - // Humdrum Toolkit classic rid options: - define("D|all-data=b", "remove all data records"); - define("d|null-data=b", "remove null data records"); - define("G|all-global=b", "remove all global comments"); - define("g|null-global=b", "remove null global comments"); - define("I|all-interpretation=b", "remove all interpretation records"); - define("i|null-interpretation=b", "remove null interpretation records"); - define("L|all-local-comment=b", "remove all local comments"); - define("l|1|null-local-comment=b", "remove null local comments"); - define("T|all-tandem-interpretation=b", "remove all tandem interpretations"); - define("U|u=b", "remove unnecessary (duplicate ex. interps."); - define("k|consider-kern-only=b", "for -d, only consider **kern spines."); - define("V=b", "negate filtering effect of program."); - define("H|no-humdrum-syntax=b", "equivalent to -GLIMd."); - - // additional options - define("M|all-barlines=b", "remove measure lines"); - define("C|all-comments=b", "remove all comment lines"); - define("c=b", "remove global and local comment lines"); -} - - - -///////////////////////////////// -// -// Tool_rid::run -- Do the main work of the tool. -// - -bool Tool_rid::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisKern()) { + cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + return; } - return status; -} - - -bool Tool_rid::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + // Print the first **kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; } - return status; -} - - -bool Tool_rid::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + fcount++; + m_humdrum_text << infile.token(index, nextIndex++); + // Next print all non-kern spines after first **kern spine: + for (int i=nextIndex; iisKern()) { + break; + } + if (fcount > 0) { + m_humdrum_text << "\t"; + } + m_humdrum_text << token; + nextIndex++; } - return status; -} - - -bool Tool_rid::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; -} - - - -////////////////////////////// -// -// Tool_rid::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. -// - -void Tool_rid::initialize(void) { - option_D = getBoolean("D"); - option_d = getBoolean("d"); - option_G = getBoolean("G"); - option_g = getBoolean("g"); - option_I = getBoolean("I"); - option_i = getBoolean("i"); - option_L = getBoolean("L"); - option_l = getBoolean("l"); - option_T = getBoolean("T"); - option_U = getBoolean("U"); - option_M = getBoolean("M"); - option_C = getBoolean("C"); - option_c = getBoolean("c"); - option_k = getBoolean("k"); - option_V = getBoolean("V"); - - if (getBoolean("no-humdrum-syntax")) { - // remove all Humdrum file structure - option_G = option_L = option_I = option_M = option_d = 1; - } -} - - - -////////////////////////////// -// -// Tool_rid::processFile -- -// - -void Tool_rid::processFile(HumdrumFile& infile) { - int setcount = 1; // disabled for now. - - HumRegex hre; - int revQ = option_V; - - // if bibliographic/reference records are not suppressed - // print the !!!!SEGMENT: marker if present. - if ((setcount > 1) && (!option_G)) { - infile.printNonemptySegmentLabel(m_humdrum_text); - } - - for (int i=0; iisKern()) { + cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Ignore the second kern spine as it does not exist yet in the + // output data. + nextIndex++; + // Then store any non-kern spines between the second and third kern spines to + // append to the end of the data line later. + string postData; + for (int i=nextIndex; iisKern()) { + break; + } + if (!postData.empty()) { + postData += "\t"; + } + nextIndex++; + postData += *token; + } + // Third kern spine must be **kern data: + if (!infile.token(index, nextIndex)->isKern()) { + cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; + return; + } + // Now print the third kern spine: + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << infile.token(index, nextIndex++); + // Now print the non-kern spines after the third **kern spine (or rather just + // all spines including any other **kern spines, although current requirement + // is that there are only three **kern spines. + for (int i=nextIndex; i 0) { + m_humdrum_text << "\t"; + } + fcount++; + nextIndex++; + m_humdrum_text << token; + } + // Finally print any non-kern spines after the second **kern spine: + if (!postData.empty()) { + if (fcount > 0) { + m_humdrum_text << "\t"; + } + fcount++; + m_humdrum_text << postData; + } + m_humdrum_text << endl; +} - // non-classical options: - if (option_M && infile[i].isBarline()) { - // remove all measure lines - if (revQ) { - m_humdrum_text << infile[i] << "\n"; - } - continue; - } - if (option_C && infile[i].isComment()) { - // remove all comments (local & global) - if (revQ) { - m_humdrum_text << infile[i] << "\n"; - } - continue; - } - if (option_c && (infile[i].isLocalComment() || - infile[i].isGlobalComment())) { - // remove all comments (local & global) - if (revQ) { - m_humdrum_text << infile[i] << "\n"; - } - continue; - } +////////////////////////////// +// +// Tool_sab2gs::adjustMiddleVoice -- +// - // got past all test, so print the current line: - if (!revQ) { - m_humdrum_text << infile[i] << "\n"; - } - } -} +void Tool_sab2gs::adjustMiddleVoice(HTp spineStart) { + HTp current = spineStart; + // staff: +1 = top staff, -1 = bottom staff + // when on top staff, force stem down, or on bottom staff, force stem up + // when on bottom staff add "<" marker after pitch (or rest) to move to + // bottom staff. Staff choice is selected by clef: clefG2 is for top staff + // and staffF4 is for bottom staff. Chords are not expected. + int staff = 0; + string replacement = "$1" + m_belowMarker; + HumRegex hre; + while (current) { + if (*current == "*-") { + break; + } + if (!m_downQ && current->isClef()) { + if (current->substr(0, 7) == "*clefG2") { + staff = 1; + // suppress clef: + string text = "*x" + current->substr(1); + current->setText(text); + } else if (current->substr(0, 7) == "*clefF4") { + staff = -1; + // suppress clef: + string text = "*x" + current->substr(1); + current->setText(text); + } + } else if (current->isInterpretation()) { + if (*current == "*down") { + staff = -1; + } else if (*current == "*Xdown") { + staff = 1; + } + } else if ((staff != 0) && current->isData()) { + if (current->isNull()) { + // nothing to do with token + current = current->getNextToken(); + continue; + } + if (staff > 0) { + // force stems down or add stem down to non-rest notes + if (hre.search(current, "[/\\\\]")) { + string value = hre.replaceCopy(current, "\\", "/", "g"); + if (value != *current) { + current->setText(value); + } + current = current->getNextToken(); + continue; + } if (current->isRest()) { + current = current->getNextToken(); + continue; + } else { + string value = *current; + value += "\\"; + current->setText(value); + current = current->getNextToken(); + continue; + } + } else if (staff < 0) { + // force stems up or add stem up to non-rest notes + if (hre.search(current, "[/\\\\]")) { + string value = hre.replaceCopy(current, "\\", "/", "g"); + if (value != *current) { + current->setText(value); + } + current = current->getNextToken(); + continue; + } if (current->isRest()) { + // Do not at stem direction to rests + } else { + // Force stem up (assuming not a chord, although it should not matter): + string value = hre.replaceCopy(current, "/", "$"); + if (value != *current) { + current->setText(value); + } + } + // Add < after pitch (and accidental and qualifiers) to display + // on staff below. + m_hasCrossStaff = true; + string output = hre.replaceCopy(current, replacement, "([A-Ga-gr]+[-#nXYxy]*)", "g"); + if (output != *current) { + current->setText(output); + } + } + } + current = current->getNextToken(); + } +} ///////////////////////////////// // -// Tool_rphrase::Tool_rphrase -- Set the recognized options for the tool. +// Tool_satb2gs::Tool_satb2gs -- Set the recognized options for the tool. // -Tool_rphrase::Tool_rphrase(void) { - define("a|average=b", "calculate average length of rest-phrases by score"); - define("A|all-average=b", "calculate average length of rest-phrases for all scores"); - define("B|no-breath=b", "ignore breath interpretations"); - define("c|composite|collapse=b", "collapse all voices into single part"); - define("d|duration-unit=d:2.0", "duration units, default: 2.0 (minims/half notes)"); - define("f|filename=b", "include filename in output analysis"); - define("F|full-filename=b", "include full filename location in output analysis"); - define("I|no-info=b", "do not display summary info"); - define("l|longa=b", "display minim length of longas"); - define("m|b|measure|barline=b", "include barline numbers in output analysis"); - define("mark=b", "mark starts of phrases in score"); - define("s|sort=b", "sort phrases by short to long length"); - define("S|reverse-sort=b", "sort phrases by long to short length"); - define("u|url-type=s", "URL type (jrp, 1520s) for hyperlink"); - define("z|squeeze=b", "squeeze notation"); - define("close=b", "close details element initially"); +Tool_satb2gs::Tool_satb2gs(void) { + // no options } ///////////////////////////////// // -// Tool_rphrase::run -- Do the main work of the tool. +// Tool_satb2gs::run -- Do the main work of the tool. // -bool Tool_rphrase::run(HumdrumFileSet& infiles) { +bool Tool_satb2gs::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i kernStarts = infile.getKernSpineStartList(); - vector voiceInfo(kernStarts.size()); - Tool_rphrase::VoiceInfo compositeInfo; - - if (m_compositeQ) { - fillCompositeInfo(compositeInfo, infile); - } else { - fillVoiceInfo(voiceInfo, kernStarts, infile); - } - - if (m_longaQ) { - markLongaDurations(infile); - } - - if ((!m_allAverageQ) && (!m_markQ)) { - if (m_line == 1) { - if (m_compositeQ) { - m_free_text << "Filename"; - if (!m_urlType.empty()) { - m_free_text << "\tVHV"; - } - m_free_text << "\tVoice"; - m_free_text << "\tComp seg count"; - if (m_averageQ) { - m_free_text << "\tAvg comp seg dur"; - } - m_free_text << "\tComposite seg durs"; - m_free_text << endl; - } else { - m_free_text << "Filename"; - if (!m_urlType.empty()) { - m_free_text << "\tVHV"; - } - m_free_text << "\tVoice"; - m_free_text << "\tSounding dur"; - m_free_text << "\tResting dur"; - m_free_text << "\tTotal dur"; - m_free_text << "\tSeg count"; - if (m_averageQ) { - m_free_text << "\tSeg dur average"; - } - m_free_text << "\tSegment durs"; - m_free_text << endl; - } - } - if (m_compositeQ) { - if (m_compositeQ) { - m_line++; - } - printVoiceInfo(compositeInfo); - } else { - printVoiceInfo(voiceInfo); - } - } - - if (m_markQ) { - outputMarkedFile(infile, voiceInfo, compositeInfo); - if (m_squeezeQ) { - m_humdrum_text << "!!!verovio: evenNoteSpacing" << endl; - } - } - -} - - - -////////////////////////// -// -// Tool_rphrase::markLongaDuratios -- -// +void Tool_satb2gs::processFile(HumdrumFile& infile) { + vector> tracks; + getTrackInfo(tracks, infile); -void Tool_rphrase::markLongaDurations(HumdrumFile& infile) { - string longrdf; - for (int i=0; iisKern()) { - continue; - } - if (token->find(longrdf) != string::npos) { - HumNum duration = token->getTiedDuration(); - stringstream value; - value.str(""); - value << duration.getFloat() / m_durUnit; - token->setValue("auto", "rphrase-longa", value.str()); + if (infile[i].isData()) { + if (!dataQ) { + printSpineSplitLine(tracks); } + dataQ = true; } - } -} - - - -////////////////////////////// -// -// Tool_rphrase::outputMarkedFile -- -// - -void Tool_rphrase::outputMarkedFile(HumdrumFile& infile, vector& voiceInfo, - Tool_rphrase::VoiceInfo& compositeInfo) { - m_free_text.clear(); - m_free_text.str(""); - for (int i=0; iisKern()) { - continue; - } - string lotext = token->getValue("auto", "rphrase-longa"); - if (!lotext.empty()) { - hasLonga = true; - break; - } - } - } - - - bool hasLo = false; - for (int j=0; jisKern()) { - continue; - } - string lotext = token->getValue("auto", "rphrase-start"); - if (!lotext.empty()) { - hasLo = true; - break; - } - } - - // search for composite phrase info - bool hasGlo = false; - if (infile[index].isData()) { - string glotext = infile[index].getValue("auto", "rphrase-composite-start"); - if (!glotext.empty()) { - hasGlo = true; - } - } - - if (hasGlo) { - string glotext = infile[index].getValue("auto", "rphrase-composite-start"); - m_humdrum_text << "!!LO:TX:b:B:color=" << m_compositeLengthColor << ":t=" << glotext << endl; - } +void Tool_satb2gs::printRegularLine(HumdrumFile& infile, int line, + vector>& tracks) { - if (hasLonga) { - for (int j=0; jisKern()) { - m_humdrum_text << "!"; - } else { - string value = token->getValue("auto", "rphrase-longa"); - if (value.empty()) { - m_humdrum_text << "!"; - } else { - m_humdrum_text << "!LO:TX:a:B:color=silver:t=" << value; - } - } - if (j < infile[index].getFieldCount() - 1) { - m_humdrum_text << "\t"; - } - } - m_humdrum_text << endl; + int spinecount = infile[line].getFieldCount(); + int track; + HTp token; + vector>> tokens; + tokens.resize(5); + for (int i=0; i<(int)tracks.size(); i++) { + tokens[i].resize(tracks[i].size()); } - if (hasLo) { - for (int j=0; jisKern()) { - m_humdrum_text << "!"; - } else { - string value = token->getValue("auto", "rphrase-start"); - if (value.empty()) { - m_humdrum_text << "!"; - } else { - m_humdrum_text << "!LO:TX:a:B:color=" << m_voiceLengthColor << ":t=" << value; + // store tokens in output order: + for (int i=0; i<(int)tracks.size(); i++) { + for (int j=0; j<(int)tracks[i].size(); j++) { + int target = tracks[i][j]; + for (int k=0; kgetTrack(); + if (track != target) { + continue; } - } - if (j < infile[index].getFieldCount() - 1) { - m_humdrum_text << "\t"; + tokens[i][j].push_back(token); } } - m_humdrum_text << endl; } - m_humdrum_text << infile[index] << endl; -} - + int counter = 0; + HTp top; + HTp bot; + HTp inner; + HTp outer; + bool suppressQ; + // now print in output order, but hide fermatas + // in the alto and tenor parts if there are fermatas + // int the soprano and bass parts respectively. + for (int i=0; i<(int)tokens.size(); i++) { + for (int j=0; j<(int)tokens[i].size(); j++) { + switch (i) { + case 0: + case 2: + case 4: + // non-kern spines + for (int k=0; k<(int)tokens[i][j].size(); k++) { + m_humdrum_text << tokens[i][j][k]; + counter++; + if (counter < spinecount) { + m_humdrum_text << "\t"; + } + } + break; -////////////////////////////// -// -// Tool_rphrase::getCompositeStates -- -// + case 1: + case 3: + top = tokens[i][0][0]; + bot = tokens[i][1][0]; + if (i == 1) { + // tenor: top is inner + inner = top; + outer = bot; + } else { + // alto: bottom is inner + inner = bot; + outer = top; + } + if (inner->hasFermata() && outer->hasFermata()) { + suppressQ = true; + } else { + suppressQ = false; + } -void Tool_rphrase::getCompositeStates(vector& noteStates, HumdrumFile& infile) { - noteStates.resize(infile.getLineCount()); - fill(noteStates.begin(), noteStates.end(), -1); - for (int i=0; iisKern()) { - continue; - } - if (token->isRest()) { - continue; - } else if (token->isNull()) { - HTp resolve = token->resolveNull(); - if (!resolve) { - continue; - } else if (resolve->isRest()) { - continue; - } else { - value = 1; + for (int k=0; k<(int)tokens[i][j].size(); k++) { + token = tokens[i][j][k]; + if (suppressQ && ((void*)token == (void*)inner)) { + string value = *token; + // Make fermata invisible by adding 'y' after it: + for (int m=0; m<(int)value.size(); m++) { + m_humdrum_text << value[m]; + if (value[m] == ';') { + if (m < (int)value.size() - 1) { + if (value.at(m+1) != 'y') { + m_humdrum_text << 'y'; + } + } else { + m_humdrum_text << 'y'; + } + } + } + } else { + m_humdrum_text << token; + } + counter++; + if (counter < spinecount) { + m_humdrum_text << "\t"; + } + } break; - } - } else { - value = 1; - break; } } - noteStates[i] = value; } + + m_humdrum_text << endl; } ////////////////////////////// // -// Tool_rphrase::printVoiceInfo -- +// Tool_satb2gs::printTerminatorLine -- Print the terminator line in the +// output data. // -void Tool_rphrase::printVoiceInfo(vector& voiceInfo) { - for (int i=(int)voiceInfo.size() - 1; i>=0; i--) { - if (!m_compositeQ) { - m_line++; +void Tool_satb2gs::printTerminatorLine(vector>& tracks) { + int count = getNewTrackCount(tracks); + for (int i=0; i>& tracks) { + int count = getNewTrackCount(tracks); + int counter = 0; - if (urlType == "jrp") { - m_free_text << "=HYPERLINK(\"https://verovio.humdrum.org/?file=jrp/\" & "; - m_free_text << "LEFT(A" << m_line << ", 3) & \"/\" & A" << m_line << " & "; - m_free_text << "\".krn&filter=" << command << "&k=ey\", LEFT(A" << m_line; - m_free_text << ", FIND(\"-\", A" << m_line << ") - 1))"; - } else if (urlType == "1520s") { - m_free_text << "=HYPERLINK(\"https://verovio.humdrum.org/?file=1520s/\" & A"; - m_free_text << m_line << " & \".krn&filter=" << command << "&k=ey\", LEFT(A"; - m_free_text << m_line << ", FIND(\"-\", A" << m_line << ") - 1))"; + for (int i=0; i<(int)tracks.size(); i++) { + switch (i) { + case 0: + case 2: + case 4: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; + case 1: + case 3: + m_humdrum_text << "*^"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + break; + } } + m_humdrum_text << endl; } ////////////////////////////// // -// Tool_rphrase::printVoiceInfo -- +// Tool_satb2gs::printSpineMergeLine -- // -void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) { - if (m_filenameQ) { - m_free_text << m_filename << "\t"; - } - if (!m_urlType.empty()) { - printHyperlink(m_urlType); - m_free_text << "\t"; - } - m_free_text << voiceInfo.name << "\t"; - - if (!m_compositeQ) { - double sounding = 0.0; - double resting = 0.0; - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - if (voiceInfo.phraseDurs[i] > 0.0) { - sounding += voiceInfo.phraseDurs[i]; - } - if (voiceInfo.restsBefore[i] > 0.0) { - resting += voiceInfo.restsBefore[i]; - } - } - double total = sounding + resting; - // double sounding_percent = int (sounding/total * 100.0 + 0.5); - // double resting_percent = int (sounding/total * 100.0 + 0.5); - - m_free_text << twoDigitRound(sounding) << "\t"; - m_free_text << twoDigitRound(resting) << "\t"; - m_free_text << twoDigitRound(total) << "\t"; - } +void Tool_satb2gs::printSpineMergeLine(vector>& tracks) { + int count = getNewTrackCount(tracks); + count += 2; + int counter; - m_free_text << voiceInfo.phraseDurs.size() << "\t"; + if (!tracks[2].empty()) { + // do not need to place merges on separate lines since they are + // separated by non-kern spine(s) between bass and soprano subspines. - if (m_averageQ) { - double sum = 0; - int count = 0; - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - count++; - sum += voiceInfo.phraseDurs.at(i); + counter = 0; + for (int i=0; i<(int)tracks.size(); i++) { + switch (i) { + case 0: + case 2: + case 4: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; + case 1: + case 3: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*v"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; + } } - m_free_text << int(sum / count * 100.0 + 0.5)/100.0 << "\t"; - } + m_humdrum_text << endl; - if (m_sortQ || m_reverseSortQ) { - vector> sortList; - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - sortList.emplace_back(voiceInfo.phraseDurs[i], i); - } - if (m_sortQ) { - sort(sortList.begin(), sortList.end(), - [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); - } else if (m_reverseSortQ) { - sort(sortList.begin(), sortList.end(), - [](const std::pair& a, const std::pair& b) { - return a.first > b.first; - }); - } + } else { + // Merges for tenor/bass and soprano/alto need to be placed + // on separate lines. - for (int i=0; i<(int)sortList.size(); i++) { - int ii = sortList[i].second; - if (m_barlineQ) { - m_free_text << "m" << voiceInfo.barStarts.at(ii) << ":"; - } - m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(ii)); - if (i < (int)sortList.size() - 1) { - m_free_text << " "; + // First merge tenor/bass (tracks[1]) + counter = 0; + for (int i=0; i<(int)tracks.size(); i++) { + switch (i) { + case 0: + case 2: + case 3: + case 4: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; + case 1: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*v"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; } } - } else { - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - if (voiceInfo.restsBefore.at(i) > 0) { - m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; - } else if (i > 0) { - // force display r:0 for section boundaries. - m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; - } - if (m_barlineQ) { - m_free_text << "m" << voiceInfo.barStarts.at(i) << ":"; - } - m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(i)); - if (i < (int)voiceInfo.phraseDurs.size() - 1) { - m_free_text << " "; + m_humdrum_text << endl; + + // Now merge soprano/alto (tracks[3]) + count--; + counter = 0; + for (int i=0; i<(int)tracks.size(); i++) { + switch (i) { + case 0: + case 2: + case 4: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; + case 1: + m_humdrum_text << "*"; + m_humdrum_text << "\t"; + counter++; + break; + case 3: + for (int j=0; j<(int)tracks[i].size(); j++) { + m_humdrum_text << "*v"; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + } + break; } } + m_humdrum_text << endl; } - - m_free_text << endl; } ////////////////////////////// // -// Tool_rphrase::printEmbeddedVoiceInfo -- +// Tool_satb2gs::getNewTrackCount -- Return the number of tracks (spines) +// in the output data (not counting subspines). // -void Tool_rphrase::printEmbeddedVoiceInfo(vector& voiceInfo, Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { - - m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;; - - m_humdrum_text << "!!@SCRIPT:" << endl; - m_humdrum_text << "!! function rphraseGotoMeasure(measure) {" << endl; - m_humdrum_text << "!! let target = `svg .measure.m-${measure}`;" << endl; - m_humdrum_text << "!! let element = document.querySelector(target);" << endl; - m_humdrum_text << "!! if (element) {" << endl; - m_humdrum_text << "!! element.scrollIntoViewIfNeeded({ behavior: 'smooth' });" << endl; - m_humdrum_text << "!! }" << endl; - m_humdrum_text << "!! }" << endl; - - m_humdrum_text << "!!@CONTENT:\n"; - if (m_compositeQ) { - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - } else { - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - } - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - - if (m_compositeQ) { - m_humdrum_text << "!!Composite rest phrasing\n"; - } else { - m_humdrum_text << "!!Voice rest phrasing\n"; - } - if (m_compositeQ) { - printEmbeddedCompositeInfo(compositeInfo, infile); - } else { - if (voiceInfo.size() > 0) { - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!" << endl; - for (int i=(int)voiceInfo.size() - 1; i>=0; i--) { - printEmbeddedIndividualVoiceInfo(voiceInfo[i], infile); - } - m_humdrum_text << "!!
    VoiceSoundingRestingSegmentsAverageSegment durations
    " << endl; - printEmbeddedVoiceInfoSummary(voiceInfo, infile); +int Tool_satb2gs::getNewTrackCount(vector>& tracks) { + int sum = 0; + for (int i=0; i<(int)tracks.size(); i++) { + for (int j=0; j<(int)tracks[i].size(); j++) { + sum++; } } - m_humdrum_text << "!!" << endl; - m_humdrum_text << "!!@@END: PREHTML" << endl; + // remove two spines that were merged into two others: + sum -= 2; + return sum; } ////////////////////////////// // -// Tool_rphrase::printEmbeddedCompositeInfo -- +// Tool_satb2gs::printHeaderLine -- // -void Tool_rphrase::printEmbeddedCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { - - m_humdrum_text << "!!
    " << endl; - m_humdrum_text << "!!
      " << endl; - m_humdrum_text << "!!
    • Composite segment count: " << compositeInfo.phraseDurs.size() << "
    • " << endl; - - if (!compositeInfo.phraseDurs.empty()) { - m_humdrum_text << "!!
    • Composite segment duration"; - if (compositeInfo.phraseDurs.size() != 1) { - m_humdrum_text << "s"; - } - m_humdrum_text << ": "; - if (m_sortQ || m_reverseSortQ) { - vector> sortList; - for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { - sortList.emplace_back(compositeInfo.phraseDurs[i], i); - } - if (m_sortQ) { - sort(sortList.begin(), sortList.end(), - [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); - } else if (m_reverseSortQ) { - sort(sortList.begin(), sortList.end(), - [](const std::pair& a, const std::pair& b) { - return a.first > b.first; - }); - } +void Tool_satb2gs::printHeaderLine(HumdrumFile& infile, int line, + vector>& tracks) { + int count = infile.getMaxTrack() - 2; - for (int i=0; i<(int)sortList.size(); i++) { - int ii = sortList[i].second; - if (m_barlineQ) { - m_humdrum_text << "m" << compositeInfo.barStarts.at(ii) << ":"; - } - m_humdrum_text << "" << twoDigitRound(compositeInfo.phraseDurs.at(ii)) << ""; - if (i < (int)sortList.size() - 1) { - m_humdrum_text << " "; - } - } - } else { - for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { - if (compositeInfo.restsBefore.at(i) > 0) { - m_humdrum_text << "" << twoDigitRound(compositeInfo.restsBefore.at(i)) << " "; - } else if (i > 0) { - // force display r:0 for section boundaries. - m_humdrum_text << "" << twoDigitRound(compositeInfo.restsBefore.at(i)) << " "; - } - if (m_barlineQ) { - m_humdrum_text << "m" << compositeInfo.barStarts.at(i) << ":"; + HTp token; + int counter = 0; + for (int i=0; i<(int)tracks.size(); i++) { + switch (i) { + case 0: + case 2: + case 4: + for (int j=0; j<(int)tracks[i].size(); j++) { + token = infile.token(line, tracks[i][j]-1); + m_humdrum_text << token; + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } } - m_humdrum_text << "" << twoDigitRound(compositeInfo.phraseDurs.at(i)) << ""; - if (i < (int)compositeInfo.phraseDurs.size() - 1) { - m_humdrum_text << " "; + break; + + case 1: + case 3: + token = infile.token(line, tracks[i][0]-1); + if (token->isInstrumentName()) { + // suppress instrument names, but keep blank name + // to force indent. + m_humdrum_text << "*I\""; + } else if (token->isInstrumentAbbreviation()) { + // suppress instrument abbreviations + m_humdrum_text << "*"; + } else if (token->isInstrumentDesignation()) { + // suppress instrument designations (such as *Itenor) + m_humdrum_text << "*"; + } else if (token->isClef()) { + vector clefs = getClefs(infile, line); + if (i == 1) { + if (clefs.size() == 4) { + m_humdrum_text << clefs[0]; + } else { + m_humdrum_text << "*clefF4"; + } + } else { + if (clefs.size() == 4) { + m_humdrum_text << clefs.back(); + } else { + m_humdrum_text << "*clefG2"; + } + } + } else { + m_humdrum_text << token; } - } + counter++; + if (counter < count) { + m_humdrum_text << "\t"; + } + break; } - m_humdrum_text << "
    • " << endl; + } + m_humdrum_text << endl; +} - if (m_averageQ && (compositeInfo.phraseDurs.size() > 1)) { - double sum = 0; - int count = 0; - for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) { - count++; - sum += compositeInfo.phraseDurs.at(i); - } - double average = int(sum / count * 100.0 + 0.5)/100.0; - m_humdrum_text << "!!
    • Average composite segment durations: " << average << "
    • " << endl; - } - m_humdrum_text << "!!
    • Voices: " << getVoiceInfo(infile) << "
    • " << endl; - if (m_durUnit != 2.0) { - m_humdrum_text << "!!
    • Duration unit: " << m_durUnit << "
    • " << endl; +////////////////////////////// +// +// Tool_satb2gs::getClefs -- get a list of the clefs on the current line. +// + +vector Tool_satb2gs::getClefs(HumdrumFile& infile, int line) { + vector output; + for (int i=0; iisKern()) { + continue; + } + if (token->isClef()) { + output.push_back(token); } } - - m_humdrum_text << "!!
    " << endl; - m_humdrum_text << "!!
    " << endl; + return output; } ////////////////////////////// // -// Tool_rphrase::getVoiceInfo -- +// Tool_satb2gs::getTrackInfo -- +// tracks 0 = list of spines before bass **kern spine +// tracks 1 = tenor and then bass **kern track numbers +// tracks 2 = aux. spines after after tenor and then after bass +// tracks 3 = soprano and then alto **kern track numbers +// tracks 4 = aux. spines after after soprano and then after alto // -string Tool_rphrase::getVoiceInfo(HumdrumFile& infile) { - vector kspines = infile.getKernSpineStartList(); - string vcount = to_string(kspines.size()); - string ocount; - for (int i=0; i>& tracks, HumdrumFile& infile) { + tracks.resize(5); + for (int i=0; i<(int)tracks.size(); i++) { + tracks[i].clear(); + } + vector sstarts; + infile.getSpineStartList(sstarts); + int track; + + // fill in tracks[0]: spines before first **kern spine + for (int i=0; i<(int)sstarts.size(); i++) { + if (sstarts[i]->isKern()) { break; } - if (infile[i].isReferenceRecord()) { - string key = infile[i].getReferenceKey(); - if (key == "voices") { - ocount = infile[i].getReferenceValue(); - } + track = sstarts[i]->getTrack(); + tracks[0].push_back(track); + } + + int kcount = 0; + + kcount = 0; + // Store tracks related to the tenor part: + for (int i=0; i<(int)sstarts.size(); i++) { + if (sstarts[i]->isKern()) { + kcount++; + } + if (kcount > 2) { + break; + } + if (kcount < 2) { + continue; + } + track = sstarts[i]->getTrack(); + if (sstarts[i]->isKern()) { + tracks[1].push_back(track); + } else { + tracks[2].push_back(track); } } - if (ocount.empty()) { - return vcount; + kcount = 0; + // Store tracks related to the bass part: + for (int i=0; i<(int)sstarts.size(); i++) { + if (sstarts[i]->isKern()) { + kcount++; + } + if (kcount > 1) { + break; + } + if (kcount < 1) { + continue; + } + track = sstarts[i]->getTrack(); + if (sstarts[i]->isKern()) { + tracks[1].push_back(track); + } else { + tracks[2].push_back(track); + } } - if (ocount != vcount) { - string output = ocount; - output += "("; - output += vcount; - output += ")"; - return output; - } else { - return vcount; + kcount = 0; + // Store tracks related to the soprano part: + for (int i=0; i<(int)sstarts.size(); i++) { + if (sstarts[i]->isKern()) { + kcount++; + } + if (kcount > 4) { + break; + } + if (kcount < 4) { + continue; + } + track = sstarts[i]->getTrack(); + if (sstarts[i]->isKern()) { + tracks[3].push_back(track); + } else { + tracks[4].push_back(track); + } + } + + kcount = 0; + // Store tracks related to the alto part: + for (int i=0; i<(int)sstarts.size(); i++) { + if (sstarts[i]->isKern()) { + kcount++; + } + if (kcount > 3) { + break; + } + if (kcount < 3) { + continue; + } + track = sstarts[i]->getTrack(); + if (sstarts[i]->isKern()) { + tracks[3].push_back(track); + } else { + tracks[4].push_back(track); + } } } @@ -118949,287 +123592,163 @@ string Tool_rphrase::getVoiceInfo(HumdrumFile& infile) { ////////////////////////////// // -// Tool_rphrase::printEmbeddedVoiceInfoSummary -- +// Tool_satb2gs::validateHeader -- Header cannot contain +// spine manipulators. // -void Tool_rphrase::printEmbeddedVoiceInfoSummary(vector& voiceInfo, HumdrumFile& infile) { - m_humdrum_text << "!!
      " << endl; - - double total = 0.0; - for (int i=0; i<(int)voiceInfo[0].phraseDurs.size(); i++) { - if (voiceInfo[0].phraseDurs[i] > 0.0) { - total += voiceInfo[0].phraseDurs[i]; +bool Tool_satb2gs::validateHeader(HumdrumFile& infile) { + for (int i=0; i 0.0) { - total += voiceInfo[0].restsBefore[i]; + if (!infile[i].isInterpretation()) { + continue; + } + HTp token = infile.token(i, 0); + if (token->isExclusive()) { + continue; + } + if (infile[i].isManipulator()) { + return false; } } - m_humdrum_text << "!!
    • Score duration: " << twoDigitRound(total) << "
    • " << endl; - int countSum = 0; - for (int i=0; i<(int)voiceInfo.size(); i++) { - countSum += (int)voiceInfo[i].phraseDurs.size(); - } - m_humdrum_text << "!!
    • Total segments: " << countSum << "
    • " << endl; + return true; +} - double averageCount = countSum / (double)voiceInfo.size(); - averageCount = (int)(averageCount * 10 + 0.5) / 10.0; - m_humdrum_text << "!!
    • Average voice segments: " << averageCount << "
    • " << endl; - double durSum = 0.0; - for (int i=0; i<(int)voiceInfo.size(); i++) { - for (int j=0; j<(int)voiceInfo[i].phraseDurs.size(); j++) { - durSum += voiceInfo[i].phraseDurs[j]; - } - } - double averageDur = durSum / countSum; - averageDur = (int)(averageDur * 10 + 0.5) / 10.0; - m_humdrum_text << "!!
    • Average segment duration: " << averageDur << "
    • " << endl; - m_humdrum_text << "!!
    • Voices: " << getVoiceInfo(infile) << "
    • " << endl; - m_humdrum_text << "!!
    " << endl; + +///////////////////////////////// +// +// Tool_scordatura::Tool_scordatura -- Set the recognized options for the tool. +// + +Tool_scordatura::Tool_scordatura(void) { + define("s|sounding=b", "generate sounding score"); + define("w|written=b", "generate written score"); + define("m|mark|marker=s:@", "marker to add to score"); + define("p|pitch|pitches=s", "list of pitches to mark"); + define("i|interval=s", "musical interval of marked pitches"); + define("I|is-sounding=s", "musical score is in sounding format for marks"); + define("c|chromatic=i:0", "chromatic interval of marked pitches"); + define("d|diatonic=i:0", "diatonic interval of marked pitches"); + define("color=s", "color marked pitches"); + define("string=s", "string number"); } -////////////////////////////// +///////////////////////////////// // -// Tool_rphrase::printEmbeddedIndividualVoiceInfo -- +// Tool_scordatura::run -- Do the main work of the tool. // -void Tool_rphrase::printEmbeddedIndividualVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HumdrumFile& infile) { - m_humdrum_text << "!!" << endl; - - m_humdrum_text << "!!" << voiceInfo.name << "" << endl; - - double sounding = 0.0; - double resting = 0.0; - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - if (voiceInfo.phraseDurs[i] > 0.0) { - sounding += voiceInfo.phraseDurs[i]; - } - if (voiceInfo.restsBefore[i] > 0.0) { - resting += voiceInfo.restsBefore[i]; - } +bool Tool_scordatura::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i" << sounding << "(" << spercent << "%)" << endl; - m_humdrum_text << "!!" << resting << "(" << rpercent << "%)" << endl; + return status; +} - // Segment count - m_humdrum_text << "!!"; - m_humdrum_text << voiceInfo.phraseDurs.size(); - m_humdrum_text << "" << endl; - // Segment duration average - m_humdrum_text << "!!"; - double sum = 0; - int count = 0; - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - count++; - sum += voiceInfo.phraseDurs.at(i); +bool Tool_scordatura::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } - double average = int(sum / count * 100.0 + 0.5)/100.0; - m_humdrum_text << average; - m_humdrum_text << "" << endl; + return status; +} - // Segments - m_humdrum_text << "!!"; - if (m_sortQ || m_reverseSortQ) { - vector> sortList; - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - sortList.emplace_back(voiceInfo.phraseDurs[i], i); - } - if (m_sortQ) { - sort(sortList.begin(), sortList.end(), - [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); - } else if (m_reverseSortQ) { - sort(sortList.begin(), sortList.end(), - [](const std::pair& a, const std::pair& b) { - return a.first > b.first; - }); - } - for (int i=0; i<(int)sortList.size(); i++) { - int ii = sortList[i].second; - if (m_barlineQ) { - m_humdrum_text << "m" << voiceInfo.barStarts.at(ii) << ":"; - } - m_humdrum_text << "" << twoDigitRound(voiceInfo.phraseDurs.at(ii)) << ""; - if (i < (int)sortList.size() - 1) { - m_humdrum_text << " "; - } - } + +bool Tool_scordatura::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); } else { - for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) { - if (voiceInfo.restsBefore.at(i) > 0) { - m_humdrum_text << "" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; - } else if (i > 0) { - // force display r:0 for section boundaries. - m_humdrum_text << "" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " "; - } - if (m_barlineQ) { - m_humdrum_text << "m" << voiceInfo.barStarts.at(i) << ":"; - } - m_humdrum_text << "" << twoDigitRound(voiceInfo.phraseDurs.at(i)) << ""; - if (i < (int)voiceInfo.phraseDurs.size() - 1) { - m_humdrum_text << " "; - } - } + out << infile; } + return status; +} - m_humdrum_text << "" << endl; - m_humdrum_text << "!!" << endl; + +bool Tool_scordatura::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_rphrase::fillCompositeInfo -- +// Tool_scordatura::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -void Tool_rphrase::fillCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) { - compositeInfo.name = getCompositeLabel(infile); - vector noteStates; - getCompositeStates(noteStates, infile); - - bool inPhraseQ = false; - int currentBarline = 0; - int startBarline = 1; - HumNum startTime = 0; - HumNum restBefore = 0; - HumNum startTimeRest = 0; - HumNum scoreDur = infile.getScoreDuration(); - HTp phraseStartTok = NULL; - - for (int i=0; ifind("||") != string::npos) { - HumNum tdur = token->getDurationFromStart(); - if (tdur != scoreDur) { - // Only process if double barline is not at the end of the score. - - if (inPhraseQ) { - // In phrase, so continue if still notes, otherwise - // if a rest, then record the currently active phrase - // that has ended. - - // ending a phrase - HumNum endTime = infile[i].getDurationFromStart(); - HumNum duration = endTime - startTime; - startTime = -1; - inPhraseQ = false; - double value = duration.getFloat() / m_durUnit; - compositeInfo.phraseDurs.push_back(value); - compositeInfo.barStarts.push_back(startBarline); - compositeInfo.phraseStartToks.push_back(phraseStartTok); - phraseStartTok = NULL; - m_sumComposite += duration.getFloat() / m_durUnit; - m_pcountComposite++; - double rvalue = restBefore.getFloat() / m_durUnit; - compositeInfo.restsBefore.push_back(rvalue); - - // record rest start - startTimeRest = endTime; - } else { - // Not in phrase, so not splitting a rest region. - // This case should be rare (starting a medial cadence - // with rests and potentially starting new section with rests. - } - - } - } +void Tool_scordatura::initialize(void) { + m_writtenQ = getBoolean("written"); + m_soundingQ = getBoolean("sounding"); + m_pitches.clear(); + m_marker = getString("mark"); + m_IQ = getBoolean("I"); + m_color = getString("color"); + if (getBoolean("pitches")) { + m_pitches = parsePitches(getString("pitches")); + } + m_cd = getBoolean("diatonic") && getBoolean("chromatic"); + m_interval.clear(); + if (m_cd) { + m_diatonic = getInteger("diatonic"); + m_chromatic = getInteger("chromatic"); + } else { + if (getBoolean("interval")) { + m_interval = getString("interval"); } + } + if ((abs(m_diatonic) > 28) || (abs(m_chromatic) > 48)) { + m_diatonic = 0; + m_chromatic = 0; + m_cd = false; + } + if (!m_pitches.empty()) { + prepareTranspositionInterval(); + } + m_string = getString("string"); +} - if (infile[i].isBarline()) { - HTp token = infile.token(i, 0); - HumRegex hre; - if (hre.search(token, "(\\d+)")) { - currentBarline = hre.getMatchInt(1); - continue; - } - } - if (!infile[i].isData()) { - continue; - } +////////////////////////////// +// +// Tool_scordatura::processFile -- +// - if (inPhraseQ) { - // In phrase, so continue if still notes, otherwise - // if a rest, then record the currently active phrase - // that has ended. - if (noteStates[i] == 0) { - // ending a phrase - HumNum endTime = infile[i].getDurationFromStart(); - HumNum duration = endTime - startTime; - startTime = -1; - inPhraseQ = false; - double value = duration.getFloat() / m_durUnit; - compositeInfo.phraseDurs.push_back(value); - compositeInfo.barStarts.push_back(startBarline); - compositeInfo.phraseStartToks.push_back(phraseStartTok); - phraseStartTok = NULL; - m_sumComposite += duration.getFloat() / m_durUnit; - m_pcountComposite++; - double rvalue = restBefore.getFloat() / m_durUnit; - compositeInfo.restsBefore.push_back(rvalue); - // record rest start - startTimeRest = endTime; - } else { - // continuing a phrase, so do nothing - } - } else { - // Not in phrase, so continue if rest; otherwise, - // if a note, then record a phrase start. - if (noteStates[i] == 0) { - // continuing a non-phrase, so do nothing - } else { - // starting a phrase - startTime = infile[i].getDurationFromStart(); - startBarline = currentBarline; - inPhraseQ = true; - // check if there are rests before the phrase - // The rest duration will be stored when the - // end of the next phrase is encountered. - if (startTimeRest >= 0) { - restBefore = startTime - startTimeRest; - } else { - restBefore = 0; - } - phraseStartTok = infile.token(i, 0); - } - } +void Tool_scordatura::processFile(HumdrumFile& infile) { + m_modifiedQ = false; + if (!m_pitches.empty()) { + markPitches(infile); + if (m_modifiedQ) { + addMarkerRdf(infile); + } } - if (inPhraseQ) { - // process last phrase - HumNum endTime = infile.getScoreDuration(); - HumNum duration = endTime - startTime; - double value = duration.getFloat() / m_durUnit; - compositeInfo.phraseDurs.push_back(value); - compositeInfo.barStarts.push_back(startBarline); - compositeInfo.phraseStartToks.push_back(phraseStartTok); - m_sumComposite += duration.getFloat() / m_durUnit; - m_pcountComposite++; - double rvalue = restBefore.getFloat() / m_durUnit; - compositeInfo.restsBefore.push_back(rvalue); + if (m_writtenQ || m_soundingQ) { + vector rdfs; + getScordaturaRdfs(rdfs, infile); + if (!rdfs.empty()) { + processScordaturas(infile, rdfs); + } } - if (m_markQ) { - markCompositePhraseStartsInScore(infile, compositeInfo); + if (m_modifiedQ) { + infile.createLinesFromTokens(); } } @@ -119237,403 +123756,376 @@ void Tool_rphrase::fillCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, Hum ////////////////////////////// // -// Tool_rphrase::getCompositeLabel -- +// Tool_scordatura::processScoredaturas -- // -string Tool_rphrase::getCompositeLabel(HumdrumFile& infile) { - string voices; - for (int i=0; i& rdfs) { + for (int i=0; i<(int)rdfs.size(); i++) { + processScordatura(infile, rdfs[i]); } +} - vector kstarts = infile.getKernSpineStartList(); - string output = "composite "; - output += voices; +////////////////////////////// +// +// Tool_scordatura::processScordatura -- +// +void Tool_scordatura::processScordatura(HumdrumFile& infile, HTp reference) { HumRegex hre; - if (hre.search(voices, "^\\d+$")) { - int vint = stoi(voices); - if (vint != (int)kstarts.size()) { - output += "("; - output += to_string(kstarts.size()); - output += ")"; + if (m_writtenQ) { + if (!hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s]+)\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*ITrd(-?\\d+)c(-?\\d+)\\b")) { + return; + } + } else if (m_soundingQ) { + if (!hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s]+)\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*Trd(-?\\d+)c(-?\\d+)\\b")) { + return; } - } else { - output += "("; - output += to_string(kstarts.size()); - output += ")"; } - return output; + string marker = hre.getMatch(1); + int diatonic = hre.getMatchInt(2); + int chromatic = hre.getMatchInt(3); + + if (diatonic == 0 && chromatic == 0) { + // nothing to do + return; + } + + flipScordaturaInfo(reference, diatonic, chromatic); + transposeMarker(infile, marker, diatonic, chromatic); } ////////////////////////////// // -// Tool_rphrase::fillVoiceInfo -- +// Tool_scordatura::transposeMarker -- // -void Tool_rphrase::fillVoiceInfo(vector& voiceInfo, - vector& kstarts, HumdrumFile& infile) { - for (int i=0; i<(int)kstarts.size(); i++) { - fillVoiceInfo(voiceInfo.at(i), kstarts.at(i), infile); + +void Tool_scordatura::transposeMarker(HumdrumFile& infile, const string& marker, int diatonic, int chromatic) { + m_transposer.setTranspositionDC(diatonic, chromatic); + for (int i=0; iisKern()) { + continue; + } + HTp sstop = infile.getStrandEnd(i); + transposeStrand(sstart, sstop, marker); } } -void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile) { - HTp current = kstart; - - bool inPhraseQ = false; - int currentBarline = 0; - int startBarline = 1; - HumNum startTime = 0; - - HumNum restBefore = 0; - HumNum startTimeRest = 0; - - HumNum scoreDur = infile.getScoreDuration(); - HTp phraseStartTok = NULL; - - while (current) { - - // Split phrases at double barlines (medial cadences): - if (infile[current->getLineIndex()].isBarline()) { - HTp token = infile.token(current->getLineIndex(), 0); - if (token->find("||") != string::npos) { - HumNum tdur = token->getDurationFromStart(); - if (tdur != scoreDur) { - // Only process if double barline is not at the end of the score. - - if (inPhraseQ) { - // In phrase, so continue if still notes, otherwise - // if a rest, then record the currently active phrase - // that has ended. - - HumNum endTime = current->getDurationFromStart(); - HumNum duration = endTime - startTime; - startTime = -1; - inPhraseQ = false; - double value = duration.getFloat() / m_durUnit; - voiceInfo.phraseDurs.push_back(value); - voiceInfo.barStarts.push_back(startBarline); - voiceInfo.phraseStartToks.push_back(phraseStartTok); - phraseStartTok = NULL; - m_sum += duration.getFloat() / m_durUnit; - m_pcount++; - double rvalue = restBefore.getFloat() / m_durUnit; - voiceInfo.restsBefore.push_back(rvalue); - - // record rest start - startTimeRest = endTime; - } else { - // Not in phrase, so not splitting a rest region. - // This case should be rare (starting a medial cadence - // with rests and potentially starting new section with rests. - } - - } - } - } - if (current->isBarline()) { - HumRegex hre; - if (hre.search(current, "(\\d+)")) { - currentBarline = hre.getMatchInt(1); - current = current->getNextToken(); - continue; - } - } +////////////////////////////// +// +// Tool_scordatura::transposeStrand -- +// - if (current->isInstrumentName()) { - voiceInfo.name = current->substr(3); - } - if (!(current->isData() || (m_breathQ && (*current == "*breath")))) { +void Tool_scordatura::transposeStrand(HTp sstart, HTp sstop, const string& marker) { + HTp current = sstart; + while (current && current != sstop) { + if (!current->isData()) { current = current->getNextToken(); continue; } - if (current->isNull()) { + if (current->isNull() || current->isRest()) { current = current->getNextToken(); continue; } - - if (inPhraseQ) { - // In phrase, so continue if still notes, otherwise - // if a rest, then record the currently active phrase - // that has ended. - if (current->isRest() || (*current == "*breath")) { - // ending a phrase - HumNum endTime = current->getDurationFromStart(); - HumNum duration = endTime - startTime; - startTime = -1; - inPhraseQ = false; - double value = duration.getFloat() / m_durUnit; - voiceInfo.phraseDurs.push_back(value); - voiceInfo.barStarts.push_back(startBarline); - voiceInfo.phraseStartToks.push_back(phraseStartTok); - phraseStartTok = NULL; - m_sum += duration.getFloat() / m_durUnit; - m_pcount++; - double rvalue = restBefore.getFloat() / m_durUnit; - voiceInfo.restsBefore.push_back(rvalue); - // record rest start - startTimeRest = endTime; - } else { - // continuing a phrase, so do nothing - } - } else { - // Not in phrase, so continue if rest; otherwise, - // if a note, then record a phrase start. - if (current->isRest() || (*current == "*breath")) { - // continuing a non-phrase, so do nothing - } else { - // starting a phrase - startTime = current->getDurationFromStart(); - startBarline = currentBarline; - inPhraseQ = true; - // check if there are rests before the phrase - // The rest duration will be stored when the - // end of the next phrase is encountered. - if (startTimeRest >= 0) { - restBefore = startTime - startTimeRest; - } else { - restBefore = 0; - } - phraseStartTok = current; - } + if (current->find(marker) != string::npos) { + transposeChord(current, marker); } - current = current->getNextToken(); } - if (inPhraseQ) { - // process last phrase - HumNum endTime = kstart->getLine()->getOwner()->getScoreDuration(); - HumNum duration = endTime - startTime; - double value = duration.getFloat() / m_durUnit; - voiceInfo.phraseDurs.push_back(value); - voiceInfo.barStarts.push_back(startBarline); - voiceInfo.phraseStartToks.push_back(phraseStartTok); - m_sum += duration.getFloat() / m_durUnit; - m_pcount++; - double rvalue = restBefore.getFloat() / m_durUnit; - voiceInfo.restsBefore.push_back(rvalue); - } - - if (m_markQ) { - markPhraseStartsInScore(infile, voiceInfo); - } } ////////////////////////////// // -// Tool_rphrase::markCompositePhraseStartsInScore -- +// Tool_scordatura::transposeChord -- // -void Tool_rphrase::markCompositePhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& compositeInfo) { - stringstream buffer; - for (int i=0; i<(int)compositeInfo.phraseStartToks.size(); i++) { - HTp tok = compositeInfo.phraseStartToks.at(i); - string measure = ""; - if (m_barlineQ) { - measure = to_string(compositeInfo.barStarts.at(i)); +void Tool_scordatura::transposeChord(HTp token, const string& marker) { + int scount = token->getSubtokenCount(); + if (scount == 1) { + string inputnote = *token; + string newtoken; + newtoken = transposeNote(inputnote); + token->setText(newtoken); + return; + } + vector subtokens; + subtokens = token->getSubtokens(); + for (int i=0; i<(int)subtokens.size(); i++) { + if (subtokens[i].find(marker) == string::npos) { + continue; } - double duration = compositeInfo.phraseDurs.at(i); - buffer.str(""); - if (!measure.empty()) { - buffer << "m" << measure << ":"; + string newtoken = transposeNote(subtokens[i]); + subtokens[i] = newtoken; + } + string newchord; + for (int i=0; i<(int)subtokens.size(); i++) { + newchord += subtokens[i]; + if (i<(int)subtokens.size() - 1) { + newchord += ' '; } - buffer << twoDigitRound(duration); - int lineIndex = tok->getLineIndex(); - infile[lineIndex].setValue("auto", "rphrase-composite-start", buffer.str()); } + token->setText(newchord); } ////////////////////////////// // -// Tool_rphrase::twoDigitRound -- +// Tool_scordatura::transposeNote -- // -double Tool_rphrase::twoDigitRound(double input) { - return int(input * 100.0 + 0.499999) / 100.0; +string Tool_scordatura::transposeNote(const string& note) { + HumRegex hre; + if (!hre.search(note, "(.*?)([A-Ga-g]+[-#]*)(.*)")) { + return note; + } + string pre = hre.getMatch(1); + string pitch = hre.getMatch(2); + string post = hre.getMatch(3); + HumPitch hpitch; + hpitch.setKernPitch(pitch); + m_transposer.transpose(hpitch); + string output; + output = pre; + output += hpitch.getKernPitch(); + output += post; + return output; } ////////////////////////////// // -// Tool_rphrase::markPhraseStartsInScore -- +// Tool_scordatura::flipScordaturaInfo -- // -void Tool_rphrase::markPhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& voiceInfo) { - stringstream buffer; - for (int i=0; i<(int)voiceInfo.phraseStartToks.size(); i++) { - HTp tok = voiceInfo.phraseStartToks.at(i); - string measure = ""; - if (m_barlineQ) { - measure = to_string(voiceInfo.barStarts.at(i)); - } - double duration = voiceInfo.phraseDurs.at(i); - buffer.str(""); - if (!measure.empty()) { - buffer << "m" << measure << ":"; - } - buffer << duration; - tok->setValue("auto", "rphrase-start", buffer.str()); +void Tool_scordatura::flipScordaturaInfo(HTp reference, int diatonic, int chromatic) { + diatonic *= -1; + chromatic *= -1; + string output; + if (m_writtenQ) { + output = "Trd"; + output += to_string(diatonic); + output += "c"; + output += to_string(chromatic); + } else if (m_soundingQ) { + output = "ITrd"; + output += to_string(diatonic); + output += "c"; + output += to_string(chromatic); + } else { + return; + } + HumRegex hre; + string token = *reference; + hre.replaceDestructive(token, output, "I?Trd-?\\dc-?\\d"); + if (token != *reference) { + m_modifiedQ = true; + reference->setText(token); } } - -///////////////////////////////// +////////////////////////////// // -// Tool_ruthfix::Tool_ruthfix -- Set the recognized options for the tool. +// Tool_scordatura::getScoredaturaRdfs -- // -Tool_ruthfix::Tool_ruthfix(void) { - // add options here +void Tool_scordatura::getScordaturaRdfs(vector& rdfs, HumdrumFile& infile) { + rdfs.clear(); + HumRegex hre; + for (int i=0; i Tool_scordatura::parsePitches(const string& input) { + HumRegex hre; + string value = input; + hre.replaceDestructive(value, "-", "\\s*-\\s*", "g"); + vector pieces; + hre.split(pieces, value, "[^A-Ga-g0-9-]+"); -bool Tool_ruthfix::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + HumPitch pitcher; + set output; + string p1; + string p2; + int d1; + int d2; + for (int i=0; i<(int)pieces.size(); i++) { + if (hre.search(pieces[i], "(.*)-(.*)")) { + // pitch range + p1 = hre.getMatch(1); + p2 = hre.getMatch(2); + d1 = Convert::kernToBase7(p1); + d2 = Convert::kernToBase7(p2); + if ((d1 < 0) || (d2 < 0) || (d1 > d2) || (d1 > 127) || (d2 > 127)) { + continue; + } + for (int j=d1; j<=d2; j++) { + output.insert(j); + } + } else { + // single pitch + d1 = Convert::kernToBase7(pieces[i]); + if ((d1 < 0) || (d1 > 127)) { + continue; + } + output.insert(d1); + } } - return status; -} - - -bool Tool_ruthfix::run(HumdrumFile& infile) { - insertCrossBarTies(infile); - return true; + return output; } ////////////////////////////// // -// Tool_ruthfix::insertCrossBarTies -- +// Tool_scordatura::markPitches -- // -void Tool_ruthfix::insertCrossBarTies(HumdrumFile& infile) { - int scount = infile.getStrandCount(); - if (scount == 0) { - // The input file was not read from a file but was created - // dynamically. The easiest thing to do is to reload to get the - // spine/strand information. - stringstream ss; - infile.createLinesFromTokens(); - ss << infile; - infile.readString(ss.str()); +void Tool_scordatura::markPitches(HumdrumFile& infile) { + for (int i=0; iisKern()) { + continue; + } + HTp sstop = infile.getStrandStop(i); + markPitches(sstart, sstop); } - scount = infile.getStrandCount(); +} - HTp token; - for (int i=0; iisKern()) { +void Tool_scordatura::markPitches(HTp sstart, HTp sstop) { + HTp current = sstart; + while (current && (current != sstop)) { + if (current->isNull() || current->isRest()) { + current = current->getNextToken(); continue; } - insertCrossBarTies(infile, i); + markPitches(current); + current = current->getNextToken(); } } -void Tool_ruthfix::insertCrossBarTies(HumdrumFile& infile, int strand) { - HTp sstart = infile.getStrandStart(strand); - HTp send = infile.getStrandEnd(strand); - HTp s = sstart; - HTp lastnote = NULL; - bool barstart = true; - while (s != send) { - if (s->isBarline()) { - barstart = true; - } else if (s->isNote()) { - if (lastnote && barstart && (s->find("yy") != string::npos)) { - createTiedNote(lastnote, s); - } - barstart = false; - lastnote = s; - } else if (s->isRest()) { - lastnote = NULL; - barstart = false; +void Tool_scordatura::markPitches(HTp token) { + vector subtokens = token->getSubtokens(); + int counter = 0; + for (int i=0; i<(int)subtokens.size(); i++) { + int dia = Convert::kernToBase7(subtokens[i]); + if (m_pitches.find(dia) != m_pitches.end()) { + counter++; + subtokens[i] += m_marker; } - s = s->getNextToken(); - if (!s) { - break; + } + if (counter == 0) { + return; + } + string newtoken; + for (int i=0; i<(int)subtokens.size(); i++) { + newtoken += subtokens[i]; + if (i < (int)subtokens.size() - 1) { + newtoken += ' '; } } + token->setText(newtoken); + m_modifiedQ = true; +} + + + +////////////////////////////// +// +// Tool_scordatura::addMarkerRdf -- +// + +void Tool_scordatura::addMarkerRdf(HumdrumFile& infile) { + string line = "!!!RDF**kern: "; + line += m_marker; + line += " = "; + if (!m_string.empty()) { + line += "string="; + line += m_string; + line += " "; + } + line += "scordatura="; + if (m_IQ) { + line += "I"; + } + line += "Tr"; + if (m_transposition.empty()) { + line += "XXX"; + } else { + line += m_transposition; + } + if (!m_color.empty()) { + line += ", color="; + line += m_color; + } + infile.appendLine(line); + m_modifiedQ = true; } ////////////////////////////// // -// Tool_ruthfix::createTiedNote -- Does not work for chords. -// change 1E-X TO 2E-Xyy -// to [1E-X TO 2E-X] +// Tool_scordatura::prepareTranspositionInterval -- // -void Tool_ruthfix::createTiedNote(HTp left, HTp right) { - if (left->isChord() || right->isChord()) { +void Tool_scordatura::prepareTranspositionInterval(void) { + m_transposition.clear(); + if (m_cd) { + m_transposition = "d"; + m_transposition += to_string(m_diatonic); + m_transposition += "c"; + m_transposition += to_string(m_chromatic); return; } - auto loc = right->find("yy"); - if (loc != string::npos) { - left->insert(0, 1, '['); - right->replace(loc, 2, "]"); + + if (m_interval.empty()) { + return; } + + HumTransposer trans; + trans.intervalToDiatonicChromatic(m_diatonic, m_chromatic, m_interval); + m_transposition = "d"; + m_transposition += to_string(m_diatonic); + m_transposition += "c"; + m_transposition += to_string(m_chromatic); } @@ -119642,22 +124134,41 @@ void Tool_ruthfix::createTiedNote(HTp left, HTp right) { ///////////////////////////////// // -// Tool_sab2gs::Tool_sab2gs -- Set the recognized options for the tool. +// Tool_semitones::Tool_semitones -- Set the recognized options for the tool. // -Tool_sab2gs::Tool_sab2gs(void) { - define("b|below=s:<", "Marker for displaying on next staff below"); - define("d|down=b", "Use only *down/*Xdown interpretations"); +Tool_semitones::Tool_semitones(void) { + define("1|first=b", "mark only the first note of intervals"); + define("2|second=b", "mark only the second note of intervals"); + define("A|O|no-analysis|no-output=b", "do not print analysis spines"); + define("I|no-input=b", "do not print input data spines"); + define("M|no-mark|no-marks=b", "do not mark notes"); + define("R|no-rests=b", "ignore rests"); + define("T|no-ties=b", "do not mark ties"); + define("X|include|only=s", "include only **kern tokens with given pattern"); + define("color=s:red", "mark color"); + define("c|cdata=b", "store resulting data as **cdata (allowing display in VHV"); + define("d|down=b", "highlight notes that that have a negative semitone interval"); + define("j|jump=i:3", "starting interval defining leaps"); + define("l|leap=b", "highlight notes that have leap motion"); + define("mark=s:@", "mark character"); + define("m|midi=b", "show MIDI note number for pitches"); + define("n|count=b", "output count of intervals being marked"); + define("p|pc=b", "output pitch classes from C=0 instead of MIDI notes for -m option"); + define("r|same|repeat|repeated=b", "highlight notes that are repeated "); + define("s|step=b", "highlight notes that have step-wise motion"); + define("u|up=b", "highlight notes that that have a positive semitone interval"); + define("x|exclude=s", "exclude **kern tokens with given pattern"); } ///////////////////////////////// // -// Tool_sab2gs::run -- Do the main work of the tool. +// Tool_semitones::run -- Do the main work of the tool. // -bool Tool_sab2gs::run(HumdrumFileSet& infiles) { +bool Tool_semitones::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i spines; - infile.getSpineStartList(spines); - vector kernSpines; - for (int i=0; i<(int)spines.size(); i++) { - if (spines[i]->isKern()) { - kernSpines.push_back(spines[i]); - } +void Tool_semitones::processFile(HumdrumFile& infile) { + m_markCount = 0; + for (int i=0; i 0) && !m_nomarkQ) { + m_humdrum_text << "!!!RDF**kern: "; + m_humdrum_text << m_marker; + m_humdrum_text << " = marked note"; + if (getBoolean("color")) { + m_humdrum_text << ", color=" << m_color; + } + m_humdrum_text << '\n'; } - - string belowMarker = hasBelowMarker(infile); - if (!belowMarker.empty()) { - m_hasBelowMarker = true; - m_belowMarker = belowMarker; + if (m_count) { + showCount(); } - - adjustMiddleVoice(kernSpines[1]); - printGrandStaff(infile, kernSpines); } -///////////////////////////// +////////////////////////////// // -// Tool_sab2gs::hasBelowMarker -- Returns below marker if found; otherwise, -// returns empty string. +// Tool_semitones::showCount -- Give a count for the number of +// intervals that were marked. // -string Tool_sab2gs::hasBelowMarker(HumdrumFile& infile) { - string output; - HumRegex hre; - if (m_hasCrossStaff) { - // Search backwards since if there is a below marker, it will be more - // likely found at the bottom of the score. - for (int i=infile.getLineCount()-1; i<=0; i--) { - if (infile[i].hasSpines()) { - continue; - } - if (hre.search(infile.token(i, 0), "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=]+)\\s*=\\s*below\\s*$")) { - output = hre.getMatch(1); - break; - } - } +void Tool_semitones::showCount(void) { + m_humdrum_text << "!!semitone_count: " << m_markCount; + if (m_repeatQ) { + m_humdrum_text << " REPEAT"; } - return output; + if (m_upQ) { + m_humdrum_text << " UP"; + } + if (m_downQ) { + m_humdrum_text << " DOWN"; + } + if (m_stepQ) { + m_humdrum_text << " STEP"; + } + if (m_leapQ) { + m_humdrum_text << " LEAP"; + } + if ((m_stepQ || m_leapQ) && (m_leap != 3)) { + m_humdrum_text << " JUMP:" << m_leap; + } + if (m_marker != "@") { + m_humdrum_text << " MARK:" << m_marker; + } + m_humdrum_text << '\n'; } -/////////////////////////////// +////////////////////////////// // -// Tool_sab2gs::printGrandStaff -- +// Tool_semitones::analyzeLine -- Append analysis spines after every **kern +// spine. // -void Tool_sab2gs::printGrandStaff(HumdrumFile& infile, vector& starts) { - bool foundData = false; - - vector ktracks(starts.size()); - for (int i=0; i<(int)starts.size(); i++) { - ktracks.at(i) = starts.at(i)->getTrack(); +void Tool_semitones::analyzeLine(HumdrumFile& infile, int line) { + int group = 0; + if (!infile[line].hasSpines()) { + m_humdrum_text << infile[line] << "\n"; + return; } - - for (int i=0; iisKern()) { + m_humdrum_text << token; + if (i < infile[line].getFieldCount() - 1) { + m_humdrum_text << '\t'; + } + continue; } - continue; } - if (foundData) { - printSwappedLine(infile, i, ktracks); - } else { - printReducedLine(infile, i, ktracks); + i = processKernSpines(infile, line, i, group++); + if (!m_noinputQ) { + if (i < infile[line].getFieldCount() - 1) { + m_humdrum_text << '\t'; + } } } + m_humdrum_text << '\n'; } + ////////////////////////////// // -// Tool_sab2gs::printSpineSplit -- Split second and third spines, moving non-kern spines -// after the second one to the end of the line (null interpretations); +// Tool_semitones::processKernSpine -- // -void Tool_sab2gs::printSpineSplit(HumdrumFile& infile, int index, vector& ktracks) { - // First print all non-kern spines at the start of the line: - int nextIndex = 0; - int fcount = 0; - for (int i=0; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; - } - fcount++; - m_humdrum_text << "*"; - nextIndex++; - } - // Must be on the first **kern spine: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; - return; - } - // Print the first **kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; - } - fcount++; - m_humdrum_text << "*"; - nextIndex++; - // Next print all non-kern spines after first **kern spine: - for (int i=nextIndex; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; - } - m_humdrum_text << "*"; - nextIndex++; - } - // Second **kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; - return; - } - // Ignore the second kern spine as it does not exist yet in the - // output data. - nextIndex++; - // Then store any non-kern spines between the second and third kern spines to - // append to the end of the data line later. - string postData; - for (int i=nextIndex; iisKern()) { - break; - } - if (!postData.empty()) { - postData += "\t"; - } - nextIndex++; - postData += "*"; - } - // Third kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; - return; - } - // Now print the third kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; - } - fcount++; - nextIndex++; - m_humdrum_text << "*^"; - // Now print the non-kern spines after the third **kern spine (or rather just - // all spines including any other **kern spines, although current requirement - // is that there are only three **kern spines. - for (int i=nextIndex; i 0) { - m_humdrum_text << "\t"; - } - fcount++; - nextIndex++; - m_humdrum_text << "*"; +int Tool_semitones::processKernSpines(HumdrumFile& infile, int line, int start, int kspine) { + HTp token = infile.token(line, start); + if (!token->isKern()) { + return start; } - // Finally print any non-kern spines after the second **kern spine: - if (!postData.empty()) { - if (fcount > 0) { - m_humdrum_text << "\t"; + int track = token->getTrack(); + vector toks; + toks.push_back(token); + for (int i=start+1; igetTrack(); + if (newtrack == track) { + toks.push_back(newtok); + continue; } - fcount++; - m_humdrum_text << postData; + break; } - m_humdrum_text << endl; -} + int toksize = (int)toks.size(); + // calculate intervals/MIDI note numbers if appropriate + bool allQ = m_stepQ || m_leapQ || m_upQ || m_downQ || m_repeatQ; + bool dirQ = m_upQ || m_downQ; + bool typeQ = m_stepQ || m_leapQ; + vector intervals(toksize); + if (infile[line].isData()) { + for (int i=0; i 0) && (value < m_leap)) { + markInterval(toks[i]); + } else if (m_downQ && m_stepQ && (value < 0) && (value > -m_leap)) { + markInterval(toks[i]); + } else if (!dirQ && m_stepQ && (value != 0) && (abs(value) < m_leap)) { + markInterval(toks[i]); -////////////////////////////// -// -// Tool_sab2gs::printSpineMerge -- Merge second and third spines, moving non-kern spines -// after the second one to the end of the line (null interpretations); -// + } else if (m_upQ && m_leapQ && (value > 0) && (value >= m_leap)) { + markInterval(toks[i]); + } else if (m_downQ && m_leapQ && (value < 0) && (value <= -m_leap)) { + markInterval(toks[i]); + } else if (!dirQ && m_leapQ && (value != 0) && (abs(value) >= m_leap)) { + markInterval(toks[i]); -void Tool_sab2gs::printSpineMerge(HumdrumFile& infile, int index, vector& ktracks) { - // First print all non-kern spines at the start of the line: - int nextIndex = 0; - int fcount = 0; - for (int i=0; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; + } else if (m_repeatQ && (value == 0)) { + markInterval(toks[i]); + } else if (!typeQ && m_upQ && (value > 0)) { + markInterval(toks[i]); + } else if (!typeQ && m_downQ && (value < 0)) { + markInterval(toks[i]); + } + } } - fcount++; - m_humdrum_text << "*"; - nextIndex++; - } - // Must be on the first **kern spine: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; - return; - } - // Print the first **kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; } - fcount++; - m_humdrum_text << "*"; - nextIndex++; - // Next print all non-kern spines after first **kern spine: - for (int i=nextIndex; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; + + // print the **kern fields + if (!m_noinputQ) { + for (int i=0; iisKern()) { - cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; - return; } - // Save the second kern spine as it does not exist yet in the - // output data. - // HTp savedKernToken = infile.token(index, nextIndex); - nextIndex++; - // Then store any non-kern spines between the second and third kern spines to - // append to the end of the data line later. - string postData; - for (int i=nextIndex; iisKern()) { - break; - } - if (!postData.empty()) { - postData += "\t"; + + // then print the parallel analysis fields + + if (!m_noanalysisQ) { + if (!m_noinputQ) { + m_humdrum_text << '\t'; + } else if (m_noinputQ && (kspine != 0)) { + m_humdrum_text << '\t'; } - nextIndex++; - postData += "*"; - } - // Third kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; - return; - } - // Now print the third kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; - } - fcount++; - m_humdrum_text << "*v"; - nextIndex++; - // Now printed the saved second **kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; - } - m_humdrum_text << "*v"; - fcount++; - // Now print the non-kern spines after the third **kern spine (or rather just - // all spines including any other **kern spines, although current requirement - // is that there are only three **kern spines. - for (int i=nextIndex; i 0) { - m_humdrum_text << "\t"; + if (!infile[line].isData()) { + if (infile[line].isLocalComment()) { + printTokens("!", toksize); + } else if (infile[line].isInterpretation()) { + if (toks[0]->compare(0, 2, "**") == 0) { + if (m_cdataQ) { + printTokens("**cdata", toksize); + } else if (m_midiQ) { + if (m_pcQ) { + printTokens("**mpc", toksize); + } else { + printTokens("**mnn", toksize); + } + } else { + printTokens("**tti", toksize); + } + } else { + for (int i=0; i 0) { - m_humdrum_text << "\t"; + // print twelve-tone analyses. + string value; + for (int i=0; i& ktracks) { - // First print all non-kern spines at the start of the line: - int nextIndex = 0; - int fcount = 0; - for (int i=0; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; - } - fcount++; - m_humdrum_text << token; - nextIndex++; +void Tool_semitones::markInterval(HTp token) { + if (!token->isData()) { + return; } - // Must be on the first **kern spine: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; + if (!token->isKern()) { return; } - // Print the first **kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; + if (token->isNull()) { + return; } - fcount++; - m_humdrum_text << infile.token(index, nextIndex); - nextIndex++; - // Next print all non-kern spines after first **kern spine: - for (int i=nextIndex; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; - } - m_humdrum_text << token; - nextIndex++; + if (token->isRest()) { + return; } - // Second **kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; + if (token->isUnpitched()) { return; } - // Save the second kern spine as it does not exist yet in the - // output data. - HTp savedKernToken = infile.token(index, nextIndex++); - // Then store any non-kern spines between the second and third kern spines to - // append to the end of the data line later. - string postData; - for (int i=nextIndex; iisKern()) { - break; + m_markCount++; + token = markNote(token, m_firstQ); + if (m_firstQ && !m_secondQ) { + return; + } + // find next note + HTp current = token->getNextToken(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - if (!postData.empty()) { - postData += "\t"; + if (current->isNull()) { + current = current->getNextToken(); + continue; } - nextIndex++; - postData += *token; + markNote(current, m_secondQ); + break; } - // Third kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; - return; +} + + + +////////////////////////////// +// +// Tool_semitones::markNote -- make note and any tied notes after it. +// Return the last note of a tied note (or the note if no tied notes +// after it). +// + +HTp Tool_semitones::markNote(HTp token, bool markQ) { + string subtok = token->getSubtoken(0); + bool hasTieEnd = false; + if (subtok.find('_') != string::npos) { + hasTieEnd = true; + } else if (subtok.find(']') != string::npos) { + hasTieEnd = true; } - // Now print the third kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; + + if (!(hasTieEnd && m_notiesQ)) { + if (markQ) { + addMarker(token); + } } - fcount++; - m_humdrum_text << infile.token(index, nextIndex++); - // Now printed the saved second **kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; + + bool hasTie = false; + if (subtok.find('[') != string::npos) { + hasTie = true; + } else if (subtok.find('_') != string::npos) { + hasTie = true; } - m_humdrum_text << savedKernToken; - fcount++; - // Now print the non-kern spines after the third **kern spine (or rather just - // all spines including any other **kern spines, although current requirement - // is that there are only three **kern spines. - for (int i=nextIndex; i 0) { - m_humdrum_text << "\t"; - } - fcount++; - nextIndex++; - m_humdrum_text << token; + + if (!hasTie) { + return token; } - // Finally print any non-kern spines after the second **kern spine: - if (!postData.empty()) { - if (fcount > 0) { - m_humdrum_text << "\t"; + HTp current = token->getNextToken(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - fcount++; - m_humdrum_text << postData; + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + subtok = current->getSubtoken(0); + bool hasTie = false; + if (subtok.find('[') != string::npos) { + hasTie = true; + } else if (subtok.find('_') != string::npos) { + hasTie = true; + } + if (!hasTie) { + if (subtok.find(']') != string::npos) { + markNote(current, markQ); + } + return current; + } else { + return markNote(current, markQ); + } + break; } - m_humdrum_text << endl; + return NULL; } ////////////////////////////// // -// Tool_sab2gs::printReducedLine -- remove the contents of the second **kern -// spine, and move any non-kernspines after it to become after the third **kern spine +// Tool_semitones::addMarker -- // -void Tool_sab2gs::printReducedLine(HumdrumFile& infile, int index, vector& ktracks) { - // First print all non-kern spines at the start of the line: - int nextIndex = 0; - int fcount = 0; - for (int i=0; iisKern()) { - break; - } - if (fcount > 0) { - m_humdrum_text << "\t"; +void Tool_semitones::addMarker(HTp token) { + if (!m_nomarkQ) { + string contents = m_marker; + contents += token->getText(); + token->setText(contents); + } +} + + + +////////////////////////////// +// +// Tool_semitones::printTokens -- +// + +void Tool_semitones::printTokens(const string& value, int count) { + for (int i=0; iisKern()) { - cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl; - return; +} + + + +/////////////////////////////// +// +// Tool_semitones::getTwelveToneIntervalString -- +// + +string Tool_semitones::getTwelveToneIntervalString(HTp token) { + if (token->isNull()) { + return "."; } - // Print the first **kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; + if (token->isRest()) { + if (m_midiQ) { + return "r"; + } else { + return "."; + } } - fcount++; - m_humdrum_text << infile.token(index, nextIndex++); - // Next print all non-kern spines after first **kern spine: - for (int i=nextIndex; iisKern()) { - break; + if (token->isUnpitched()) { + if (m_midiQ) { + return "R"; + } else { + return "."; } - if (fcount > 0) { - m_humdrum_text << "\t"; + } + if ((m_include.size() > 0) || (m_exclude.size() > 0)) { + int status = filterData(token); + if (status == 0) { + return "."; + } else if (status < 0) { + return "x"; // excluded note } - m_humdrum_text << token; - nextIndex++; } - // Second **kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl; - return; + string tok = token->getSubtoken(0); + if (tok.find(']') != string::npos) { + return "."; } - // Ignore the second kern spine as it does not exist yet in the - // output data. - nextIndex++; - // Then store any non-kern spines between the second and third kern spines to - // append to the end of the data line later. - string postData; - for (int i=nextIndex; iisKern()) { - break; - } - if (!postData.empty()) { - postData += "\t"; + if (tok.find('_') != string::npos) { + return "."; + } + int value = Convert::kernToMidiNoteNumber(tok); + + if (m_midiQ) { + string output; + if (m_pcQ) { + value = value % 12; } - nextIndex++; - postData += *token; + output = to_string(value); + return output; } - // Third kern spine must be **kern data: - if (!infile.token(index, nextIndex)->isKern()) { - cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl; - return; + + string nexttok = getNextNoteAttack(token); + if (nexttok.empty()) { + return "."; } - // Now print the third kern spine: - if (fcount > 0) { - m_humdrum_text << "\t"; + if (nexttok.find('r') != string::npos) { + // no interval since next note is a rest + return "r"; } - fcount++; - m_humdrum_text << infile.token(index, nextIndex++); - // Now print the non-kern spines after the third **kern spine (or rather just - // all spines including any other **kern spines, although current requirement - // is that there are only three **kern spines. - for (int i=nextIndex; i 0) { - m_humdrum_text << "\t"; + int value2 = Convert::kernToMidiNoteNumber(nexttok); + int interval = value2 - value; + string output = to_string(interval); + return output; +} + + + +/////////////////////////////// +// +// Tool_semitones::getNextNoteAttack -- Or rest. +// + +string Tool_semitones::getNextNoteAttack(HTp token) { + HTp current = token; + current = current->getNextToken(); + string tok; + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - fcount++; - nextIndex++; - m_humdrum_text << token; - } - // Finally print any non-kern spines after the second **kern spine: - if (!postData.empty()) { - if (fcount > 0) { - m_humdrum_text << "\t"; + if (current->isNull()) { + current = current->getNextToken(); + continue; } - fcount++; - m_humdrum_text << postData; + if (current->isRest()) { + if (!m_norestsQ) { + return "r"; + } else { + current = current->getNextToken(); + continue; + } + } + if (current->isUnpitched()) { + return "R"; + } + string tok = current->getSubtoken(0); + if (tok.find(']') != string::npos) { + current = current->getNextToken(); + continue; + } + if (tok.find('_') != string::npos) { + current = current->getNextToken(); + continue; + } + return tok; } - m_humdrum_text << endl; + + if (!current) { + return ""; + } + if (!current->isData()) { + return ""; + } + // Some other strange problem. + return "."; } + ////////////////////////////// // -// Tool_sab2gs::adjustMiddleVoice -- +// Tool_semitones::filterData -- select or deselect an interval based +// on regular expression pattern. Return true if the note should +// be kept; otherwise, return false. // -void Tool_sab2gs::adjustMiddleVoice(HTp spineStart) { - HTp current = spineStart; - // staff: +1 = top staff, -1 = bottom staff - // when on top staff, force stem down, or on bottom staff, force stem up - // when on bottom staff add "<" marker after pitch (or rest) to move to - // bottom staff. Staff choice is selected by clef: clefG2 is for top staff - // and staffF4 is for bottom staff. Chords are not expected. - int staff = 0; - string replacement = "$1" + m_belowMarker; +int Tool_semitones::filterData(HTp token) { + vector toks = getTieGroup(token); HumRegex hre; - while (current) { - if (*current == "*-") { - break; - } - if (!m_downQ && current->isClef()) { - if (current->substr(0, 7) == "*clefG2") { - staff = 1; - // suppress clef: - string text = "*x" + current->substr(1); - current->setText(text); - } else if (current->substr(0, 7) == "*clefF4") { - staff = -1; - // suppress clef: - string text = "*x" + current->substr(1); - current->setText(text); - } - } else if (current->isInterpretation()) { - if (*current == "*down") { - staff = -1; - } else if (*current == "*Xdown") { - staff = 1; + if (!m_exclude.empty()) { + for (int i=0; i<(int)toks.size(); i++) { + if (hre.search(toks[i], m_exclude)) { + return -1; } - } else if ((staff != 0) && current->isData()) { - if (current->isNull()) { - // nothing to do with token - current = current->getNextToken(); - continue; + } + return 1; + } else if (!m_include.empty()) { + for (int i=0; i<(int)toks.size(); i++) { + if (hre.search(toks[i], m_include)) { + return 1; } - if (staff > 0) { - // force stems down or add stem down to non-rest notes - if (hre.search(current, "[/\\\\]")) { - string value = hre.replaceCopy(current, "\\", "/", "g"); - if (value != *current) { - current->setText(value); - } - current = current->getNextToken(); - continue; - } if (current->isRest()) { - current = current->getNextToken(); - continue; - } else { - string value = *current; - value += "\\"; - current->setText(value); - current = current->getNextToken(); - continue; - } + } + return 0; + } + return 0; +} + + + +////////////////////////////// +// +// Tool_semitones::getTieGroup -- +// + +vector Tool_semitones::getTieGroup(HTp token) { + vector output; + if (!token) { + return output; + } + if (token->isNull()) { + return output; + } + if (!token->isData()) { + return output; + } + output.push_back(token); + if (token->isRest()) { + return output; + } + string subtok = token->getSubtoken(0); + bool continues = hasTieContinue(subtok); + HTp current = token; + while (continues) { + current = getNextNote(current); + if (!current) { + break; + } + string subtok = current->getSubtoken(0); + if (subtok.find(']') != string::npos) { + output.push_back(current); + break; + } + continues = hasTieContinue(subtok); + } + return output; +} - } else if (staff < 0) { - // force stems up or add stem up to non-rest notes - if (hre.search(current, "[/\\\\]")) { - string value = hre.replaceCopy(current, "\\", "/", "g"); - if (value != *current) { - current->setText(value); - } - current = current->getNextToken(); - continue; - } if (current->isRest()) { - // Do not at stem direction to rests - } else { - // Force stem up (assuming not a chord, although it should not matter): - string value = hre.replaceCopy(current, "/", "$"); - if (value != *current) { - current->setText(value); - } - } - // Add < after pitch (and accidental and qualifiers) to display - // on staff below. - m_hasCrossStaff = true; - string output = hre.replaceCopy(current, replacement, "([A-Ga-gr]+[-#nXYxy]*)", "g"); - if (output != *current) { - current->setText(output); - } - } + + +////////////////////////////// +// +// Tool_semitones::hasTieContinue -- +// + +bool Tool_semitones::hasTieContinue(const string& value) { + if (value.find('_') != string::npos) { + return true; + } + if (value.find('[') != string::npos) { + return true; + } + return false; +} + + + +////////////////////////////// +// +// getNextNote -- +// + +HTp Tool_semitones::getNextNote(HTp token) { + HTp current = token->getNextToken(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); + continue; } - current = current->getNextToken(); + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + break; } + return current; } + ///////////////////////////////// // -// Tool_satb2gs::Tool_satb2gs -- Set the recognized options for the tool. +// Tool_shed::Tool_shed -- Set the recognized options for the tool. // -Tool_satb2gs::Tool_satb2gs(void) { - // no options +Tool_shed::Tool_shed(void) { + define("s|spine|spines=s", "list of spines to process"); + define("e|expression=s", "regular expression"); + define("E|exclusion-expression=s", "regular expression to skip"); + define("x|exclusive-interpretations=s", "apply only to spine types in list"); + define("k|kern=b", "apply only to **kern data"); + define("X=s", "defineable exclusive interpretation x"); + define("Y=s", "defineable exclusive interpretation y"); + define("Z=s", "defineable exclusive interpretation z"); } ///////////////////////////////// // -// Tool_satb2gs::run -- Do the main work of the tool. +// Tool_shed::run -- Do the main work of the tool. // -bool Tool_satb2gs::run(HumdrumFileSet& infiles) { +bool Tool_shed::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; i extra = addToExInterpList(); + for (int i=0; i<(int)extra.size(); i++) { + m_exinterps.push_back(extra[i]); + } + } -void Tool_satb2gs::processFile(HumdrumFile& infile) { - vector> tracks; - getTrackInfo(tracks, infile); + m_search = m_searches.at(index); + m_replace = m_replaces.at(index); + m_option = m_options.at(index); - if ((tracks[1].size() != 2) || (tracks[3].size() != 2)) { - cerr << "Warning: not processing data since there must be at least four **kern spines" << endl; - return; + m_grepoptions = ""; + if (m_option.find("i") != std::string::npos) { + m_grepoptions += "i"; } - - bool goodHeader = validateHeader(infile); - if (!goodHeader) { - cerr << "Warning: no spine manipulations allows within header, not processing file" << endl; - return; + if (m_option.find("g") != std::string::npos) { + m_grepoptions += "g"; } - bool dataQ = false; - for (int i=0; i>& tracks) { + m_data = true; // process data + m_barline = false; // process barline + m_exinterp = false; // process exclusive interpretations + m_interpretation = false; // process interpretations (other than exinterp + // and spine manipulators). - int spinecount = infile[line].getFieldCount(); - int track; - HTp token; - vector>> tokens; - tokens.resize(5); - for (int i=0; i<(int)tracks.size(); i++) { - tokens[i].resize(tracks[i].size()); + if (m_option.find("I") != std::string::npos) { + m_interpretation = true; + m_data = false; } - - // store tokens in output order: - for (int i=0; i<(int)tracks.size(); i++) { - for (int j=0; j<(int)tracks[i].size(); j++) { - int target = tracks[i][j]; - for (int k=0; kgetTrack(); - if (track != target) { - continue; - } - tokens[i][j].push_back(token); - } - } + if (m_option.find("X") != std::string::npos) { + m_exinterp = true; + m_data = false; } - - int counter = 0; - HTp top; - HTp bot; - HTp inner; - HTp outer; - bool suppressQ; - - // now print in output order, but hide fermatas - // in the alto and tenor parts if there are fermatas - // int the soprano and bass parts respectively. - for (int i=0; i<(int)tokens.size(); i++) { - for (int j=0; j<(int)tokens[i].size(); j++) { - switch (i) { - case 0: - case 2: - case 4: - // non-kern spines - for (int k=0; k<(int)tokens[i][j].size(); k++) { - m_humdrum_text << tokens[i][j][k]; - counter++; - if (counter < spinecount) { - m_humdrum_text << "\t"; - } - } - break; - - case 1: - case 3: - top = tokens[i][0][0]; - bot = tokens[i][1][0]; - if (i == 1) { - // tenor: top is inner - inner = top; - outer = bot; - } else { - // alto: bottom is inner - inner = bot; - outer = top; - } - if (inner->hasFermata() && outer->hasFermata()) { - suppressQ = true; - } else { - suppressQ = false; - } - - for (int k=0; k<(int)tokens[i][j].size(); k++) { - token = tokens[i][j][k]; - if (suppressQ && ((void*)token == (void*)inner)) { - string value = *token; - // Make fermata invisible by adding 'y' after it: - for (int m=0; m<(int)value.size(); m++) { - m_humdrum_text << value[m]; - if (value[m] == ';') { - if (m < (int)value.size() - 1) { - if (value.at(m+1) != 'y') { - m_humdrum_text << 'y'; - } - } else { - m_humdrum_text << 'y'; - } - } - } - } else { - m_humdrum_text << token; - } - counter++; - if (counter < spinecount) { - m_humdrum_text << "\t"; - } - } - break; - } - } + if (m_option.find("B") != std::string::npos) { + m_barline = true; + m_data = false; + } + if (m_option.find("M") != std::string::npos) { + // measure is an alias for barline + m_barline = true; + m_data = false; + } + if (m_option.find("L") != std::string::npos) { + m_localcomment = true; + m_data = false; + } + if (m_option.find("G") != std::string::npos) { + m_globalcomment = true; + m_data = false; + } + if (m_option.find("K") != std::string::npos) { + m_referencekey = true; + m_data = false; + } + if (m_option.find("V") != std::string::npos) { + m_referencevalue = true; + m_data = false; + } + if (m_option.find("R") != std::string::npos) { + m_reference = true; + m_referencekey = false; + m_referencevalue = false; + m_data = false; + } + if (m_option.find("D") != std::string::npos) { + m_data = true; } - m_humdrum_text << endl; } ////////////////////////////// // -// Tool_satb2gs::printTerminatorLine -- Print the terminator line in the -// output data. +// Tool_shed::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -void Tool_satb2gs::printTerminatorLine(vector>& tracks) { - int count = getNewTrackCount(tracks); - for (int i=0; i>& tracks) { - int count = getNewTrackCount(tracks); - int counter = 0; + m_exclusion = getString("exclusion-expression"); - for (int i=0; i<(int)tracks.size(); i++) { - switch (i) { - case 0: - case 2: - case 4: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - case 1: - case 3: - m_humdrum_text << "*^"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - break; - } + if (getBoolean("X")) { + m_xInterp = getExInterp(getString("X")); + } + if (getBoolean("Y")) { + m_yInterp = getExInterp(getString("Y")); + } + if (getBoolean("Z")) { + m_zInterp = getExInterp(getString("Z")); } - m_humdrum_text << endl; } ////////////////////////////// // -// Tool_satb2gs::printSpineMergeLine -- +// Tool_shed::getExInterp -- // -void Tool_satb2gs::printSpineMergeLine(vector>& tracks) { - int count = getNewTrackCount(tracks); - count += 2; - int counter; - - if (!tracks[2].empty()) { - // do not need to place merges on separate lines since they are - // separated by non-kern spine(s) between bass and soprano subspines. - - counter = 0; - for (int i=0; i<(int)tracks.size(); i++) { - switch (i) { - case 0: - case 2: - case 4: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - case 1: - case 3: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*v"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - } - } - m_humdrum_text << endl; - - } else { - // Merges for tenor/bass and soprano/alto need to be placed - // on separate lines. - - // First merge tenor/bass (tracks[1]) - counter = 0; - for (int i=0; i<(int)tracks.size(); i++) { - switch (i) { - case 0: - case 2: - case 3: - case 4: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - case 1: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*v"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - } - } - m_humdrum_text << endl; - - // Now merge soprano/alto (tracks[3]) - count--; - counter = 0; - for (int i=0; i<(int)tracks.size(); i++) { - switch (i) { - case 0: - case 2: - case 4: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - case 1: - m_humdrum_text << "*"; - m_humdrum_text << "\t"; - counter++; - break; - case 3: - for (int j=0; j<(int)tracks[i].size(); j++) { - m_humdrum_text << "*v"; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - } - } - m_humdrum_text << endl; +string Tool_shed::getExInterp(const string& value) { + if (value == "") { + return "**"; + } + if (value == "*") { + return "**"; + } + if (value.compare(0, 2, "**") == 0) { + return value; + } + if (value.compare(0, 1, "*") == 0) { + return "*" + value; } + return "**" + value; } ////////////////////////////// // -// Tool_satb2gs::getNewTrackCount -- Return the number of tracks (spines) -// in the output data (not counting subspines). +// Tool_shed::parseExpression -- +// Form of string: +// s/search/replace/options; s/search2/replace2/options2 +// // -int Tool_satb2gs::getNewTrackCount(vector>& tracks) { - int sum = 0; - for (int i=0; i<(int)tracks.size(); i++) { - for (int j=0; j<(int)tracks[i].size(); j++) { - sum++; +void Tool_shed::parseExpression(const string& expression) { + int state = 0; + + m_searches.clear(); + m_replaces.clear(); + m_options.clear(); + + char divchar = '/'; + + for (int i=0; i<(int)expression.size(); i++) { + if (state == 0) { // start of expression + if (isspace(expression[i])) { + continue; + } else if (expression[i] == 's') { + if (i >= (int)expression.size() - 1) { + cerr << "Error: spurious s at end of expression: " + << expression << endl; + return; + } else { + divchar = expression[i+1]; + i++; + state++; + m_searches.push_back(""); + } + } else { + cerr << "Error at position " << i + << " in expression: " << expression << endl; + return; + } + } else if (state == 1) { // search string + if (expression[i] == divchar) { + state++; + m_replaces.push_back(""); + continue; + } if (expression[i] == '\\') { + if (i >= (int)expression.size() - 1) { + cerr << "Error: expression ends too soon: " + << expression << endl; + return; + } else { + m_searches.back() += '\\'; + m_searches.back() += expression[i+1]; + i++; + } + } else { + m_searches.back() += expression[i]; + } + } else if (state == 2) { // replace string + if (expression[i] == divchar) { + state++; + m_options.push_back(""); + continue; + } if (expression[i] == '\\') { + if (i >= (int)expression.size() - 1) { + cerr << "Error: expression ends too soon: " + << expression << endl; + return; + } else { + m_replaces.back() += '\\'; + m_replaces.back() += expression[i+1]; + i++; + } + } else { + m_replaces.back() += expression[i]; + } + } else if (state == 3) { // regular expression options + if (expression[i] == ';') { + state++; + } else if (isspace(expression[i])) { + state++; + } else { + m_options.back() += expression[i]; + } + } + if (state == 4) { + state = 0; } } - // remove two spines that were merged into two others: - sum -= 2; - return sum; } ////////////////////////////// // -// Tool_satb2gs::printHeaderLine -- +// Tool_shed::initializeSegment -- Recalculate variables for each Humdrum +// input segment. // -void Tool_satb2gs::printHeaderLine(HumdrumFile& infile, int line, - vector>& tracks) { - int count = infile.getMaxTrack() - 2; - - HTp token; - int counter = 0; - for (int i=0; i<(int)tracks.size(); i++) { - switch (i) { - case 0: - case 2: - case 4: - for (int j=0; j<(int)tracks[i].size(); j++) { - token = infile.token(line, tracks[i][j]-1); - m_humdrum_text << token; - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - } - break; - - case 1: - case 3: - token = infile.token(line, tracks[i][0]-1); - if (token->isInstrumentName()) { - // suppress instrument names, but keep blank name - // to force indent. - m_humdrum_text << "*I\""; - } else if (token->isInstrumentAbbreviation()) { - // suppress instrument abbreviations - m_humdrum_text << "*"; - } else if (token->isInstrumentDesignation()) { - // suppress instrument designations (such as *Itenor) - m_humdrum_text << "*"; - } else if (token->isClef()) { - vector clefs = getClefs(infile, line); - if (i == 1) { - if (clefs.size() == 4) { - m_humdrum_text << clefs[0]; - } else { - m_humdrum_text << "*clefF4"; - } - } else { - if (clefs.size() == 4) { - m_humdrum_text << clefs.back(); - } else { - m_humdrum_text << "*clefG2"; - } - } - } else { - m_humdrum_text << token; - } - counter++; - if (counter < count) { - m_humdrum_text << "\t"; - } - break; - } +void Tool_shed::initializeSegment(HumdrumFile& infile) { + m_spines.clear(); + if (getBoolean("spines")) { + int maxtrack = infile.getMaxTrack(); + Convert::makeBooleanTrackList(m_spines, getString("spines"), maxtrack); } - m_humdrum_text << endl; } ////////////////////////////// // -// Tool_satb2gs::getClefs -- get a list of the clefs on the current line. +// Tool_shed::addToExInterpList -- // -vector Tool_satb2gs::getClefs(HumdrumFile& infile, int line) { - vector output; - for (int i=0; iisKern()) { +vector Tool_shed::addToExInterpList(void) { + string elist = getString("exclusive-interpretations"); + elist = Convert::trimWhiteSpace(elist); + HumRegex hre; + hre.replaceDestructive(elist, "", "^[,;\\s*]+"); + hre.replaceDestructive(elist, "", "[,;\\s*]+$"); + vector pieces; + hre.split(pieces, elist, "[,;\\s*]+"); + + vector output; + for (int i=0; i<(int)pieces.size(); i++) { + if (pieces[i].empty()) { continue; } - if (token->isClef()) { - output.push_back(token); - } + output.push_back("**" + pieces[i]); } return output; } @@ -120846,111 +125201,93 @@ vector Tool_satb2gs::getClefs(HumdrumFile& infile, int line) { ////////////////////////////// // -// Tool_satb2gs::getTrackInfo -- -// tracks 0 = list of spines before bass **kern spine -// tracks 1 = tenor and then bass **kern track numbers -// tracks 2 = aux. spines after after tenor and then after bass -// tracks 3 = soprano and then alto **kern track numbers -// tracks 4 = aux. spines after after soprano and then after alto +// Tool_shed::processFile -- // -void Tool_satb2gs::getTrackInfo(vector>& tracks, HumdrumFile& infile) { - tracks.resize(5); - for (int i=0; i<(int)tracks.size(); i++) { - tracks[i].clear(); +void Tool_shed::processFile(HumdrumFile& infile) { + if (m_search == "") { + // nothing to do + return; } - vector sstarts; - infile.getSpineStartList(sstarts); - int track; + m_modified = false; - // fill in tracks[0]: spines before first **kern spine - for (int i=0; i<(int)sstarts.size(); i++) { - if (sstarts[i]->isKern()) { - break; - } - track = sstarts[i]->getTrack(); - tracks[0].push_back(track); + if (m_interpretation) { + searchAndReplaceInterpretation(infile); } - int kcount = 0; + if (m_localcomment) { + searchAndReplaceLocalComment(infile); + } - kcount = 0; - // Store tracks related to the tenor part: - for (int i=0; i<(int)sstarts.size(); i++) { - if (sstarts[i]->isKern()) { - kcount++; - } - if (kcount > 2) { - break; - } - if (kcount < 2) { - continue; - } - track = sstarts[i]->getTrack(); - if (sstarts[i]->isKern()) { - tracks[1].push_back(track); - } else { - tracks[2].push_back(track); - } + if (m_globalcomment) { + searchAndReplaceGlobalComment(infile); } - kcount = 0; - // Store tracks related to the bass part: - for (int i=0; i<(int)sstarts.size(); i++) { - if (sstarts[i]->isKern()) { - kcount++; - } - if (kcount > 1) { - break; - } - if (kcount < 1) { - continue; - } - track = sstarts[i]->getTrack(); - if (sstarts[i]->isKern()) { - tracks[1].push_back(track); - } else { - tracks[2].push_back(track); - } + if (m_reference) { + searchAndReplaceReferenceRecords(infile); } - kcount = 0; - // Store tracks related to the soprano part: - for (int i=0; i<(int)sstarts.size(); i++) { - if (sstarts[i]->isKern()) { - kcount++; - } - if (kcount > 4) { - break; - } - if (kcount < 4) { - continue; - } - track = sstarts[i]->getTrack(); - if (sstarts[i]->isKern()) { - tracks[3].push_back(track); - } else { - tracks[4].push_back(track); - } + if (m_referencekey) { + searchAndReplaceReferenceKeys(infile); } - kcount = 0; - // Store tracks related to the alto part: - for (int i=0; i<(int)sstarts.size(); i++) { - if (sstarts[i]->isKern()) { - kcount++; - } - if (kcount > 3) { - break; - } - if (kcount < 3) { + if (m_referencevalue) { + searchAndReplaceReferenceValues(infile); + } + + if (m_exinterp) { + searchAndReplaceExinterp(infile); + } + + if (m_barline) { + searchAndReplaceBarline(infile); + } + + if (m_data) { + searchAndReplaceData(infile); + } + + if (m_modified) { + infile.createLinesFromTokens(); + } +} + + + +////////////////////////////// +// +// Tool_shed::searchAndReplaceBarline -- +// + +void Tool_shed::searchAndReplaceBarline(HumdrumFile& infile) { + string isearch; + if (m_search[0] == '^') { + isearch = "^=" + m_search.substr(1); + } else { + isearch = "^=.*" + m_search; + } + HumRegex hre; + for (int i=0; igetTrack(); - if (sstarts[i]->isKern()) { - tracks[3].push_back(track); - } else { - tracks[4].push_back(track); + for (int j=0; jisNull()) { + // Don't mess with null interpretations + continue; + } + if (!isValid(token)) { + continue; + } + if (hre.search(token, isearch, m_grepoptions)) { + string text = token->getText().substr(1); + hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(text, "", "^=+"); + text = "=" + text; + token->setText(text); + m_modified = true; + } } } } @@ -120959,176 +125296,248 @@ void Tool_satb2gs::getTrackInfo(vector>& tracks, HumdrumFile& infile ////////////////////////////// // -// Tool_satb2gs::validateHeader -- Header cannot contain -// spine manipulators. +// Tool_shed::searchAndReplaceInterpretation -- // -bool Tool_satb2gs::validateHeader(HumdrumFile& infile) { +void Tool_shed::searchAndReplaceInterpretation(HumdrumFile& infile) { + string isearch; + if (m_search[0] == '^') { + isearch = "^\\*" + m_search.substr(1); + } else { + isearch = "^\\*.*" + m_search; + } + HumRegex hre; for (int i=0; iisExclusive()) { + } else if (infile[i].isExclusiveInterpretation()) { + continue; + } else if (infile[i].isManipulator()) { continue; } - if (infile[i].isManipulator()) { - return false; + for (int j=0; jisNull()) { + // Don't mess with null interpretations + continue; + } + if (!isValid(token)) { + continue; + } + if (hre.search(token, isearch, m_grepoptions)) { + string text = token->getText().substr(1); + hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(text, "", "^\\*+"); + text = "*" + text; + token->setText(text); + m_modified = true; + } } } - - return true; } - - -///////////////////////////////// +////////////////////////////// // -// Tool_scordatura::Tool_scordatura -- Set the recognized options for the tool. +// Tool_shed::searchAndReplaceLocalComment -- // -Tool_scordatura::Tool_scordatura(void) { - define("s|sounding=b", "generate sounding score"); - define("w|written=b", "generate written score"); - define("m|mark|marker=s:@", "marker to add to score"); - define("p|pitch|pitches=s", "list of pitches to mark"); - define("i|interval=s", "musical interval of marked pitches"); - define("I|is-sounding=s", "musical score is in sounding format for marks"); - define("c|chromatic=i:0", "chromatic interval of marked pitches"); - define("d|diatonic=i:0", "diatonic interval of marked pitches"); - define("color=s", "color marked pitches"); - define("string=s", "string number"); +void Tool_shed::searchAndReplaceLocalComment(HumdrumFile& infile) { + string isearch; + if (m_search[0] == '^') { + isearch = "^!" + m_search.substr(1); + } else { + isearch = "^!.*" + m_search; + } + HumRegex hre; + for (int i=0; iisNull()) { + // Don't mess with null interpretations + continue; + } + if (!isValid(token)) { + continue; + } + if (hre.search(token, isearch, m_grepoptions)) { + string text = token->getText().substr(1); + hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(text, "", "^!+"); + text = "!" + text; + token->setText(text); + m_modified = true; + } + } + } } -///////////////////////////////// +////////////////////////////// // -// Tool_scordatura::run -- Do the main work of the tool. +// Tool_shed::searchAndReplaceGlobalComment -- // -bool Tool_scordatura::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; isize() < 3) { + // Don't mess with null comments + continue; + } + if (hre.search(token, isearch, m_grepoptions)) { + string text = token->getText().substr(2); + hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(text, "", "^!+"); + text = "!!" + text; + token->setText(text); + m_modified = true; + } } - return status; } -bool Tool_scordatura::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} +////////////////////////////// +// +// Tool_shed::searchAndReplaceReferenceRecords -- +// -bool Tool_scordatura::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); +void Tool_shed::searchAndReplaceReferenceRecords(HumdrumFile& infile) { + string isearch; + if (m_search[0] == '^') { + isearch = "^!!!" + m_search.substr(1); } else { - out << infile; + isearch = "^!!!.*" + m_search; + } + HumRegex hre; + for (int i=0; igetText().substr(1); + hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(text, "", "^!+"); + text = "!!!" + text; + token->setText(text); + m_modified = true; + } } - return status; -} - - -bool Tool_scordatura::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; } ////////////////////////////// // -// Tool_scordatura::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_shed::searchAndReplaceReferenceKeys -- // -void Tool_scordatura::initialize(void) { - m_writtenQ = getBoolean("written"); - m_soundingQ = getBoolean("sounding"); - m_pitches.clear(); - m_marker = getString("mark"); - m_IQ = getBoolean("I"); - m_color = getString("color"); - if (getBoolean("pitches")) { - m_pitches = parsePitches(getString("pitches")); - } - m_cd = getBoolean("diatonic") && getBoolean("chromatic"); - m_interval.clear(); - if (m_cd) { - m_diatonic = getInteger("diatonic"); - m_chromatic = getInteger("chromatic"); - } else { - if (getBoolean("interval")) { - m_interval = getString("interval"); +void Tool_shed::searchAndReplaceReferenceKeys(HumdrumFile& infile) { + string isearch = m_search; + HumRegex hre; + for (int i=0; isetText(text); + m_modified = true; } } - if ((abs(m_diatonic) > 28) || (abs(m_chromatic) > 48)) { - m_diatonic = 0; - m_chromatic = 0; - m_cd = false; - } - if (!m_pitches.empty()) { - prepareTranspositionInterval(); - } - m_string = getString("string"); } ////////////////////////////// // -// Tool_scordatura::processFile -- +// Tool_shed::searchAndReplaceReferenceValues -- // -void Tool_scordatura::processFile(HumdrumFile& infile) { - m_modifiedQ = false; - - if (!m_pitches.empty()) { - markPitches(infile); - if (m_modifiedQ) { - addMarkerRdf(infile); +void Tool_shed::searchAndReplaceReferenceValues(HumdrumFile& infile) { + string isearch = m_search; + HumRegex hre; + for (int i=0; i rdfs; - getScordaturaRdfs(rdfs, infile); - if (!rdfs.empty()) { - processScordaturas(infile, rdfs); + HTp token = infile.token(i, 0); + string value = infile[i].getReferenceValue(); + if (hre.search(value, isearch, m_grepoptions)) { + hre.replaceDestructive(value, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(value, "", "^!+"); + hre.replaceDestructive(value, "", ":+$"); + string key = infile[i].getReferenceKey(); + string text = "!!!" + key + ": " + value; + token->setText(text); + m_modified = true; } } - - if (m_modifiedQ) { - infile.createLinesFromTokens(); - } } ////////////////////////////// // -// Tool_scordatura::processScoredaturas -- +// Tool_shed::searchAndReplaceExinterp -- // -void Tool_scordatura::processScordaturas(HumdrumFile& infile, vector& rdfs) { - for (int i=0; i<(int)rdfs.size(); i++) { - processScordatura(infile, rdfs[i]); +void Tool_shed::searchAndReplaceExinterp(HumdrumFile& infile) { + string isearch; + if (m_search[0] == '^') { + isearch = "^\\*\\*" + m_search.substr(1); + } else { + isearch = "^\\*\\*.*" + m_search; + } + HumRegex hre; + for (int i=0; iisNull()) { + // Don't mess with null interpretations + continue; + } + if (!isValid(token)) { + continue; + } + if (hre.search(token, isearch, m_grepoptions)) { + string text = token->getText().substr(2); + hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); + hre.replaceDestructive(text, "", "^\\*+"); + text = "**" + text; + token->setText(text); + m_modified = true; + } + } } } @@ -121136,333 +125545,320 @@ void Tool_scordatura::processScordaturas(HumdrumFile& infile, vector& rdfs) ////////////////////////////// // -// Tool_scordatura::processScordatura -- +// Tool_shed::searchAndReplaceData -- // -void Tool_scordatura::processScordatura(HumdrumFile& infile, HTp reference) { - HumRegex hre; +void Tool_shed::searchAndReplaceData(HumdrumFile& infile) { + string dsearch = m_search; - if (m_writtenQ) { - if (!hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s]+)\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*ITrd(-?\\d+)c(-?\\d+)\\b")) { - return; + HumRegex hre; + for (int i=0; iisNull()) { + // Don't mess with null interpretations + continue; + } + if (!isValid(token)) { + continue; + } + if (hre.search(token, dsearch, m_grepoptions)) { + string text = token->getText(); + hre.replaceDestructive(text, m_replace, dsearch, m_grepoptions); + if (text == "") { + text = "."; + } + token->setText(text); + m_modified = true; + } } } - - string marker = hre.getMatch(1); - int diatonic = hre.getMatchInt(2); - int chromatic = hre.getMatchInt(3); - - if (diatonic == 0 && chromatic == 0) { - // nothing to do - return; - } - - flipScordaturaInfo(reference, diatonic, chromatic); - transposeMarker(infile, marker, diatonic, chromatic); } ////////////////////////////// // -// Tool_scordatura::transposeMarker -- +// Tool_shed::isValidDataType -- usar with -x and -k options. // - -void Tool_scordatura::transposeMarker(HumdrumFile& infile, const string& marker, int diatonic, int chromatic) { - m_transposer.setTranspositionDC(diatonic, chromatic); - for (int i=0; iisKern()) { - continue; +bool Tool_shed::isValidDataType(HTp token) { + if (m_exinterps.empty()) { + return true; + } + string datatype = token->getDataType(); + for (int i=0; i<(int)m_exinterps.size(); i++) { + if (datatype == m_exinterps[i]) { + return true; } - HTp sstop = infile.getStrandEnd(i); - transposeStrand(sstart, sstop, marker); } + return false; } ////////////////////////////// // -// Tool_scordatura::transposeStrand -- +// Tool_shed::isValidSpine -- used with -s option. // -void Tool_scordatura::transposeStrand(HTp sstart, HTp sstop, const string& marker) { - HTp current = sstart; - while (current && current != sstop) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull() || current->isRest()) { - current = current->getNextToken(); - continue; - } - if (current->find(marker) != string::npos) { - transposeChord(current, marker); - } - current = current->getNextToken(); +bool Tool_shed::isValidSpine(HTp token) { + if (m_spines.empty()) { + return true; } + int track = token->getTrack(); + return m_spines.at(track); } ////////////////////////////// // -// Tool_scordatura::transposeChord -- +// Tool_shed::isValid -- // -void Tool_scordatura::transposeChord(HTp token, const string& marker) { - int scount = token->getSubtokenCount(); - if (scount == 1) { - string inputnote = *token; - string newtoken; - newtoken = transposeNote(inputnote); - token->setText(newtoken); - return; - } - vector subtokens; - subtokens = token->getSubtokens(); - for (int i=0; i<(int)subtokens.size(); i++) { - if (subtokens[i].find(marker) == string::npos) { - continue; +bool Tool_shed::isValid(HTp token) { + if (!m_exclusion.empty()) { + HumRegex hre; + if (hre.search(token, m_exclusion)) { + return false; } - string newtoken = transposeNote(subtokens[i]); - subtokens[i] = newtoken; } - string newchord; - for (int i=0; i<(int)subtokens.size(); i++) { - newchord += subtokens[i]; - if (i<(int)subtokens.size() - 1) { - newchord += ' '; - } + if (isValidDataType(token) && isValidSpine(token)) { + return true; } - token->setText(newchord); + return false; } -////////////////////////////// + + +///////////////////////////////// // -// Tool_scordatura::transposeNote -- +// Tool_sic::Tool_sic -- Set the recognized options for the tool. // -string Tool_scordatura::transposeNote(const string& note) { - HumRegex hre; - if (!hre.search(note, "(.*?)([A-Ga-g]+[-#]*)(.*)")) { - return note; - } - string pre = hre.getMatch(1); - string pitch = hre.getMatch(2); - string post = hre.getMatch(3); - HumPitch hpitch; - hpitch.setKernPitch(pitch); - m_transposer.transpose(hpitch); - string output; - output = pre; - output += hpitch.getKernPitch(); - output += post; - return output; +Tool_sic::Tool_sic(void) { + define("s|substitution=b", "insert substitutions into music"); + define("o|original=b", "insert originals into music"); + define("r|remove=b", "remove sic layout tokens"); + define("v|verbose=b", "add verbose parameter"); + define("q|quiet=b", "remove verbose parameter"); } -////////////////////////////// +///////////////////////////////// // -// Tool_scordatura::flipScordaturaInfo -- +// Tool_sic::run -- Do the main work of the tool. // -void Tool_scordatura::flipScordaturaInfo(HTp reference, int diatonic, int chromatic) { - diatonic *= -1; - chromatic *= -1; - string output; - if (m_writtenQ) { - output = "Trd"; - output += to_string(diatonic); - output += "c"; - output += to_string(chromatic); - } else if (m_soundingQ) { - output = "ITrd"; - output += to_string(diatonic); - output += "c"; - output += to_string(chromatic); +bool Tool_sic::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; isetText(token); + return status; +} + + +bool Tool_sic::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; + } + return status; +} + + +bool Tool_sic::run(HumdrumFile& infile) { + initialize(); + if (!(m_substituteQ || m_originalQ || m_removeQ || m_verboseQ || m_quietQ)) { + return true; } + processFile(infile); + return true; } ////////////////////////////// // -// Tool_scordatura::getScoredaturaRdfs -- +// Tool_sic::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -void Tool_scordatura::getScordaturaRdfs(vector& rdfs, HumdrumFile& infile) { - rdfs.clear(); - HumRegex hre; +void Tool_sic::initialize(void) { + m_substituteQ = getBoolean("substitution"); + m_originalQ = getBoolean("original"); + m_removeQ = getBoolean("remove"); + m_verboseQ = getBoolean("verbose"); + m_quietQ = getBoolean("quiet"); +} + + + +////////////////////////////// +// +// Tool_sic::processFile -- +// + +void Tool_sic::processFile(HumdrumFile& infile) { for (int i=0; icompare(0, 8, "!LO:SIC:") != 0) { + continue; } - } else if (m_soundingQ) { - if (hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*[^\\s]+\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*Trd-?\\d+c-?\\d+\\b")) { - rdfs.push_back(reference); + if (m_verboseQ) { + addVerboseParameter(token); + } else if (m_quietQ) { + removeVerboseParameter(token); + } + if (m_removeQ) { + token->setText("!"); + m_modifiedQ = true; + } else if (m_substituteQ) { + insertSubstitutionToken(token); + } else if (m_originalQ) { + insertOriginalToken(token); } } } + if (m_modifiedQ) { + infile.createLinesFromTokens(); + } + m_humdrum_text << infile; } ////////////////////////////// // -// Tool_scordatura::parsePitches -- +// Tool_sic::addVerboseParameter -- // -set Tool_scordatura::parsePitches(const string& input) { +void Tool_sic::addVerboseParameter(HTp token) { HumRegex hre; - string value = input; - hre.replaceDestructive(value, "-", "\\s*-\\s*", "g"); - - vector pieces; - hre.split(pieces, value, "[^A-Ga-g0-9-]+"); - - HumPitch pitcher; - set output; - string p1; - string p2; - int d1; - int d2; - for (int i=0; i<(int)pieces.size(); i++) { - if (hre.search(pieces[i], "(.*)-(.*)")) { - // pitch range - p1 = hre.getMatch(1); - p2 = hre.getMatch(2); - d1 = Convert::kernToBase7(p1); - d2 = Convert::kernToBase7(p2); - if ((d1 < 0) || (d2 < 0) || (d1 > d2) || (d1 > 127) || (d2 > 127)) { - continue; - } - for (int j=d1; j<=d2; j++) { - output.insert(j); - } - } else { - // single pitch - d1 = Convert::kernToBase7(pieces[i]); - if ((d1 < 0) || (d1 > 127)) { - continue; - } - output.insert(d1); - } + string value = token->getText(); + if (hre.search(value, "(:v:)|(:v$)")) { + return; } - return output; + string newvalue = value + ":v"; + token->setText(newvalue); + m_modifiedQ = true; } ////////////////////////////// // -// Tool_scordatura::markPitches -- +// Tool_sic::removeVerboseParameter -- // -void Tool_scordatura::markPitches(HumdrumFile& infile) { - for (int i=0; iisKern()) { - continue; - } - HTp sstop = infile.getStrandStop(i); - markPitches(sstart, sstop); +void Tool_sic::removeVerboseParameter(HTp token) { + HumRegex hre; + string value = token->getText(); + string newvalue = value; + hre.replaceDestructive(newvalue, ":", ":v:", "g"); + hre.replaceDestructive(newvalue, "", ":v$", ""); + if (value == newvalue) { + return; } + token->setText(newvalue); + m_modifiedQ = true; } -void Tool_scordatura::markPitches(HTp sstart, HTp sstop) { - HTp current = sstart; - while (current && (current != sstop)) { - if (current->isNull() || current->isRest()) { + +////////////////////////////// +// +// Tool_sic::getTargetToken -- Get the token that the layout command +// applies to. +// + +HTp Tool_sic::getTargetToken(HTp stok) { + HTp current = stok->getNextToken(); + while (current) { + if (current->isNull()) { current = current->getNextToken(); continue; } - markPitches(current); - current = current->getNextToken(); - } -} - - -void Tool_scordatura::markPitches(HTp token) { - vector subtokens = token->getSubtokens(); - int counter = 0; - for (int i=0; i<(int)subtokens.size(); i++) { - int dia = Convert::kernToBase7(subtokens[i]); - if (m_pitches.find(dia) != m_pitches.end()) { - counter++; - subtokens[i] += m_marker; + if (current->isManipulator()) { + // Layout commands should not apply to manipulators nor be split + // from their associated token. + current = NULL; + break; } - } - if (counter == 0) { - return; - } - string newtoken; - for (int i=0; i<(int)subtokens.size(); i++) { - newtoken += subtokens[i]; - if (i < (int)subtokens.size() - 1) { - newtoken += ' '; + if (current->isCommentLocal()) { + current = current->getNextToken(); + continue; } + break; } - token->setText(newtoken); - m_modifiedQ = true; + if (!current) { + return NULL; + } + return current; } ////////////////////////////// // -// Tool_scordatura::addMarkerRdf -- -// - -void Tool_scordatura::addMarkerRdf(HumdrumFile& infile) { - string line = "!!!RDF**kern: "; - line += m_marker; - line += " = "; - if (!m_string.empty()) { - line += "string="; - line += m_string; - line += " "; +// Tool_sic::insertSubstitutionToken -- +// + +void Tool_sic::insertSubstitutionToken(HTp sictok) { + HTp target = getTargetToken(sictok); + if (!target) { + return; } - line += "scordatura="; - if (m_IQ) { - line += "I"; + HumRegex hre; + vector pieces; + hre.split(pieces, *sictok, ":"); + string tstring = target->getText(); + string sstring; + for (int i=2; i<(int)pieces.size(); i++) { + if (pieces[i].compare(0, 2, "s=") == 0) { + sstring = pieces[i].substr(2); + } } - line += "Tr"; - if (m_transposition.empty()) { - line += "XXX"; - } else { - line += m_transposition; + if (sstring.empty()) { + return; } - if (!m_color.empty()) { - line += ", color="; - line += m_color; + target->setText(sstring); + m_modifiedQ = true; + string newsic = "!LO:SIC"; + for (int i=2; i<(int)pieces.size(); i++) { + if (pieces[i].compare(0, 2, "s=") == 0) { + newsic += ":o=" + tstring; + } else { + newsic += ":" + pieces[i]; + } } - infile.appendLine(line); + sictok->setText(newsic); m_modifiedQ = true; } @@ -121470,1534 +125866,1101 @@ void Tool_scordatura::addMarkerRdf(HumdrumFile& infile) { ////////////////////////////// // -// Tool_scordatura::prepareTranspositionInterval -- +// Tool_sic::insertOriginalToken -- // -void Tool_scordatura::prepareTranspositionInterval(void) { - m_transposition.clear(); - if (m_cd) { - m_transposition = "d"; - m_transposition += to_string(m_diatonic); - m_transposition += "c"; - m_transposition += to_string(m_chromatic); +void Tool_sic::insertOriginalToken(HTp sictok) { + HTp target = getTargetToken(sictok); + if (!target) { return; } - - if (m_interval.empty()) { + HumRegex hre; + vector pieces; + hre.split(pieces, *sictok, ":"); + string tstring = target->getText(); + string sstring; + for (int i=2; i<(int)pieces.size(); i++) { + if (pieces[i].compare(0, 2, "o=") == 0) { + sstring = pieces[i].substr(2); + } + } + if (sstring.empty()) { return; } - - HumTransposer trans; - trans.intervalToDiatonicChromatic(m_diatonic, m_chromatic, m_interval); - m_transposition = "d"; - m_transposition += to_string(m_diatonic); - m_transposition += "c"; - m_transposition += to_string(m_chromatic); + target->setText(sstring); + m_modifiedQ = true; + string newsic = "!LO:SIC"; + for (int i=2; i<(int)pieces.size(); i++) { + if (pieces[i].compare(0, 2, "o=") == 0) { + newsic += ":s=" + tstring; + } else { + newsic += ":" + pieces[i]; + } + } + sictok->setText(newsic); + m_modifiedQ = true; } -///////////////////////////////// +////////////////////////////// // -// Tool_semitones::Tool_semitones -- Set the recognized options for the tool. +// MeasureData::MeasureData -- // -Tool_semitones::Tool_semitones(void) { - define("1|first=b", "mark only the first note of intervals"); - define("2|second=b", "mark only the second note of intervals"); - define("A|O|no-analysis|no-output=b", "do not print analysis spines"); - define("I|no-input=b", "do not print input data spines"); - define("M|no-mark|no-marks=b", "do not mark notes"); - define("R|no-rests=b", "ignore rests"); - define("T|no-ties=b", "do not mark ties"); - define("X|include|only=s", "include only **kern tokens with given pattern"); - define("color=s:red", "mark color"); - define("c|cdata=b", "store resulting data as **cdata (allowing display in VHV"); - define("d|down=b", "highlight notes that that have a negative semitone interval"); - define("j|jump=i:3", "starting interval defining leaps"); - define("l|leap=b", "highlight notes that have leap motion"); - define("mark=s:@", "mark character"); - define("m|midi=b", "show MIDI note number for pitches"); - define("n|count=b", "output count of intervals being marked"); - define("p|pc=b", "output pitch classes from C=0 instead of MIDI notes for -m option"); - define("r|same|repeat|repeated=b", "highlight notes that are repeated "); - define("s|step=b", "highlight notes that have step-wise motion"); - define("u|up=b", "highlight notes that that have a positive semitone interval"); - define("x|exclude=s", "exclude **kern tokens with given pattern"); +MeasureData::MeasureData(void) { + m_hist7pc.resize(7); + std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0); } - -///////////////////////////////// -// -// Tool_semitones::run -- Do the main work of the tool. -// - -bool Tool_semitones::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i 0) && !m_nomarkQ) { - m_humdrum_text << "!!!RDF**kern: "; - m_humdrum_text << m_marker; - m_humdrum_text << " = marked note"; - if (getBoolean("color")) { - m_humdrum_text << ", color=" << m_color; - } - m_humdrum_text << '\n'; - } - if (m_count) { - showCount(); - } +void MeasureData::setStartLine(int startline) { + m_startline = startline; } ////////////////////////////// // -// Tool_semitones::showCount -- Give a count for the number of -// intervals that were marked. +// MeasureData::setStopLine -- // -void Tool_semitones::showCount(void) { - m_humdrum_text << "!!semitone_count: " << m_markCount; - if (m_repeatQ) { - m_humdrum_text << " REPEAT"; - } - if (m_upQ) { - m_humdrum_text << " UP"; - } - if (m_downQ) { - m_humdrum_text << " DOWN"; - } - if (m_stepQ) { - m_humdrum_text << " STEP"; - } - if (m_leapQ) { - m_humdrum_text << " LEAP"; - } - if ((m_stepQ || m_leapQ) && (m_leap != 3)) { - m_humdrum_text << " JUMP:" << m_leap; - } - if (m_marker != "@") { - m_humdrum_text << " MARK:" << m_marker; - } - m_humdrum_text << '\n'; +void MeasureData::setStopLine(int stopline) { + m_stopline = stopline; } ////////////////////////////// // -// Tool_semitones::analyzeLine -- Append analysis spines after every **kern -// spine. +// MeasureData::getStartLine -- // -void Tool_semitones::analyzeLine(HumdrumFile& infile, int line) { - int group = 0; - if (!infile[line].hasSpines()) { - m_humdrum_text << infile[line] << "\n"; - return; - } - for (int i=0; iisKern()) { - m_humdrum_text << token; - if (i < infile[line].getFieldCount() - 1) { - m_humdrum_text << '\t'; - } - continue; - } - } - i = processKernSpines(infile, line, i, group++); - if (!m_noinputQ) { - if (i < infile[line].getFieldCount() - 1) { - m_humdrum_text << '\t'; - } - } - } - m_humdrum_text << '\n'; +int MeasureData::getStartLine(void) { + return m_startline; } ////////////////////////////// // -// Tool_semitones::processKernSpine -- +// MeasureData::getStopLine -- // -int Tool_semitones::processKernSpines(HumdrumFile& infile, int line, int start, int kspine) { - HTp token = infile.token(line, start); - if (!token->isKern()) { - return start; - } - int track = token->getTrack(); - vector toks; - toks.push_back(token); - for (int i=start+1; igetTrack(); - if (newtrack == track) { - toks.push_back(newtok); - continue; - } - break; - } - - int toksize = (int)toks.size(); - - // calculate intervals/MIDI note numbers if appropriate - bool allQ = m_stepQ || m_leapQ || m_upQ || m_downQ || m_repeatQ; - bool dirQ = m_upQ || m_downQ; - bool typeQ = m_stepQ || m_leapQ; - vector intervals(toksize); - if (infile[line].isData()) { - for (int i=0; i 0) && (value < m_leap)) { - markInterval(toks[i]); - } else if (m_downQ && m_stepQ && (value < 0) && (value > -m_leap)) { - markInterval(toks[i]); - } else if (!dirQ && m_stepQ && (value != 0) && (abs(value) < m_leap)) { - markInterval(toks[i]); - - } else if (m_upQ && m_leapQ && (value > 0) && (value >= m_leap)) { - markInterval(toks[i]); - } else if (m_downQ && m_leapQ && (value < 0) && (value <= -m_leap)) { - markInterval(toks[i]); - } else if (!dirQ && m_leapQ && (value != 0) && (abs(value) >= m_leap)) { - markInterval(toks[i]); - - } else if (m_repeatQ && (value == 0)) { - markInterval(toks[i]); - } else if (!typeQ && m_upQ && (value > 0)) { - markInterval(toks[i]); - } else if (!typeQ && m_downQ && (value < 0)) { - markInterval(toks[i]); - } - } - } - } - - // print the **kern fields - if (!m_noinputQ) { - for (int i=0; icompare(0, 2, "**") == 0) { - if (m_cdataQ) { - printTokens("**cdata", toksize); - } else if (m_midiQ) { - if (m_pcQ) { - printTokens("**mpc", toksize); - } else { - printTokens("**mnn", toksize); - } - } else { - printTokens("**tti", toksize); - } - } else { - for (int i=0; iisData()) { - return; - } - if (!token->isKern()) { - return; - } - if (token->isNull()) { - return; - } - if (token->isRest()) { - return; - } - if (token->isUnpitched()) { - return; - } - m_markCount++; - token = markNote(token, m_firstQ); - if (m_firstQ && !m_secondQ) { - return; +double MeasureData::getStartTime(void) { + if (m_owner == NULL) { + return 0.0; } - // find next note - HTp current = token->getNextToken(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - markNote(current, m_secondQ); - break; + if (getStartLine() < 0) { + return 0.0; } + return (*m_owner)[getStartLine()].getDurationFromStart().getFloat(); } ////////////////////////////// // -// Tool_semitones::markNote -- make note and any tied notes after it. -// Return the last note of a tied note (or the note if no tied notes -// after it). +// MeasureData::getMeasure -- return the measure number of the measure. +// return -1 if no measure number. // -HTp Tool_semitones::markNote(HTp token, bool markQ) { - string subtok = token->getSubtoken(0); - bool hasTieEnd = false; - if (subtok.find('_') != string::npos) { - hasTieEnd = true; - } else if (subtok.find(']') != string::npos) { - hasTieEnd = true; - } - - if (!(hasTieEnd && m_notiesQ)) { - if (markQ) { - addMarker(token); - } - } - - bool hasTie = false; - if (subtok.find('[') != string::npos) { - hasTie = true; - } else if (subtok.find('_') != string::npos) { - hasTie = true; +int MeasureData::getMeasure(void) { + if (m_owner == NULL) { + return -1; } - - if (!hasTie) { - return token; + if (getStartLine() < 0) { + return -1; } - HTp current = token->getNextToken(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - subtok = current->getSubtoken(0); - bool hasTie = false; - if (subtok.find('[') != string::npos) { - hasTie = true; - } else if (subtok.find('_') != string::npos) { - hasTie = true; - } - if (!hasTie) { - if (subtok.find(']') != string::npos) { - markNote(current, markQ); - } - return current; - } else { - return markNote(current, markQ); - } - break; + HumdrumFile& infile = *m_owner; + if (!infile[getStartLine()].isBarline()) { + return -1; + } + HumRegex hre; + if (hre.search(infile.token(getStartLine(), 0), "(\\d+)")) { + return hre.getMatchInt(1); + } else { + return -1; } - return NULL; } ////////////////////////////// // -// Tool_semitones::addMarker -- +// MeasureData::getQon -- return the start time class id of the measure. // -void Tool_semitones::addMarker(HTp token) { - if (!m_nomarkQ) { - string contents = m_marker; - contents += token->getText(); - token->setText(contents); +std::string MeasureData::getQon(void) { + if (m_owner == NULL) { + return ""; + } + if (getStartLine() < 0) { + return ""; + } + HumdrumFile& infile = *m_owner; + HumNum ts = infile[getStartLine()].getDurationFromStart(); + string output = "qon" + to_string(ts.getNumerator()); + if (ts.getDenominator() != 1) { + output += "-" + to_string(ts.getDenominator()); } + return output; } ////////////////////////////// // -// Tool_semitones::printTokens -- +// MeasureData::getQoff -- return the end time class id of the measure. // -void Tool_semitones::printTokens(const string& value, int count) { - for (int i=0; iisNull()) { - return "."; - } - if (token->isRest()) { - if (m_midiQ) { - return "r"; - } else { - return "."; - } - } - if (token->isUnpitched()) { - if (m_midiQ) { - return "R"; - } else { - return "."; - } - } - if ((m_include.size() > 0) || (m_exclude.size() > 0)) { - int status = filterData(token); - if (status == 0) { - return "."; - } else if (status < 0) { - return "x"; // excluded note - } - } - string tok = token->getSubtoken(0); - if (tok.find(']') != string::npos) { - return "."; +double MeasureData::getStopTime(void) { + if (m_owner == NULL) { + return 0.0; } - if (tok.find('_') != string::npos) { - return "."; + if (getStopLine() < 0) { + return 0.0; } - int value = Convert::kernToMidiNoteNumber(tok); + return (*m_owner)[getStopLine()].getDurationFromStart().getFloat(); +} - if (m_midiQ) { - string output; - if (m_pcQ) { - value = value % 12; - } - output = to_string(value); - return output; - } - string nexttok = getNextNoteAttack(token); - if (nexttok.empty()) { - return "."; - } - if (nexttok.find('r') != string::npos) { - // no interval since next note is a rest - return "r"; - } - int value2 = Convert::kernToMidiNoteNumber(nexttok); - int interval = value2 - value; - string output = to_string(interval); - return output; + +////////////////////////////// +// +// MeasureData::getDuration -- return the duration of the measure +// int quarter notes +// + +double MeasureData::getDuration(void) { + return getStopTime() - getStartTime(); } -/////////////////////////////// +////////////////////////////// // -// Tool_semitones::getNextNoteAttack -- Or rest. +// MeasureData::getScoreDuration -- // -string Tool_semitones::getNextNoteAttack(HTp token) { - HTp current = token; - current = current->getNextToken(); - string tok; - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->isRest()) { - if (!m_norestsQ) { - return "r"; - } else { - current = current->getNextToken(); - continue; - } - } - if (current->isUnpitched()) { - return "R"; - } - string tok = current->getSubtoken(0); - if (tok.find(']') != string::npos) { - current = current->getNextToken(); - continue; - } - if (tok.find('_') != string::npos) { - current = current->getNextToken(); - continue; - } - return tok; +double MeasureData::getScoreDuration(void) { + if (m_owner == NULL) { + return 0.0; } + return m_owner->getScoreDuration().getFloat(); +} - if (!current) { - return ""; - } - if (!current->isData()) { - return ""; - } - // Some other strange problem. - return "."; + + +////////////////////////////// +// +// MeasureData::clear -- +// + +void MeasureData::clear(void) { + m_owner = NULL; + m_owner = NULL; + m_startline = -1; + m_startline = -1; + m_hist7pc.resize(7); + std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0); + m_sum7pc = 0.0; } ////////////////////////////// // -// Tool_semitones::filterData -- select or deselect an interval based -// on regular expression pattern. Return true if the note should -// be kept; otherwise, return false. +// MeasureData::getHistogram7pc -- // -int Tool_semitones::filterData(HTp token) { - vector toks = getTieGroup(token); - HumRegex hre; - if (!m_exclude.empty()) { - for (int i=0; i<(int)toks.size(); i++) { - if (hre.search(toks[i], m_exclude)) { - return -1; - } - } - return 1; - } else if (!m_include.empty()) { - for (int i=0; i<(int)toks.size(); i++) { - if (hre.search(toks[i], m_include)) { - return 1; - } - } - return 0; - } - return 0; +std::vector& MeasureData::getHistogram7pc(void) { + return m_hist7pc; +} + + +////////////////////////////// +// +// MeasureData::getSum7pc -- +// + +double MeasureData::getSum7pc(void) { + return m_sum7pc; } ////////////////////////////// // -// Tool_semitones::getTieGroup -- +// MeasureData::generateNoteHistogram -- // -vector Tool_semitones::getTieGroup(HTp token) { - vector output; - if (!token) { - return output; - } - if (token->isNull()) { - return output; +void MeasureData::generateNoteHistogram(void) { + m_hist7pc.resize(7); + std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0); + m_sum7pc = 0; + if (m_owner == NULL) { + return; } - if (!token->isData()) { - return output; + if (m_startline < 0) { + return; } - output.push_back(token); - if (token->isRest()) { - return output; + if (m_stopline < 0) { + return; } - string subtok = token->getSubtoken(0); - bool continues = hasTieContinue(subtok); - HTp current = token; - while (continues) { - current = getNextNote(current); - if (!current) { - break; + + HumdrumFile& infile = *m_owner; + for (int i=m_startline; igetSubtoken(0); - if (subtok.find(']') != string::npos) { - output.push_back(current); - break; + for (int j=0; jisKern()) { + continue; + } + if (token->isNull()) { + continue; + } + if (token->isRest()) { + continue; + } + double duration = token->getDuration().getFloat(); + int subtokcount = token->getSubtokenCount(); + for (int k=0; kgetSubtoken(k); + int pc = Convert::kernToBase7PC(subtok); + if (pc < 0) { + continue; + } + m_hist7pc.at(pc) += duration; + } } - continues = hasTieContinue(subtok); } - return output; + m_sum7pc = 0.0; + for (int i=0; i<(int)m_hist7pc.size(); i++) { + m_sum7pc += m_hist7pc[i]; + } } +/////////////////////////////////////////////////////////////////////////// + ////////////////////////////// // -// Tool_semitones::hasTieContinue -- +// MeasureDataSet::MeasureDataSet -- // -bool Tool_semitones::hasTieContinue(const string& value) { - if (value.find('_') != string::npos) { - return true; - } - if (value.find('[') != string::npos) { - return true; - } - return false; +MeasureDataSet::MeasureDataSet(void) { + m_data.reserve(1000); } ////////////////////////////// // -// getNextNote -- +// MeasureDataSet::MeasureDataSet -- // -HTp Tool_semitones::getNextNote(HTp token) { - HTp current = token->getNextToken(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - break; - } - return current; +MeasureDataSet::MeasureDataSet(HumdrumFile& infile) { + parse(infile); } - - -///////////////////////////////// +////////////////////////////// // -// Tool_shed::Tool_shed -- Set the recognized options for the tool. +// MeasureDataSet::~MeasureDataSet -- // -Tool_shed::Tool_shed(void) { - define("s|spine|spines=s", "list of spines to process"); - define("e|expression=s", "regular expression"); - define("E|exclusion-expression=s", "regular expression to skip"); - define("x|exclusive-interpretations=s", "apply only to spine types in list"); - define("k|kern=b", "apply only to **kern data"); - define("X=s", "defineable exclusive interpretation x"); - define("Y=s", "defineable exclusive interpretation y"); - define("Z=s", "defineable exclusive interpretation z"); +MeasureDataSet::~MeasureDataSet() { + clear(); } -///////////////////////////////// +////////////////////////////// // -// Tool_shed::run -- Do the main work of the tool. +// MeasureDataSet::clear -- // -bool Tool_shed::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; igenerateNoteHistogram(); + m_data.push_back(info); + lastbar = i; } - return status; + MeasureData* info = new MeasureData(infile, lastbar, infile.getLineCount() - 1); + m_data.push_back(info); + return 1; } -bool Tool_shed::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; + +////////////////////////////// +// +// MeasureDataSet::operator[] -- +// + +MeasureData& MeasureDataSet::operator[](int index) { + return *m_data[index]; } -bool Tool_shed::run(HumdrumFile& infile) { - initialize(); - initializeSegment(infile); - if (m_options.empty()) { - cerr << "Error: -e option is required" << endl; - return false; - } - for (int i=0; i<(int)m_options.size(); i++) { - prepareSearch(i); - processFile(infile); + +////////////////////////////// +// +// MeasureDataSet::getScoreDuration -- +// + +double MeasureDataSet::getScoreDuration(void) { + if (m_data.empty()) { + return 0.0; } - return true; + return m_data[0]->getScoreDuration(); + } +/////////////////////////////////////////////////////////////////////////// + ////////////////////////////// // -// Tool_shed::prepareSearch -- +// MeasureComparison::MeasureComparison -- // -void Tool_shed::prepareSearch(int index) { - // deal with command-line options (seprately for each search): - m_exinterps.clear(); +MeasureComparison::MeasureComparison() { + // do nothing +} - if (getBoolean("kern")) { - m_exinterps.push_back("**kern"); - } else if (getBoolean("exclusive-interpretations")) { - vector extra = addToExInterpList(); - for (int i=0; i<(int)extra.size(); i++) { - m_exinterps.push_back(extra[i]); - } - } - m_search = m_searches.at(index); - m_replace = m_replaces.at(index); - m_option = m_options.at(index); +MeasureComparison::MeasureComparison(MeasureData& data1, MeasureData& data2) { + compare(data1, data2); +} - m_grepoptions = ""; - if (m_option.find("i") != std::string::npos) { - m_grepoptions += "i"; - } - if (m_option.find("g") != std::string::npos) { - m_grepoptions += "g"; - } - if (m_option.find("X") != std::string::npos) { - if (m_xInterp != "") { - m_exinterps.push_back(m_xInterp); - } - } - if (m_option.find("Y") != std::string::npos) { - if (m_yInterp != "") { - m_exinterps.push_back(m_yInterp); - } - } - if (m_option.find("Z") != std::string::npos) { - if (m_zInterp != "") { - m_exinterps.push_back(m_zInterp); - } - } +MeasureComparison::MeasureComparison(MeasureData* data1, MeasureData* data2) { + compare(data1, data2); +} - m_data = true; // process data - m_barline = false; // process barline - m_exinterp = false; // process exclusive interpretations - m_interpretation = false; // process interpretations (other than exinterp - // and spine manipulators). - if (m_option.find("I") != std::string::npos) { - m_interpretation = true; - m_data = false; - } - if (m_option.find("X") != std::string::npos) { - m_exinterp = true; - m_data = false; - } - if (m_option.find("B") != std::string::npos) { - m_barline = true; - m_data = false; - } - if (m_option.find("M") != std::string::npos) { - // measure is an alias for barline - m_barline = true; - m_data = false; - } - if (m_option.find("L") != std::string::npos) { - m_localcomment = true; - m_data = false; - } - if (m_option.find("G") != std::string::npos) { - m_globalcomment = true; - m_data = false; - } - if (m_option.find("K") != std::string::npos) { - m_referencekey = true; - m_data = false; - } - if (m_option.find("V") != std::string::npos) { - m_referencevalue = true; - m_data = false; - } - if (m_option.find("R") != std::string::npos) { - m_reference = true; - m_referencekey = false; - m_referencevalue = false; - m_data = false; - } - if (m_option.find("D") != std::string::npos) { - m_data = true; - } +////////////////////////////// +// +// MeasureComparison::~MeasureComparison -- +// + +MeasureComparison::~MeasureComparison() { + clear(); } ////////////////////////////// // -// Tool_shed::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// MeasureComparison::clear -- // -void Tool_shed::initialize(void) { - if (getBoolean("expression")) { - string value = getString("expression"); - parseExpression(value); - } - m_exclusion = getString("exclusion-expression"); - - if (getBoolean("X")) { - m_xInterp = getExInterp(getString("X")); - } - if (getBoolean("Y")) { - m_yInterp = getExInterp(getString("Y")); - } - if (getBoolean("Z")) { - m_zInterp = getExInterp(getString("Z")); - } +void MeasureComparison::clear(void) { + correlation7pc = 0.0; } ////////////////////////////// // -// Tool_shed::getExInterp -- +// MeasureComparison::compare -- // -string Tool_shed::getExInterp(const string& value) { - if (value == "") { - return "**"; +void MeasureComparison::compare(MeasureData& data1, MeasureData& data2) { + compare(&data1, &data2); +} + + +void MeasureComparison::compare(MeasureData* data1, MeasureData* data2) { + double sum1 = data1->getSum7pc(); + double sum2 = data2->getSum7pc(); + if ((sum1 == sum2) && (sum1 == 0.0)) { + correlation7pc = 1.0; + return; } - if (value == "*") { - return "**"; + if (sum1 == 0.0) { + correlation7pc = 0.0; + return; } - if (value.compare(0, 2, "**") == 0) { - return value; + if (sum2 == 0.0) { + correlation7pc = 0.0; + return; } - if (value.compare(0, 1, "*") == 0) { - return "*" + value; + correlation7pc = Convert::pearsonCorrelation(data1->getHistogram7pc(), data2->getHistogram7pc()); + if (fabs(correlation7pc - 1.0) < 0.00000001) { + correlation7pc = 1.0; } - return "**" + value; } ////////////////////////////// // -// Tool_shed::parseExpression -- -// Form of string: -// s/search/replace/options; s/search2/replace2/options2 +// MeasureComparison::getCorrelation7pc -- // + +double MeasureComparison::getCorrelation7pc(void) { + return correlation7pc; +} + +////////////////////////////////////////////////////////////////////////// + +////////////////////////////// +// +// MeasureComparisonGrid::MeasureComparisonGrid -- // -void Tool_shed::parseExpression(const string& expression) { - int state = 0; +MeasureComparisonGrid::MeasureComparisonGrid(void) { + // do nothing +} - m_searches.clear(); - m_replaces.clear(); - m_options.clear(); - char divchar = '/'; +MeasureComparisonGrid::MeasureComparisonGrid(MeasureDataSet& set1, MeasureDataSet& set2) { + analyze(set1, set2); +} - for (int i=0; i<(int)expression.size(); i++) { - if (state == 0) { // start of expression - if (isspace(expression[i])) { - continue; - } else if (expression[i] == 's') { - if (i >= (int)expression.size() - 1) { - cerr << "Error: spurious s at end of expression: " - << expression << endl; - return; - } else { - divchar = expression[i+1]; - i++; - state++; - m_searches.push_back(""); - } - } else { - cerr << "Error at position " << i - << " in expression: " << expression << endl; - return; - } - } else if (state == 1) { // search string - if (expression[i] == divchar) { - state++; - m_replaces.push_back(""); - continue; - } if (expression[i] == '\\') { - if (i >= (int)expression.size() - 1) { - cerr << "Error: expression ends too soon: " - << expression << endl; - return; - } else { - m_searches.back() += '\\'; - m_searches.back() += expression[i+1]; - i++; - } - } else { - m_searches.back() += expression[i]; - } - } else if (state == 2) { // replace string - if (expression[i] == divchar) { - state++; - m_options.push_back(""); - continue; - } if (expression[i] == '\\') { - if (i >= (int)expression.size() - 1) { - cerr << "Error: expression ends too soon: " - << expression << endl; - return; - } else { - m_replaces.back() += '\\'; - m_replaces.back() += expression[i+1]; - i++; - } - } else { - m_replaces.back() += expression[i]; - } - } else if (state == 3) { // regular expression options - if (expression[i] == ';') { - state++; - } else if (isspace(expression[i])) { - state++; - } else { - m_options.back() += expression[i]; - } - } - if (state == 4) { - state = 0; - } - } + +MeasureComparisonGrid::MeasureComparisonGrid(MeasureDataSet* set1, MeasureDataSet* set2) { + analyze(set1, set2); } ////////////////////////////// // -// Tool_shed::initializeSegment -- Recalculate variables for each Humdrum -// input segment. +// MeasureComparisonGrid::~MeasureComparisonGrid -- // -void Tool_shed::initializeSegment(HumdrumFile& infile) { - m_spines.clear(); - if (getBoolean("spines")) { - int maxtrack = infile.getMaxTrack(); - Convert::makeBooleanTrackList(m_spines, getString("spines"), maxtrack); - } +MeasureComparisonGrid::~MeasureComparisonGrid() { + // do nothing } ////////////////////////////// // -// Tool_shed::addToExInterpList -- +// MeasureComparisonGrid::clear -- // -vector Tool_shed::addToExInterpList(void) { - string elist = getString("exclusive-interpretations"); - elist = Convert::trimWhiteSpace(elist); - HumRegex hre; - hre.replaceDestructive(elist, "", "^[,;\\s*]+"); - hre.replaceDestructive(elist, "", "[,;\\s*]+$"); - vector pieces; - hre.split(pieces, elist, "[,;\\s*]+"); +void MeasureComparisonGrid::clear(void) { + m_grid.clear(); +} - vector output; - for (int i=0; i<(int)pieces.size(); i++) { - if (pieces[i].empty()) { - continue; + + +////////////////////////////// +// +// MeasureComparisonGrid::analyze -- +// + +void MeasureComparisonGrid::analyze(MeasureDataSet* set1, MeasureDataSet* set2) { + analyze(*set1, *set2); +} + +void MeasureComparisonGrid::analyze(MeasureDataSet& set1, MeasureDataSet& set2) { + m_grid.resize(set1.size()); + for (int i=0; i<(int)m_grid.size(); i++) { + m_grid[i].resize(set2.size()); + } + for (int i=0; i<(int)m_grid.size(); i++) { + for (int j=0; j<(int)m_grid[i].size(); j++) { + m_grid[i][j].compare(set1[i], set2[j]); } - output.push_back("**" + pieces[i]); } - return output; + m_set1 = &set1; + m_set2 = &set2; } ////////////////////////////// // -// Tool_shed::processFile -- +// MeasureComparisonGrid::printCorrelationGrid -- +// default value: out = std::cout // -void Tool_shed::processFile(HumdrumFile& infile) { - if (m_search == "") { - // nothing to do - return; +ostream& MeasureComparisonGrid::printCorrelationGrid(ostream& out) { + for (int i=0; i<(int)m_grid.size(); i++) { + for (int j=0; j<(int)m_grid[i].size(); j++) { + double correl = m_grid[i][j].getCorrelation7pc(); + if (correl > 0.0) { + out << int(correl * 100.0 + 0.5)/100.0; + } else { + out << -int(-correl * 100.0 + 0.5)/100.0; + } + if (j < (int)m_grid[i].size() - 1) { + out << '\t'; + } + } + out << endl; } - m_modified = false; + return out; +} - if (m_interpretation) { - searchAndReplaceInterpretation(infile); - } - if (m_localcomment) { - searchAndReplaceLocalComment(infile); - } - if (m_globalcomment) { - searchAndReplaceGlobalComment(infile); - } +////////////////////////////// +// +// MeasureComparisonGrid::printCorrelationDiagonal -- Assuming a square grid for now. +// default value: out = std::cout +// - if (m_reference) { - searchAndReplaceReferenceRecords(infile); +ostream& MeasureComparisonGrid::printCorrelationDiagonal(ostream& out) { + for (int i=0; i<(int)m_grid.size(); i++) { + for (int j=0; j<(int)m_grid[i].size(); j++) { + if (i != j) { + continue; + } + double correl = m_grid[i][j].getCorrelation7pc(); + if (correl > 0.0) { + out << int(correl * 100.0 + 0.5)/100.0; + } else { + out << -int(-correl * 100.0 + 0.5)/100.0; + } + if (j < (int)m_grid[i].size() - 1) { + out << '\t'; + } + } + out << endl; } + return out; +} - if (m_referencekey) { - searchAndReplaceReferenceKeys(infile); - } - if (m_referencevalue) { - searchAndReplaceReferenceValues(infile); + +////////////////////////////// +// +// MeasureComparisonGrid::getColorMapping -- +// + +void MeasureComparisonGrid::getColorMapping(double input, double& hue, + double& saturation, double& lightness) { + double maxhue = 0.75 * 360.0; + hue = input; + if (hue < 0.0) { + hue = 0.0; + } + hue = hue * hue; + if (hue != 1.0) { + hue *= 0.95; } - if (m_exinterp) { - searchAndReplaceExinterp(infile); + hue = (1.0 - hue) * 360.0; + if (hue == 0.0) { + // avoid -0.0; + hue = 0.0; } - if (m_barline) { - searchAndReplaceBarline(infile); + if (hue > maxhue) { + hue = maxhue; + } + if (hue < 0.0) { + hue = maxhue; } - if (m_data) { - searchAndReplaceData(infile); + saturation = 100.0; + lightness = 50.0; + + if (hue > 60) { + lightness = lightness - (hue-60) / (maxhue-60) * lightness / 1.5; } +} - if (m_modified) { - infile.createLinesFromTokens(); + + +////////////////////////////// +// +// MeasureComparisonGrid::getQoff1 -- return the end time class ID of the +// current grid cell (for the first piece being compared). +// + +std::string MeasureComparisonGrid::getQoff1(int index) { + if (m_set1 == NULL) { + return ""; } + return (*m_set1)[index].getQoff(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceBarline -- +// MeasureComparisonGrid::getQoff2 -- return the end time class ID of the +// current grid cell (for the first piece being compared). // -void Tool_shed::searchAndReplaceBarline(HumdrumFile& infile) { - string isearch; - if (m_search[0] == '^') { - isearch = "^=" + m_search.substr(1); - } else { - isearch = "^=.*" + m_search; +std::string MeasureComparisonGrid::getQoff2(int index) { + if (m_set2 == NULL) { + return ""; } - HumRegex hre; - for (int i=0; iisNull()) { - // Don't mess with null interpretations - continue; - } - if (!isValid(token)) { - continue; - } - if (hre.search(token, isearch, m_grepoptions)) { - string text = token->getText().substr(1); - hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); - hre.replaceDestructive(text, "", "^=+"); - text = "=" + text; - token->setText(text); - m_modified = true; - } - } + return (*m_set2)[index].getQoff(); +} + + + +////////////////////////////// +// +// MeasureComparisonGrid::getQon1 -- return the start time class ID of the +// current grid cell (for the first piece being compared). +// + +string MeasureComparisonGrid::getQon1(int index) { + if (m_set1 == NULL) { + return ""; } + return (*m_set1)[index].getQon(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceInterpretation -- +// MeasureComparisonGrid::getQon2 -- return the start time class ID of the +// current grid cell (for the second piece being compared). // -void Tool_shed::searchAndReplaceInterpretation(HumdrumFile& infile) { - string isearch; - if (m_search[0] == '^') { - isearch = "^\\*" + m_search.substr(1); - } else { - isearch = "^\\*.*" + m_search; +string MeasureComparisonGrid::getQon2(int index) { + if (m_set2 == NULL) { + return ""; } - HumRegex hre; - for (int i=0; iisNull()) { - // Don't mess with null interpretations - continue; - } - if (!isValid(token)) { - continue; - } - if (hre.search(token, isearch, m_grepoptions)) { - string text = token->getText().substr(1); - hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); - hre.replaceDestructive(text, "", "^\\*+"); - text = "*" + text; - token->setText(text); - m_modified = true; - } - } + return (*m_set2)[index].getQon(); +} + + + +////////////////////////////// +// +// MeasureComparisonGrid::getMeasure1 -- return the measure of the +// current grid cell (for the first piece being compared). +// + +int MeasureComparisonGrid::getMeasure1(int index) { + if (m_set1 == NULL) { + return 0.0; } + return (*m_set1)[index].getMeasure(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceLocalComment -- +// MeasureComparisonGrid::getMeasure2 -- return the measure of the +// current grid cell (for the second piece being compared). // -void Tool_shed::searchAndReplaceLocalComment(HumdrumFile& infile) { - string isearch; - if (m_search[0] == '^') { - isearch = "^!" + m_search.substr(1); - } else { - isearch = "^!.*" + m_search; +int MeasureComparisonGrid::getMeasure2(int index) { + if (m_set2 == NULL) { + return 0.0; } - HumRegex hre; - for (int i=0; iisNull()) { - // Don't mess with null interpretations - continue; - } - if (!isValid(token)) { - continue; - } - if (hre.search(token, isearch, m_grepoptions)) { - string text = token->getText().substr(1); - hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); - hre.replaceDestructive(text, "", "^!+"); - text = "!" + text; - token->setText(text); - m_modified = true; - } - } + return (*m_set2)[index].getMeasure(); +} + + + +////////////////////////////// +// +// MeasureComparisonGrid::getStartTime1 -- return the start time of the +// measure at index position in the first compared score. +// + +double MeasureComparisonGrid::getStartTime1(int index) { + if (m_set1 == NULL) { + return 0.0; } + return (*m_set1)[index].getStartTime(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceGlobalComment -- +// MeasureComparisonGrid::getScoreDuration1 -- // -void Tool_shed::searchAndReplaceGlobalComment(HumdrumFile& infile) { - string isearch; - if (m_search[0] == '^') { - isearch = "^!!" + m_search.substr(1); - } else { - isearch = "^!!.*" + m_search; +double MeasureComparisonGrid::getScoreDuration1(void) { + if (m_set1 == NULL) { + return 0.0; } - HumRegex hre; - for (int i=0; isize() < 3) { - // Don't mess with null comments - continue; - } - if (hre.search(token, isearch, m_grepoptions)) { - string text = token->getText().substr(2); - hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); - hre.replaceDestructive(text, "", "^!+"); - text = "!!" + text; - token->setText(text); - m_modified = true; - } + return m_set1->getScoreDuration(); +} + + + +////////////////////////////// +// +// MeasureComparisonGrid::getStartTime2 -- +// + +double MeasureComparisonGrid::getStartTime2(int index) { + if (m_set2 == NULL) { + return 0.0; } + return (*m_set2)[index].getStartTime(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceReferenceRecords -- +// MeasureComparisonGrid::getStopTime1 -- // -void Tool_shed::searchAndReplaceReferenceRecords(HumdrumFile& infile) { - string isearch; - if (m_search[0] == '^') { - isearch = "^!!!" + m_search.substr(1); - } else { - isearch = "^!!!.*" + m_search; - } - HumRegex hre; - for (int i=0; igetText().substr(1); - hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); - hre.replaceDestructive(text, "", "^!+"); - text = "!!!" + text; - token->setText(text); - m_modified = true; - } +double MeasureComparisonGrid::getStopTime1(int index) { + if (m_set1 == NULL) { + return 0.0; } + return (*m_set1)[index].getStopTime(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceReferenceKeys -- +// MeasureComparisonGrid::getStopTime2 -- // -void Tool_shed::searchAndReplaceReferenceKeys(HumdrumFile& infile) { - string isearch = m_search; - HumRegex hre; - for (int i=0; isetText(text); - m_modified = true; - } +double MeasureComparisonGrid::getStopTime2(int index) { + if (m_set2 == NULL) { + return 0.0; } + return (*m_set2)[index].getStopTime(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceReferenceValues -- +// MeasureComparisonGrid::getDuration1 -- // -void Tool_shed::searchAndReplaceReferenceValues(HumdrumFile& infile) { - string isearch = m_search; - HumRegex hre; - for (int i=0; isetText(text); - m_modified = true; - } +double MeasureComparisonGrid::getDuration1(int index) { + if (m_set1 == NULL) { + return 0.0; } + return (*m_set1)[index].getDuration(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceExinterp -- +// MeasureComparisonGrid::getDuration2 -- // -void Tool_shed::searchAndReplaceExinterp(HumdrumFile& infile) { - string isearch; - if (m_search[0] == '^') { - isearch = "^\\*\\*" + m_search.substr(1); - } else { - isearch = "^\\*\\*.*" + m_search; +double MeasureComparisonGrid::getDuration2(int index) { + if (m_set2 == NULL) { + return 0.0; } - HumRegex hre; - for (int i=0; iisNull()) { - // Don't mess with null interpretations - continue; - } - if (!isValid(token)) { - continue; - } - if (hre.search(token, isearch, m_grepoptions)) { - string text = token->getText().substr(2); - hre.replaceDestructive(text, m_replace, m_search, m_grepoptions); - hre.replaceDestructive(text, "", "^\\*+"); - text = "**" + text; - token->setText(text); - m_modified = true; - } - } + return (*m_set2)[index].getDuration(); +} + + + +////////////////////////////// +// +// MeasureComparisonGrid::getScoreDuration2 -- +// + +double MeasureComparisonGrid::getScoreDuration2(void) { + if (m_set2 == NULL) { + return 0.0; } + return m_set2->getScoreDuration(); } ////////////////////////////// // -// Tool_shed::searchAndReplaceData -- +// MeasureComparisonGrid::printSvgGrid -- +// default value: out = std::cout // -void Tool_shed::searchAndReplaceData(HumdrumFile& infile) { - string dsearch = m_search; +ostream& MeasureComparisonGrid::printSvgGrid(ostream& out) { + pugi::xml_document image; + auto declaration = image.prepend_child(pugi::node_declaration); + declaration.append_attribute("version") = "1.0"; + declaration.append_attribute("encoding") = "UTF-8"; + declaration.append_attribute("standalone") = "no"; - HumRegex hre; - for (int i=0; iisNull()) { - // Don't mess with null interpretations - continue; - } - if (!isValid(token)) { - continue; - } - if (hre.search(token, dsearch, m_grepoptions)) { - string text = token->getText(); - hre.replaceDestructive(text, m_replace, dsearch, m_grepoptions); - if (text == "") { - text = "."; - } - token->setText(text); - m_modified = true; - } + auto svgnode = image.append_child("svg"); + svgnode.append_attribute("version") = "1.1"; + svgnode.append_attribute("xmlns") = "http://www.w3.org/2000/svg"; + svgnode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink"; + svgnode.append_attribute("overflow") = "visible"; + svgnode.append_attribute("viewBox") = "0 0 1000 1000"; + svgnode.append_attribute("width") = "1000px"; + svgnode.append_attribute("height") = "1000px"; + + auto grid = svgnode.append_child("g"); + grid.append_attribute("id") = "grid"; + + double hue = 0.0; + double saturation = 100; + double lightness = 75; + + pugi::xml_node crect; + double width; + double height; + + stringstream ss; + stringstream css; + double x; + double y; + + double imagewidth = 1000.0; + double imageheight = 1000.0; + + double sdur1 = getScoreDuration1(); + double sdur2 = getScoreDuration2(); + + for (int i=0; i<(int)m_grid.size(); i++) { + for (int j=0; j<(int)m_grid[i].size(); j++) { + width = getDuration2(j) / sdur2 * imagewidth; + height = getDuration1(i) / sdur1 * imageheight; + + x = getStartTime2(j)/sdur2 * imageheight; + y = getStartTime1(i)/sdur1 * imagewidth; + + getColorMapping(m_grid[i][j].getCorrelation7pc(), hue, saturation, lightness); + ss << "hsl(" << hue << "," << saturation << "%," << lightness << "%)"; + crect = grid.append_child("rect"); + crect.append_attribute("x") = to_string(x).c_str(); + crect.append_attribute("y") = to_string(y).c_str(); + crect.append_attribute("width") = to_string(width*0.99).c_str(); + crect.append_attribute("height") = to_string(height*0.99).c_str(); + crect.append_attribute("fill") = ss.str().c_str(); + css << "Xm" << getMeasure1(i) << " Ym" << getMeasure2(j); + css << " X" << getQon1(i) << " Y" << getQon2(j); + css << " X" << getQoff1(i) << " Y" << getQoff2(j); + crect.append_attribute("class") = css.str().c_str(); + ss.str(""); + css.str(""); } } + + image.save(out); + return out; } +/////////////////////////////////////////////////////////////////////////// + -////////////////////////////// +///////////////////////////////// // -// Tool_shed::isValidDataType -- usar with -x and -k options. +// Tool_simat::Tool_simat -- Set the recognized options for the tool. // -bool Tool_shed::isValidDataType(HTp token) { - if (m_exinterps.empty()) { - return true; +Tool_simat::Tool_simat(void) { + define("r|raw=b", "output raw correlation matrix"); + define("d|diagonal=b", "output diagonal of correlation matrix"); +} + + + +///////////////////////////////// +// +// Tool_simat::run -- Primary interfaces to the tool. +// + +bool Tool_simat::run(HumdrumFileSet& infiles) { + bool status = true; + if (infiles.getCount() == 1) { + status = run(infiles[0], infiles[0]); + } else if (infiles.getCount() > 1) { + status = run(infiles[0], infiles[1]); + } else { + status = false; } - string datatype = token->getDataType(); - for (int i=0; i<(int)m_exinterps.size(); i++) { - if (datatype == m_exinterps[i]) { - return true; - } + return status; +} + + +bool Tool_simat::run(const string& indata1, const string& indata2, ostream& out) { + HumdrumFile infile1(indata1); + HumdrumFile infile2; + bool status; + if (indata2.empty()) { + infile2.read(indata2); + status = run(infile1, infile2); + } else { + status = run(infile1, infile1); } - return false; + if (hasAnyText()) { + getAllText(out); + } else { + out << infile1; + out << infile2; + } + return status; } +bool Tool_simat::run(HumdrumFile& infile1, HumdrumFile& infile2, ostream& out) { + bool status; + if (infile2.getLineCount() == 0) { + status = run(infile1, infile1); + } else { + status = run(infile1, infile2); + } + if (hasAnyText()) { + getAllText(out); + } else { + out << infile1; + out << infile2; + } + return status; +} -////////////////////////////// // -// Tool_shed::isValidSpine -- used with -s option. +// In-place processing of file: // -bool Tool_shed::isValidSpine(HTp token) { - if (m_spines.empty()) { - return true; +bool Tool_simat::run(HumdrumFile& infile1, HumdrumFile& infile2) { + if (infile2.getLineCount() == 0) { + processFile(infile1, infile1); + } else { + processFile(infile1, infile2); } - int track = token->getTrack(); - return m_spines.at(track); + + return true; } ////////////////////////////// // -// Tool_shed::isValid -- +// Tool_simat::processFile -- // -bool Tool_shed::isValid(HTp token) { - if (!m_exclusion.empty()) { - HumRegex hre; - if (hre.search(token, m_exclusion)) { - return false; - } - } - if (isValidDataType(token) && isValidSpine(token)) { - return true; +void Tool_simat::processFile(HumdrumFile& infile1, HumdrumFile& infile2) { + m_data1.parse(infile1); + m_data2.parse(infile2); + m_grid.analyze(m_data1, m_data2); + if (getBoolean("raw")) { + m_grid.printCorrelationGrid(m_free_text); + suppressHumdrumFileOutput(); + } else if (getBoolean("diagonal")) { + m_grid.printCorrelationDiagonal(m_free_text); + suppressHumdrumFileOutput(); + } else { + m_grid.printSvgGrid(m_free_text); + suppressHumdrumFileOutput(); } - return false; } @@ -123006,25 +126969,25 @@ bool Tool_shed::isValid(HTp token) { ///////////////////////////////// // -// Tool_sic::Tool_sic -- Set the recognized options for the tool. +// Tool_slurcheck::Tool_slurcheck -- Set the recognized options for the tool. // -Tool_sic::Tool_sic(void) { - define("s|substitution=b", "insert substitutions into music"); - define("o|original=b", "insert originals into music"); - define("r|remove=b", "remove sic layout tokens"); - define("v|verbose=b", "add verbose parameter"); - define("q|quiet=b", "remove verbose parameter"); +Tool_slurcheck::Tool_slurcheck(void) { + // add options here + define("l|list=b", "list locations of unclosed slur endings"); + define("c|count=b", "count unclosed slur endings"); + define("Z|no-zeros=b", "do not list files that have zero unclosed slurs in counts"); + define("f|filename=b", "print filename for list and count options"); } ///////////////////////////////// // -// Tool_sic::run -- Do the main work of the tool. +// Tool_slurcheck::run -- Do the main work of the tool. // -bool Tool_sic::run(HumdrumFileSet& infiles) { +bool Tool_slurcheck::run(HumdrumFileSet& infiles) { bool status = true; for (int i=0; iisKern()) { continue; } - for (int j=0; jcompare(0, 8, "!LO:SIC:") != 0) { + HTp etok = infile.getStrandEnd(i); + HTp tok = stok; + while (tok && (tok != etok)) { + if (!tok->isData()) { + tok = tok->getNextToken(); continue; } - if (m_verboseQ) { - addVerboseParameter(token); - } else if (m_quietQ) { - removeVerboseParameter(token); + if (tok->isNull()) { + tok = tok->getNextToken(); + continue; } - if (m_removeQ) { - token->setText("!"); - m_modifiedQ = true; - } else if (m_substituteQ) { - insertSubstitutionToken(token); - } else if (m_originalQ) { - insertOriginalToken(token); + string value = tok->getValue("auto", "hangingSlur"); + if (value == "true") { + string side = tok->getValue("auto", "slurSide"); + if (side == "start") { + opencount++; + if (listQ) { + if (filenameQ) { + m_free_text << infile.getFilename() << ":\t"; + } + m_free_text << "UNCLOSED SLUR\tline:" << tok->getLineIndex()+1 + << "\tfield:" << tok->getFieldIndex()+1 << "\ttoken:" << tok << endl; + } else if (!countQ) { + string data = *tok; + data += "i"; + tok->setText(data); + } + } else if (side == "stop") { + closecount++; + if (listQ) { + if (filenameQ) { + m_free_text << infile.getFilename() << ":\t"; + } + m_free_text << "UNOPENED SLUR\tline:" << tok->getLineIndex()+1 + << "\tfield:" << tok->getFieldIndex()+1 << "\ttoken:" << tok << endl; + } else if (!countQ) { + string data = *tok; + data += "j"; + tok->setText(data); + } + } } + tok = tok->getNextToken(); } } - if (m_modifiedQ) { - infile.createLinesFromTokens(); + + if (countQ) { + int sum = opencount + closecount; + if ((!zeroQ) && (sum == 0)) { + return; + } + if (filenameQ) { + m_free_text << infile.getFilename() << ":\t"; + } + m_free_text << (opencount + closecount) << "\t(:" << opencount << "\t):" << closecount << endl; } - m_humdrum_text << infile; + + if (countQ || listQ) { + return; + } + + if (opencount + closecount == 0) { + return; + } + + if (opencount) { + infile.appendLine("!!!RDF**kern: i = marked note, color=\"hotpink\", text=\"extra(\""); + } + + if (closecount) { + infile.appendLine("!!!RDF**kern: j = marked note, color=\"magenta\", text=\"extra)\""); + } + + infile.createLinesFromTokens(); } -////////////////////////////// + + +///////////////////////////////// // -// Tool_sic::addVerboseParameter -- +// Tool_gridtest::Tool_spinetrace -- Set the recognized options for the tool. +// + +Tool_spinetrace::Tool_spinetrace(void) { + define("a|append=b", "append analysis to input data lines"); + define("p|prepend=b", "prepend analysis to input data lines"); +} + + + +/////////////////////////////// // +// Tool_spinetrace::run -- Primary interfaces to the tool. +// + +bool Tool_spinetrace::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; igetText(); - if (hre.search(value, "(:v:)|(:v$)")) { - return; - } - string newvalue = value + ":v"; - token->setText(newvalue); - m_modifiedQ = true; + +bool Tool_spinetrace::run(HumdrumFile& infile) { + initialize(infile); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_sic::removeVerboseParameter -- +// Tool_spinetrace::initialize -- // -void Tool_sic::removeVerboseParameter(HTp token) { - HumRegex hre; - string value = token->getText(); - string newvalue = value; - hre.replaceDestructive(newvalue, ":", ":v:", "g"); - hre.replaceDestructive(newvalue, "", ":v$", ""); - if (value == newvalue) { - return; - } - token->setText(newvalue); - m_modifiedQ = true; +void Tool_spinetrace::initialize(HumdrumFile& infile) { + // do nothing for now } ////////////////////////////// // -// Tool_sic::getTargetToken -- Get the token that the layout command -// applies to. +// Tool_spinetrace::processFile -- // -HTp Tool_sic::getTargetToken(HTp stok) { - HTp current = stok->getNextToken(); - while (current) { - if (current->isNull()) { - current = current->getNextToken(); +void Tool_spinetrace::processFile(HumdrumFile& infile) { + bool appendQ = getBoolean("append"); + bool prependQ = getBoolean("prepend"); + + int linecount = infile.getLineCount(); + for (int i=0; iisManipulator()) { - // Layout commands should not apply to manipulators nor be split - // from their associated token. - current = NULL; - break; + if (appendQ) { + m_humdrum_text << infile[i] << "\t"; } - if (current->isCommentLocal()) { - current = current->getNextToken(); - continue; + + if (!infile[i].isData()) { + if (infile[i].isInterpretation()) { + int fieldcount = infile[i].getFieldCount(); + for (int j=0; jcompare(0, 2, "**") == 0) { + m_humdrum_text << "**spine"; + } else { + m_humdrum_text << token; + } + if (j < fieldcount - 1) { + m_humdrum_text << "\t"; + } + } + } else { + m_humdrum_text << infile[i]; + } + } else { + int fieldcount = infile[i].getFieldCount(); + for (int j=0; jgetSpineInfo(); + if (j < fieldcount - 1) { + m_humdrum_text << '\t'; + } + } } - break; - } - if (!current) { - return NULL; + + if (prependQ) { + m_humdrum_text << "\t" << infile[i]; + } + m_humdrum_text << "\n"; } - return current; } -////////////////////////////// + +///////////////////////////////// // -// Tool_sic::insertSubstitutionToken -- +// Tool_strophe::Tool_strophe -- Set the recognized options for the tool. // -void Tool_sic::insertSubstitutionToken(HTp sictok) { - HTp target = getTargetToken(sictok); - if (!target) { - return; - } - HumRegex hre; - vector pieces; - hre.split(pieces, *sictok, ":"); - string tstring = target->getText(); - string sstring; - for (int i=2; i<(int)pieces.size(); i++) { - if (pieces[i].compare(0, 2, "s=") == 0) { - sstring = pieces[i].substr(2); - } - } - if (sstring.empty()) { - return; - } - target->setText(sstring); - m_modifiedQ = true; - string newsic = "!LO:SIC"; - for (int i=2; i<(int)pieces.size(); i++) { - if (pieces[i].compare(0, 2, "s=") == 0) { - newsic += ":o=" + tstring; - } else { - newsic += ":" + pieces[i]; - } - } - sictok->setText(newsic); - m_modifiedQ = true; +Tool_strophe::Tool_strophe(void) { + define("l|list=b", "list all possible variants"); + define("m=b", "mark strophe music"); + define("mark|marker=s:@", "character to mark with"); + define("c|color=s:red", "character to mark with"); } -////////////////////////////// +///////////////////////////////// // -// Tool_sic::insertOriginalToken -- +// Tool_strophe::run -- Do the main work of the tool. // -void Tool_sic::insertOriginalToken(HTp sictok) { - HTp target = getTargetToken(sictok); - if (!target) { - return; - } - HumRegex hre; - vector pieces; - hre.split(pieces, *sictok, ":"); - string tstring = target->getText(); - string sstring; - for (int i=2; i<(int)pieces.size(); i++) { - if (pieces[i].compare(0, 2, "o=") == 0) { - sstring = pieces[i].substr(2); - } - } - if (sstring.empty()) { - return; +bool Tool_strophe::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; isetText(sstring); - m_modifiedQ = true; - string newsic = "!LO:SIC"; - for (int i=2; i<(int)pieces.size(); i++) { - if (pieces[i].compare(0, 2, "o=") == 0) { - newsic += ":s=" + tstring; - } else { - newsic += ":" + pieces[i]; - } + for (auto it = m_variants.begin(); it != m_variants.end(); ++it) { + m_free_text << *it << endl; } - sictok->setText(newsic); - m_modifiedQ = true; + return status; } - - - -////////////////////////////// -// -// MeasureData::MeasureData -- -// - -MeasureData::MeasureData(void) { - m_hist7pc.resize(7); - std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0); +bool Tool_strophe::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else if (!m_listQ) { + out << infile; + } + return status; } -MeasureData::MeasureData(HumdrumFile& infile, int startline, int stopline) { - setStartLine(startline); - setStopLine(stopline); - setOwner(infile); +bool Tool_strophe::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else if (!m_listQ) { + out << infile; + } + return status; } -MeasureData::MeasureData(HumdrumFile* infile, int startline, int stopline) { - setStartLine(startline); - setStopLine(stopline); - setOwner(infile); +bool Tool_strophe::run(HumdrumFile& infile) { + initialize(); + processFile(infile); + return true; } ////////////////////////////// // -// MeasureData::~MeasureData -- +// Tool_strophe::initialize -- Initializations that only have to be done once +// for all HumdrumFile segments. // -MeasureData::~MeasureData() { - clear(); +void Tool_strophe::initialize(void) { + m_listQ = getBoolean("list"); + m_markQ = getBoolean("m"); + m_marker = getString("marker"); + m_color = getString("color"); } ////////////////////////////// // -// MeasureData::setOwner -- +// Tool_strophe::processFile -- // -void MeasureData::setOwner(HumdrumFile* infile) { - m_owner = infile; -} - - -void MeasureData::setOwner(HumdrumFile& infile) { - m_owner = &infile; +void Tool_strophe::processFile(HumdrumFile& infile) { + infile.analyzeStrophes(); + if (m_listQ) { + displayStropheVariants(infile); + } else { + markWithColor(infile); + } } ////////////////////////////// // -// MeasureData::setStartLine -- +// Tool_strophe::markWithColor -- Maybe give different colors +// to different variants. Currently only marking the primary +// strophe. // -void MeasureData::setStartLine(int startline) { - m_startline = startline; +void Tool_strophe::markWithColor(HumdrumFile& infile) { + int counter = 0; + for (int i=0; iisData() && !current->isNull()) { + // Think about multiple marking for individual notes in chords. + string value = current->getText(); + value += m_marker; + current->setText(value); + output++; + } + current = current->getNextToken(); + } + return output; } ////////////////////////////// // -// MeasureData::getStartLine -- +// displayStropheVariants -- // -int MeasureData::getStartLine(void) { - return m_startline; +void Tool_strophe::displayStropheVariants(HumdrumFile& infile) { + for (int i=0; icompare(0, 3, "*S/") != 0) { + continue; + } + string variant = token->substr(3); + m_variants.insert(variant); + } + } } -////////////////////////////// -// -// MeasureData::getStopLine -- -// -int MeasureData::getStopLine(void) { - return m_stopline; -} -////////////////////////////// +///////////////////////////////// // -// MeasureData::getStartTime -- return the start time in -// quarter notes +// Tool_synco::Tool_synco -- Set the recognized options for the tool. // -double MeasureData::getStartTime(void) { - if (m_owner == NULL) { - return 0.0; - } - if (getStartLine() < 0) { - return 0.0; - } - return (*m_owner)[getStartLine()].getDurationFromStart().getFloat(); +Tool_synco::Tool_synco(void) { + define("c|color=s:skyblue", "SVG color to highlight syncopation notes"); + define("i|info=b", "display only statistics info"); + define("f|filename=b", "add filename to statistics info"); + define("a|all=b", "average all statistics info"); } -////////////////////////////// +///////////////////////////////// // -// MeasureData::getMeasure -- return the measure number of the measure. -// return -1 if no measure number. +// Tool_synco::run -- Do the main work of the tool. // -int MeasureData::getMeasure(void) { - if (m_owner == NULL) { - return -1; - } - if (getStartLine() < 0) { - return -1; - } - HumdrumFile& infile = *m_owner; - if (!infile[getStartLine()].isBarline()) { - return -1; +bool Tool_synco::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; iisKern()) { + continue; + } + HTp etok = infile.getStrandEnd(i); + processStrand(stok, etok); } - return m_owner->getScoreDuration().getFloat(); } ////////////////////////////// // -// MeasureData::clear -- +// Tool_synco::processStrand -- // -void MeasureData::clear(void) { - m_owner = NULL; - m_owner = NULL; - m_startline = -1; - m_startline = -1; - m_hist7pc.resize(7); - std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0); - m_sum7pc = 0.0; +void Tool_synco::processStrand(HTp stok, HTp etok) { + HTp current = stok; + while (current && (current != etok)) { + if (!current->isData()) { + current = current->getNextToken(); + continue; + } + if (current->isNull()) { + current = current->getNextToken(); + continue; + } + if (current->isRest()) { + current = current->getNextToken(); + continue; + } + if (current->isSecondaryTiedNote()) { + current = current->getNextToken(); + continue; + } + if (isSyncopated(current)) { + m_hasSyncoQ = true; + m_scount++; + markNote(current); + } + current = current->getNextToken(); + } } ////////////////////////////// // -// MeasureData::getHistogram7pc -- +// Tool_synco::isSyncopated -- // -std::vector& MeasureData::getHistogram7pc(void) { - return m_hist7pc; +bool Tool_synco::isSyncopated(HTp token) { + double metlev = getMetricLevel(token); + HumNum duration = token->getTiedDuration(); + double logDur = log2(duration.getFloat()); + if (metlev == 2) { + return false; + } + if (logDur > metlev) { + return true; + } else { + return false; + } } + ////////////////////////////// // -// MeasureData::getSum7pc -- +// Tool_synco::getMetricLevel -- Assuming whole-note beats for now. // -double MeasureData::getSum7pc(void) { - return m_sum7pc; +double Tool_synco::getMetricLevel(HTp token) { + HumNum durbar = token->getDurationFromBarline(); + if (!durbar.isInteger()) { + return -1.0; + } + if (durbar.getNumerator() % 4 == 0) { + return 2.0; + } + if (durbar.getNumerator() % 2 == 0) { + return 1.0; + } + return 0.0; } ////////////////////////////// // -// MeasureData::generateNoteHistogram -- +// Tool_synco::markNote -- Currently ignoring chords. // -void MeasureData::generateNoteHistogram(void) { - m_hist7pc.resize(7); - std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0); - m_sum7pc = 0; - if (m_owner == NULL) { - return; - } - if (m_startline < 0) { - return; - } - if (m_stopline < 0) { - return; - } - - HumdrumFile& infile = *m_owner; - for (int i=m_startline; iisKern()) { +void Tool_synco::markNote(HTp token) { + token->setText(token->getText() + "|"); + if ((token->find('[') != string::npos) || (token->find('_') != string::npos)) { + HTp current = token->getNextToken(); + while (current) { + if (!current->isData()) { + current = current->getNextToken(); continue; } - if (token->isNull()) { + if (current->isNull()) { + current = current->getNextToken(); continue; } - if (token->isRest()) { - continue; + if (current->isRest()) { + break; } - double duration = token->getDuration().getFloat(); - int subtokcount = token->getSubtokenCount(); - for (int k=0; kgetSubtoken(k); - int pc = Convert::kernToBase7PC(subtok); - if (pc < 0) { - continue; - } - m_hist7pc.at(pc) += duration; + if (current->find("_") != string::npos) { + current->setText(current->getText() + "|"); + } else if (current->find("]") != string::npos) { + current->setText(current->getText() + "|"); + break; } + current = current->getNextToken(); } } - m_sum7pc = 0.0; - for (int i=0; i<(int)m_hist7pc.size(); i++) { - m_sum7pc += m_hist7pc[i]; - } } -/////////////////////////////////////////////////////////////////////////// -////////////////////////////// + +///////////////////////////////// // -// MeasureDataSet::MeasureDataSet -- +// Tool_gridtest::Tool_tabber -- Set the recognized options for the tool. // -MeasureDataSet::MeasureDataSet(void) { - m_data.reserve(1000); +Tool_tabber::Tool_tabber(void) { + // do nothing for now. + define("r|remove=b", "remove any extra tabs"); } -////////////////////////////// +/////////////////////////////// // -// MeasureDataSet::MeasureDataSet -- +// Tool_tabber::run -- Primary interfaces to the tool. // -MeasureDataSet::MeasureDataSet(HumdrumFile& infile) { - parse(infile); +bool Tool_tabber::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; igenerateNoteHistogram(); - m_data.push_back(info); - lastbar = i; - } - MeasureData* info = new MeasureData(infile, lastbar, infile.getLineCount() - 1); - m_data.push_back(info); - return 1; +void Tool_tabber::initialize(HumdrumFile& infile) { + // do nothing for now } ////////////////////////////// // -// MeasureDataSet::operator[] -- +// Tool_tabber::processFile -- // -MeasureData& MeasureDataSet::operator[](int index) { - return *m_data[index]; +void Tool_tabber::processFile(HumdrumFile& infile) { + if (getBoolean("remove")) { + infile.removeExtraTabs(); + } else { + infile.addExtraTabs(); + } + infile.createLinesFromTokens(); } -////////////////////////////// + +///////////////////////////////// // -// MeasureDataSet::getScoreDuration -- +// Tool_tandeminfo::Tool_tandeminfo -- Set the recognized options for the tool. // -double MeasureDataSet::getScoreDuration(void) { - if (m_data.empty()) { - return 0.0; - } - return m_data[0]->getScoreDuration(); +Tool_tandeminfo::Tool_tandeminfo(void) { -} + define("c|count=b", "show only unique list of interpretations with counts"); + define("D|no-description|M|no-meaning=b", "do not include descriptions of tandem interpretations in output"); + define("f|filename=b", "show filename"); + define("h|header-only=b", "only process interpretations before first data line"); + define("H|body-only=b", "only process interpretations after first data line"); + define("l|location=b", "show location of interpretation in file (row, column)"); + define("n|sort-by-count=b", "sort entries by unique counts from low to high (when -c is used)"); + define("N|sort-by-reverse-count=b", "sort entries by unique counts from high to low (when -c is used)"); + define("s|sort=b", "sort entries alphabetically by tandem interpretation"); + define("t|table=b", "embed analysis withing input data"); + define("u|unknown-tandem-interpretations-only=b", "only list unknown interpretations"); + define("x|exclusive-interpretations=b", "show exclusive interpretation context"); + define("z|zero-indexed-locations=b", "locations are 0-indexed"); + define("close=b", "close
    by default in HTML output"); + define("humdrum|hmd=b", "textual output formatted with Humdrum syntax"); + m_entries.reserve(1000); +} -/////////////////////////////////////////////////////////////////////////// -////////////////////////////// + +///////////////////////////////// // -// MeasureComparison::MeasureComparison -- +// Tool_tandeminfo::run -- Do the main work of the tool. // -MeasureComparison::MeasureComparison() { - // do nothing +bool Tool_tandeminfo::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; igetSum7pc(); - double sum2 = data2->getSum7pc(); - if ((sum1 == sum2) && (sum1 == 0.0)) { - correlation7pc = 1.0; - return; - } - if (sum1 == 0.0) { - correlation7pc = 0.0; - return; - } - if (sum2 == 0.0) { - correlation7pc = 0.0; - return; - } - correlation7pc = Convert::pearsonCorrelation(data1->getHistogram7pc(), data2->getHistogram7pc()); - if (fabs(correlation7pc - 1.0) < 0.00000001) { - correlation7pc = 1.0; + bool foundDataQ = false; + for (int i=0; igetText(); + string bb = b.token->getText(); + std::transform(aa.begin(), aa.end(), aa.begin(), ::tolower); + std::transform(bb.begin(), bb.end(), bb.begin(), ::tolower); + return (aa < bb); + }); + } + if (m_countQ) { + doCountAnalysis(); + } -////////////////////////////// -// -// MeasureComparisonGrid::MeasureComparisonGrid -- -// + if (m_sortByCountQ) { -MeasureComparisonGrid::MeasureComparisonGrid(void) { - // do nothing -} + sort(m_entries.begin(), m_entries.end(), [](const Entry &a, const Entry &b) { + int anum = a.count; + int bnum = b.count; + if (anum != bnum) { + return anum < bnum; + } + string aa = a.token->getText(); + string bb = b.token->getText(); + std::transform(aa.begin(), aa.end(), aa.begin(), ::tolower); + std::transform(bb.begin(), bb.end(), bb.begin(), ::tolower); + return (aa < bb); + }); + } else if (m_sortByReverseCountQ) { -MeasureComparisonGrid::MeasureComparisonGrid(MeasureDataSet& set1, MeasureDataSet& set2) { - analyze(set1, set2); -} + sort(m_entries.begin(), m_entries.end(), [](const Entry &a, const Entry &b) { + int anum = a.count; + int bnum = b.count; + if (anum != bnum) { + return anum > bnum; + } + string aa = a.token->getText(); + string bb = b.token->getText(); + std::transform(aa.begin(), aa.end(), aa.begin(), ::tolower); + std::transform(bb.begin(), bb.end(), bb.begin(), ::tolower); + return (aa < bb); + }); + } -MeasureComparisonGrid::MeasureComparisonGrid(MeasureDataSet* set1, MeasureDataSet* set2) { - analyze(set1, set2); + if (m_tableQ) { + printEntriesHtml(infile); + } else { + printEntriesText(infile); + } } ////////////////////////////// // -// MeasureComparisonGrid::~MeasureComparisonGrid -- +// Tool_tandeminfo::doCountAnalysis -- // -MeasureComparisonGrid::~MeasureComparisonGrid() { - // do nothing +void Tool_tandeminfo::doCountAnalysis(void) { + m_count.clear(); + for (int i=0; i<(int)m_entries.size(); i++) { + m_count[m_entries[i].token->getText()] += 1; + } + + // store counts in entries: + for (int i=0; i<(int)m_entries.size(); i++) { + m_entries[i].count = m_count[m_entries[i].token->getText()]; + } } ////////////////////////////// // -// MeasureComparisonGrid::clear -- +// Tool_tandeminfo::printEntiesHtml -- Print as embedded HTML code at end of +// input score. // -void MeasureComparisonGrid::clear(void) { - m_grid.clear(); -} +void Tool_tandeminfo::printEntriesHtml(HumdrumFile& infile) { + map processed; // used for -c option + m_humdrum_text << infile; + m_humdrum_text << "!!@@BEGIN: PREHTML" << endl; -////////////////////////////// -// -// MeasureComparisonGrid::analyze -- -// + m_humdrum_text << "!!@SCRIPT:" << endl; + m_humdrum_text << "!!function gotoEditorCoordinate(row, col) {" << endl; + m_humdrum_text << "!! if ((typeof EDITOR == 'undefined') || !EDITOR) {" << endl; + m_humdrum_text << "!! return;" << endl; + m_humdrum_text << "!! }" << endl; + m_humdrum_text << "!! gotoLineFieldInEditor(row, col);" << endl; + m_humdrum_text << "!!}" << endl; + m_humdrum_text << "!!@CONTENT:" << endl; + + m_humdrum_text << "!!" << endl; + + m_humdrum_text << "!!
    " << endl;; + m_humdrum_text << "!!Tandem interpretation information" << endl;; + if (!m_entries.empty()) { + m_humdrum_text << "!!" << endl; + + // print table header + m_humdrum_text << "!!" << endl; + if (m_locationQ) { + m_humdrum_text << "!!" << endl; + } else if (m_countQ) { + m_humdrum_text << "!!" << endl; + } + if (m_exclusiveQ) { + m_humdrum_text << "!!" << endl; + } + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << endl; + + // print table entries + for (int i=0; i<(int)m_entries.size(); i++) { + HTp token = m_entries[i].token; + if (m_countQ && processed[token->getText()]) { + continue; + } + processed[token->getText()] = true; + m_humdrum_text << "!!" << endl; + + if (m_locationQ) { + m_humdrum_text << "!!" << endl; + } else if (m_countQ) { + m_humdrum_text << "!!" << endl; + } + + if (m_exclusiveQ) { + m_humdrum_text << "!!" << endl; + } + + m_humdrum_text << "!!" << endl; -void MeasureComparisonGrid::analyze(MeasureDataSet* set1, MeasureDataSet* set2) { - analyze(*set1, *set2); -} + HumRegex hre; + m_humdrum_text << "!!" << endl; -void MeasureComparisonGrid::analyze(MeasureDataSet& set1, MeasureDataSet& set2) { - m_grid.resize(set1.size()); - for (int i=0; i<(int)m_grid.size(); i++) { - m_grid[i].resize(set2.size()); + m_humdrum_text << "!!" << endl; + } + + m_humdrum_text << "!!
    LocationCountExclusiveTandemDescription
    " << endl; + m_humdrum_text << "!!(" << token->getLineNumber() << ", " << token->getFieldNumber() << ")" << endl; + m_humdrum_text << "!!"; + m_humdrum_text << m_count[token->getText()]; + m_humdrum_text << "" << endl; + m_humdrum_text << "!!" << token->getDataType() << endl; + m_humdrum_text << "!!" << endl; + m_humdrum_text << "!!" << m_entries[i].token << endl; + m_humdrum_text << "!!" << endl; + string description = m_entries[i].description; + // hre.replaceDestructive(description, "<", "<", "g"); + // hre.replaceDestructive(description, ">", ">", "g"); + hre.replaceDestructive(description, "unknown", "unknown"); + hre.replaceDestructive(description, "non-standard", "non-standard"); + m_humdrum_text << "!!" << description << endl; + m_humdrum_text << "!!
    " << endl; } - for (int i=0; i<(int)m_grid.size(); i++) { - for (int j=0; j<(int)m_grid[i].size(); j++) { - m_grid[i][j].compare(set1[i], set2[j]); + + // print relevant settings + vector settings; + if (m_headerOnlyQ) { + settings.push_back("Only processing header interpretations"); + } + if (m_bodyOnlyQ) { + settings.push_back("Only processing body interpretations"); + } + if (m_unknownQ) { + settings.push_back("Only processing unknown interpretations"); + } + if (m_entries.empty()) { + settings.push_back("No interpretations found"); + } + if (!m_entries.empty()) { + if ((!m_countQ) && m_sortQ && !m_entries.empty()) { + settings.push_back("List sorted alphabetically by interpretation"); + } else if (m_countQ && m_sortByCountQ) { + settings.push_back("List sorted low to high by count"); + } else if (m_countQ && m_sortByReverseCountQ) { + settings.push_back("List sorted high to low by count"); } } - m_set1 = &set1; - m_set2 = &set2; + if (!settings.empty()) { + m_humdrum_text << "!!
      " << endl; + for (int i=0; i<(int)settings.size(); i++) { + m_humdrum_text << "!!
    • "; + m_humdrum_text << settings[i]; + m_humdrum_text << "
    • " << endl; + } + m_humdrum_text << "!!
    " << endl; + } + + m_humdrum_text << "!!
    " << endl; + m_humdrum_text << "!!@@END: PREHTML" << endl; } ////////////////////////////// // -// MeasureComparisonGrid::printCorrelationGrid -- -// default value: out = std::cout +// Tool_tandeminfo::printEntiesText -- // -ostream& MeasureComparisonGrid::printCorrelationGrid(ostream& out) { - for (int i=0; i<(int)m_grid.size(); i++) { - for (int j=0; j<(int)m_grid[i].size(); j++) { - double correl = m_grid[i][j].getCorrelation7pc(); - if (correl > 0.0) { - out << int(correl * 100.0 + 0.5)/100.0; - } else { - out << -int(-correl * 100.0 + 0.5)/100.0; - } - if (j < (int)m_grid[i].size() - 1) { - out << '\t'; - } +void Tool_tandeminfo::printEntriesText(HumdrumFile& infile) { + map processed; // used for -c option + + if (m_humdrumQ) { + if (m_locationQ) { + m_free_text << "**loc" << "\t"; + } else if (m_countQ) { + m_free_text << "**count" << "\t"; } - out << endl; + if (m_filenameQ) { + m_free_text << "**file" << "\t"; + } + if (m_exclusiveQ) { + m_free_text << "**exinterp" << "\t"; + } + m_free_text << "**tandem"; + if (m_descriptionQ) { + m_free_text << "\t" << "**info"; + } + m_free_text << endl; } - return out; -} - + for (int i=0; i<(int)m_entries.size(); i++) { + HTp token = m_entries[i].token; + if (m_countQ && processed[token->getText()]) { + continue; + } + processed[token->getText()] = true; -////////////////////////////// -// -// MeasureComparisonGrid::printCorrelationDiagonal -- Assuming a square grid for now. -// default value: out = std::cout -// - -ostream& MeasureComparisonGrid::printCorrelationDiagonal(ostream& out) { - for (int i=0; i<(int)m_grid.size(); i++) { - for (int j=0; j<(int)m_grid[i].size(); j++) { - if (i != j) { - continue; - } - double correl = m_grid[i][j].getCorrelation7pc(); - if (correl > 0.0) { - out << int(correl * 100.0 + 0.5)/100.0; + string description = m_entries[i].description; + HumRegex hre; + hre.replaceDestructive(description, "", "", "g"); + if (m_filenameQ) { + m_free_text << infile.getFilename() << "\t"; + } + if (m_locationQ) { + if (m_zeroQ) { + int row = token->getLineIndex(); + int col = token->getFieldIndex(); + m_free_text << "(" << row << ", " << col << ")" << "\t"; } else { - out << -int(-correl * 100.0 + 0.5)/100.0; + int row = token->getLineNumber(); + int col = token->getFieldNumber(); + m_free_text << "(" << row << ", " << col << ")" << "\t"; } - if (j < (int)m_grid[i].size() - 1) { - out << '\t'; + } else if (m_countQ) { + m_free_text << m_count[token->getText()] << "\t"; + } + if (m_exclusiveQ) { + string exinterp = token->getDataType(); + if (m_humdrumQ) { + exinterp = exinterp.substr(2); } + m_free_text << exinterp << "\t"; } - out << endl; + if (m_humdrumQ) { + string text = token->getText(); + text = text.substr(1); + m_free_text << text; + } else { + m_free_text << token; + } + if (m_descriptionQ) { + m_free_text << "\t" << description; + } + m_free_text << endl; + } + + if (m_humdrumQ) { + if (m_locationQ) { + m_free_text << "*-" << "\t"; + } else if (m_countQ) { + m_free_text << "*-" << "\t"; + } + if (m_filenameQ) { + m_free_text << "*-" << "\t"; + } + if (m_exclusiveQ) { + m_free_text << "*-" << "\t"; + } + m_free_text << "*-"; + if (m_descriptionQ) { + m_free_text << "\t" << "*-"; + } + m_free_text << endl; } - return out; } ////////////////////////////// // -// MeasureComparisonGrid::getColorMapping -- +// Tool_tandeminfo::getDescription -- Return description of the input token; otherwise, return m_unknown. // -void MeasureComparisonGrid::getColorMapping(double input, double& hue, - double& saturation, double& lightness) { - double maxhue = 0.75 * 360.0; - hue = input; - if (hue < 0.0) { - hue = 0.0; +string Tool_tandeminfo::getDescription(HTp token) { + string tok = token->substr(1); + string description; + + description = checkForKeySignature(tok); + if (description != m_unknown) { + return description; } - hue = hue * hue; - if (hue != 1.0) { - hue *= 0.95; + + description = checkForKeyDesignation(tok); + if (description != m_unknown) { + return description; } - hue = (1.0 - hue) * 360.0; - if (hue == 0.0) { - // avoid -0.0; - hue = 0.0; + description = checkForInstrumentInfo(tok); + if (description != m_unknown) { + return description; } - if (hue > maxhue) { - hue = maxhue; + description = checkForLabelInfo(tok); + if (description != m_unknown) { + return description; } - if (hue < 0.0) { - hue = maxhue; + + description = checkForTimeSignature(tok); + if (description != m_unknown) { + return description; } - saturation = 100.0; - lightness = 50.0; + description = checkForMeter(tok); + if (description != m_unknown) { + return description; + } - if (hue > 60) { - lightness = lightness - (hue-60) / (maxhue-60) * lightness / 1.5; + description = checkForTempoMarking(tok); + if (description != m_unknown) { + return description; } -} + description = checkForClef(tok); + if (description != m_unknown) { + return description; + } + description = checkForStaffPartGroup(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getQoff1 -- return the end time class ID of the -// current grid cell (for the first piece being compared). -// + description = checkForTuplet(tok); + if (description != m_unknown) { + return description; + } -std::string MeasureComparisonGrid::getQoff1(int index) { - if (m_set1 == NULL) { - return ""; + description = checkForHands(tok); + if (description != m_unknown) { + return description; } - return (*m_set1)[index].getQoff(); -} + description = checkForPosition(tok); + if (description != m_unknown) { + return description; + } + description = checkForCue(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getQoff2 -- return the end time class ID of the -// current grid cell (for the first piece being compared). -// + description = checkForFlip(tok); + if (description != m_unknown) { + return description; + } -std::string MeasureComparisonGrid::getQoff2(int index) { - if (m_set2 == NULL) { - return ""; + description = checkForTremolo(tok); + if (description != m_unknown) { + return description; } - return (*m_set2)[index].getQoff(); -} + description = checkForOttava(tok); + if (description != m_unknown) { + return description; + } + description = checkForPedal(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getQon1 -- return the start time class ID of the -// current grid cell (for the first piece being compared). -// + description = checkForBracket(tok); + if (description != m_unknown) { + return description; + } -string MeasureComparisonGrid::getQon1(int index) { - if (m_set1 == NULL) { - return ""; + description = checkForRscale(tok); + if (description != m_unknown) { + return description; } - return (*m_set1)[index].getQon(); -} + description = checkForTimebase(tok); + if (description != m_unknown) { + return description; + } + description = checkForTransposition(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getQon2 -- return the start time class ID of the -// current grid cell (for the second piece being compared). -// + description = checkForGrp(tok); + if (description != m_unknown) { + return description; + } -string MeasureComparisonGrid::getQon2(int index) { - if (m_set2 == NULL) { - return ""; + description = checkForStria(tok); + if (description != m_unknown) { + return description; + } + + description = checkForFont(tok); + if (description != m_unknown) { + return description; } - return (*m_set2)[index].getQon(); -} + description = checkForVerseLabels(tok); + if (description != m_unknown) { + return description; + } + description = checkForLanguage(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getMeasure1 -- return the measure of the -// current grid cell (for the first piece being compared). -// + description = checkForStemInfo(tok); + if (description != m_unknown) { + return description; + } -int MeasureComparisonGrid::getMeasure1(int index) { - if (m_set1 == NULL) { - return 0.0; + description = checkForXywh(tok); + if (description != m_unknown) { + return description; } - return (*m_set1)[index].getMeasure(); -} + description = checkForCustos(tok); + if (description != m_unknown) { + return description; + } + description = checkForTextInterps(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getMeasure2 -- return the measure of the -// current grid cell (for the second piece being compared). -// + description = checkForRep(tok); + if (description != m_unknown) { + return description; + } -int MeasureComparisonGrid::getMeasure2(int index) { - if (m_set2 == NULL) { - return 0.0; + description = checkForPline(tok); + if (description != m_unknown) { + return description; } - return (*m_set2)[index].getMeasure(); -} + description = checkForTacet(tok); + if (description != m_unknown) { + return description; + } + description = checkForFb(tok); + if (description != m_unknown) { + return description; + } -////////////////////////////// -// -// MeasureComparisonGrid::getStartTime1 -- return the start time of the -// measure at index position in the first compared score. -// + description = checkForColor(tok); + if (description != m_unknown) { + return description; + } -double MeasureComparisonGrid::getStartTime1(int index) { - if (m_set1 == NULL) { - return 0.0; + description = checkForThru(tok); + if (description != m_unknown) { + return description; + } + + HumRegex hre; + if (hre.search(token, "\\s+$")) { + return "unknown (space at end of interpretation may be the problem)"; + } else { + return m_unknown; } - return (*m_set1)[index].getStartTime(); } ////////////////////////////// // -// MeasureComparisonGrid::getScoreDuration1 -- +// Tool_tandeminfo::checkForThru -- Humdrum Toolkit interpretations related to thru command. // -double MeasureComparisonGrid::getScoreDuration1(void) { - if (m_set1 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForThru(const string& tok) { + if (tok == "thru") { + return "data processed by thru command (expansion lists processed)"; } - return m_set1->getScoreDuration(); + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::getStartTime2 -- +// Tool_tandeminfo::checkForColor -- Extended interprerations for coloring notes in **kern data. +// Used in verovio. // -double MeasureComparisonGrid::getStartTime2(int index) { - if (m_set2 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForColor(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^color:(.*)")) { + string color = hre.getMatch(1); + string output; + if (hre.search(tok, "^#[0-9A-Fa-f]{3}$")) { + output = "3-digit hex "; + } else if (hre.search(tok, "^#[0-9A-Fa-f]{6}$")) { + output = "6-digit hex "; + } else if (hre.search(tok, "^#[0-9A-Fa-f]{8}$")) { + output = "8-digit hex (RGB + transparency)"; + } else if (hre.search(tok, "^rgb(\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+)$")) { + output = "RGB integer"; + } else if (hre.search(tok, "^rgb(\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+\\s*,[\\d.]+)$")) { + output = "RGB integer with alpha"; + } else if (hre.search(tok, "^hsl(\\d+\\s*,\\s*\\d+%\\s*,\\s*\\d+%)$")) { + output = "HSL"; + } else if (hre.search(tok, "^hsl(\\d+\\s*,\\s*\\d+%\\s*,\\s*\\d+%,\\s*[\\d.]+)$")) { + output = "HSL with alpha"; + } else if (hre.search(tok, "^[a-z]+$")) { + output = "named "; + } + output += " color"; + return output; } - return (*m_set2)[index].getStartTime(); + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::getStopTime1 -- +// Tool_tandeminfo::checkForFb -- Extended interprerations especially for **fb (**fa) exclusive +// interpretations. // -double MeasureComparisonGrid::getStopTime1(int index) { - if (m_set1 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForFb(const string& tok) { + if (tok == "reverse") { + return "reverse order of accidental and number in figured bass"; } - return (*m_set1)[index].getStopTime(); + if (tok == "Xreverse") { + return "stop reversing order of accidental and number in figured bass"; + } + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::getStopTime2 -- +// Tool_tandeminfo::checkForTacet -- Extended interprerations for marking parts that are not +// playing (rests only) in a movement/movement subsection. // -double MeasureComparisonGrid::getStopTime2(int index) { - if (m_set2 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForTacet(const string& tok) { + if (tok == "tacet") { + return "part is tacet in movement/section"; } - return (*m_set2)[index].getStopTime(); + if (tok == "Xtacet") { + return "end of part tacet"; + } + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::getDuration1 -- +// Tool_tandeminfo::checkForRep -- Extended interprerations for poetic line analysis related to pline tool. // -double MeasureComparisonGrid::getDuration1(int index) { - if (m_set1 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForPline(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^pline:(\\d+)([abcr]*)$")) { + string number = hre.getMatch(1); + string info = hre.getMatch(2); + string output = "poetic line markup: " + number + info; + return output; } - return (*m_set1)[index].getDuration(); + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::getDuration2 -- +// Tool_tandeminfo::checkForRep -- Extended interprerations for adding repeat sign shorthand for +// repeated music. // -double MeasureComparisonGrid::getDuration2(int index) { - if (m_set2 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForRep(const string& tok) { + if (tok == "rep") { + return "start of repeat sign replacing notes/rests"; } - return (*m_set2)[index].getDuration(); + if (tok == "Xrep") { + return "end of repeat sign replacing notes/rests"; + } + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::getScoreDuration2 -- +// Tool_tandeminfo::checkForTextInterps -- Extended interprerations for **text and **silbe // -double MeasureComparisonGrid::getScoreDuration2(void) { - if (m_set2 == NULL) { - return 0.0; +string Tool_tandeminfo::checkForTextInterps(const string& tok) { + if (tok == "ij") { + return "start of text repeat region"; } - return m_set2->getScoreDuration(); + if (tok == "Xij") { + return "end of text repeat region"; + } + if (tok == "edit") { + return "start of editorial text region"; + } + if (tok == "Xedit") { + return "end of editorial text region"; + } + + return m_unknown; } ////////////////////////////// // -// MeasureComparisonGrid::printSvgGrid -- -// default value: out = std::cout +// Tool_tandeminfo::checkForCustos -- Extended interprerations for marker +// at end of system for next note in part. // -ostream& MeasureComparisonGrid::printSvgGrid(ostream& out) { - pugi::xml_document image; - auto declaration = image.prepend_child(pugi::node_declaration); - declaration.append_attribute("version") = "1.0"; - declaration.append_attribute("encoding") = "UTF-8"; - declaration.append_attribute("standalone") = "no"; - - auto svgnode = image.append_child("svg"); - svgnode.append_attribute("version") = "1.1"; - svgnode.append_attribute("xmlns") = "http://www.w3.org/2000/svg"; - svgnode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink"; - svgnode.append_attribute("overflow") = "visible"; - svgnode.append_attribute("viewBox") = "0 0 1000 1000"; - svgnode.append_attribute("width") = "1000px"; - svgnode.append_attribute("height") = "1000px"; - - auto grid = svgnode.append_child("g"); - grid.append_attribute("id") = "grid"; - - double hue = 0.0; - double saturation = 100; - double lightness = 75; - - pugi::xml_node crect; - double width; - double height; - - stringstream ss; - stringstream css; - double x; - double y; - - double imagewidth = 1000.0; - double imageheight = 1000.0; - - double sdur1 = getScoreDuration1(); - double sdur2 = getScoreDuration2(); +string Tool_tandeminfo::checkForCustos(const string& tok) { + HumRegex hre; - for (int i=0; i<(int)m_grid.size(); i++) { - for (int j=0; j<(int)m_grid[i].size(); j++) { - width = getDuration2(j) / sdur2 * imagewidth; - height = getDuration1(i) / sdur1 * imageheight; + if (tok == "custos") { + return "custos, pitch unspecified"; + } - x = getStartTime2(j)/sdur2 * imageheight; - y = getStartTime1(i)/sdur1 * imagewidth; + if (tok == "custos:") { + return "custos, pitch unspecified"; + } - getColorMapping(m_grid[i][j].getCorrelation7pc(), hue, saturation, lightness); - ss << "hsl(" << hue << "," << saturation << "%," << lightness << "%)"; - crect = grid.append_child("rect"); - crect.append_attribute("x") = to_string(x).c_str(); - crect.append_attribute("y") = to_string(y).c_str(); - crect.append_attribute("width") = to_string(width*0.99).c_str(); - crect.append_attribute("height") = to_string(height*0.99).c_str(); - crect.append_attribute("fill") = ss.str().c_str(); - css << "Xm" << getMeasure1(i) << " Ym" << getMeasure2(j); - css << " X" << getQon1(i) << " Y" << getQon2(j); - css << " X" << getQoff1(i) << " Y" << getQoff2(j); - crect.append_attribute("class") = css.str().c_str(); - ss.str(""); - css.str(""); - } + if (hre.search(tok, "^custos:([A-G]+|[a-g]+)(#+|-+|n)?$")) { + // also deal with chord custos + string pitch = hre.getMatch(1); + string accid = hre.getMatch(2); + string output = "custos on pitch " + pitch + accid; + return output; } - image.save(out); - return out; + return m_unknown; } -/////////////////////////////////////////////////////////////////////////// - -///////////////////////////////// +////////////////////////////// // -// Tool_simat::Tool_simat -- Set the recognized options for the tool. +// Tool_tandeminfo::checkForStemInfo -- Extended interprerations +// for visual display of stems (on left or right side of notes). // -Tool_simat::Tool_simat(void) { - define("r|raw=b", "output raw correlation matrix"); - define("d|diagonal=b", "output diagonal of correlation matrix"); +string Tool_tandeminfo::checkForXywh(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^xywh-([^:\\s]+):(\\d+),(\\d+),(\\d+),(\\d+)$")) { + string page = hre.getMatch(1); + string x = hre.getMatch(2); + string y = hre.getMatch(3); + string w = hre.getMatch(4); + string h = hre.getMatch(5); + string output = "IIIF bounding box, page="; + output += page; + output += ", x=" + x; + output += ", y=" + y; + output += ", w=" + w; + output += ", h=" + h; + return output; + } + + return m_unknown; } -///////////////////////////////// +////////////////////////////// // -// Tool_simat::run -- Primary interfaces to the tool. +// Tool_tandeminfo::checkForStemInfo -- Extended interprerations +// for visual display of stems (on left or right side of notes). // -bool Tool_simat::run(HumdrumFileSet& infiles) { - bool status = true; - if (infiles.getCount() == 1) { - status = run(infiles[0], infiles[0]); - } else if (infiles.getCount() > 1) { - status = run(infiles[0], infiles[1]); - } else { - status = false; - } - return status; -} - +string Tool_tandeminfo::checkForStemInfo(const string& tok) { + HumRegex hre; -bool Tool_simat::run(const string& indata1, const string& indata2, ostream& out) { - HumdrumFile infile1(indata1); - HumdrumFile infile2; - bool status; - if (indata2.empty()) { - infile2.read(indata2); - status = run(infile1, infile2); - } else { - status = run(infile1, infile1); - } - if (hasAnyText()) { - getAllText(out); - } else { - out << infile1; - out << infile2; + if (hre.search(tok, "^(\\d+)/left$")) { + string rhythm = hre.getMatch(1); + string output = rhythm + "-rhythm notes always have stem up on the left"; + return output; } - return status; -} - -bool Tool_simat::run(HumdrumFile& infile1, HumdrumFile& infile2, ostream& out) { - bool status; - if (infile2.getLineCount() == 0) { - status = run(infile1, infile1); - } else { - status = run(infile1, infile2); - } - if (hasAnyText()) { - getAllText(out); - } else { - out << infile1; - out << infile2; + if (hre.search(tok, "^(\\d+)\\\\left$")) { + string rhythm = hre.getMatch(1); + string output = rhythm + "-rhythm notes always have stem down on the left"; + return output; } - return status; -} - -// -// In-place processing of file: -// -bool Tool_simat::run(HumdrumFile& infile1, HumdrumFile& infile2) { - if (infile2.getLineCount() == 0) { - processFile(infile1, infile1); - } else { - processFile(infile1, infile2); + if (hre.search(tok, "^(\\d+)/right$")) { + string rhythm = hre.getMatch(1); + string output = rhythm + "-rhythm notes always have stem up on the right"; + return output; } - return true; -} - - - -////////////////////////////// -// -// Tool_simat::processFile -- -// + if (hre.search(tok, "^(\\d+)\\\\right$")) { + string rhythm = hre.getMatch(1); + string output = rhythm + "-rhythm notes always have stem down on the right"; + return output; + } -void Tool_simat::processFile(HumdrumFile& infile1, HumdrumFile& infile2) { - m_data1.parse(infile1); - m_data2.parse(infile2); - m_grid.analyze(m_data1, m_data2); - if (getBoolean("raw")) { - m_grid.printCorrelationGrid(m_free_text); - suppressHumdrumFileOutput(); - } else if (getBoolean("diagonal")) { - m_grid.printCorrelationDiagonal(m_free_text); - suppressHumdrumFileOutput(); - } else { - m_grid.printSvgGrid(m_free_text); - suppressHumdrumFileOutput(); + if (tok == "all/right") { + string output = "all notes always have stem up on the right"; + return output; } -} + if (tok == "all\\right") { + string output = "all notes always have stem down on the right"; + return output; + } + if (tok == "all/left") { + string output = "all notes always have stem up on the left"; + return output; + } + if (tok == "all\\left") { + string output = "all notes always have stem down on the left"; + return output; + } + if (tok == "all/center") { + string output = "all notes always have stem up on notehead center"; + return output; + } -///////////////////////////////// -// -// Tool_slurcheck::Tool_slurcheck -- Set the recognized options for the tool. -// + if (tok == "all\\center") { + string output = "all notes always have stem down on notehead center"; + return output; + } + // there is also "middle" which is the same as "center"; -Tool_slurcheck::Tool_slurcheck(void) { - // add options here - define("l|list=b", "list locations of unclosed slur endings"); - define("c|count=b", "count unclosed slur endings"); - define("Z|no-zeros=b", "do not list files that have zero unclosed slurs in counts"); - define("f|filename=b", "print filename for list and count options"); + return m_unknown; } -///////////////////////////////// +////////////////////////////// // -// Tool_slurcheck::run -- Do the main work of the tool. +// Tool_tandeminfo::checkForLanguage -- Humdrum Toolkit and extended interprerations +// for langauages (for **text and **silbe). // -bool Tool_slurcheck::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i=unknown"; + return output; + } + string output = "language code"; + if (code.size() == 2) { + output = "ISO 639-3 two-letter language code: "; + } else if (code.size() == 3) { + output = "ISO 639-3 three-letter language code: "; + } + output += ""; + output += code; + output += "=\""; + output += name; + output += "\""; + return output; } - return status; -} - -bool Tool_slurcheck::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - infile.createLinesFromTokens(); - return true; + return m_unknown; } ////////////////////////////// // -// Tool_slurcheck::initialize -- +// Tool_tandeminfo::checkForVerseLabels -- Extended tandem interpretations (used by verovio +// for visual rendeing of notation). // -void Tool_slurcheck::initialize(void) { +string Tool_tandeminfo::checkForVerseLabels(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^v:(.*)$")) { + string output = "verse label \"" + hre.getMatch(1) + "\""; + return output; + } + if (hre.search(tok, "^vv:(.*)$")) { + string output = "verse label \"" + hre.getMatch(1) + "\", repeated after each system break"; + return output; + } + + return m_unknown; } ////////////////////////////// // -// Tool_slurcheck::processFile -- +// Tool_tandeminfo::checkForFont -- Extended interprtations for styling **text and **silbe. // -void Tool_slurcheck::processFile(HumdrumFile& infile) { - infile.analyzeSlurs(); - int opencount = 0; - int closecount = 0; - int listQ = getBoolean("list"); - int countQ = getBoolean("count"); - int zeroQ = !getBoolean("no-zeros"); - int filenameQ = getBoolean("filename"); - if (listQ || countQ) { - suppressHumdrumFileOutput(); - } - for (int i=0; iisKern()) { - continue; - } - HTp etok = infile.getStrandEnd(i); - HTp tok = stok; - while (tok && (tok != etok)) { - if (!tok->isData()) { - tok = tok->getNextToken(); - continue; - } - if (tok->isNull()) { - tok = tok->getNextToken(); - continue; - } - string value = tok->getValue("auto", "hangingSlur"); - if (value == "true") { - string side = tok->getValue("auto", "slurSide"); - if (side == "start") { - opencount++; - if (listQ) { - if (filenameQ) { - m_free_text << infile.getFilename() << ":\t"; - } - m_free_text << "UNCLOSED SLUR\tline:" << tok->getLineIndex()+1 - << "\tfield:" << tok->getFieldIndex()+1 << "\ttoken:" << tok << endl; - } else if (!countQ) { - string data = *tok; - data += "i"; - tok->setText(data); - } - } else if (side == "stop") { - closecount++; - if (listQ) { - if (filenameQ) { - m_free_text << infile.getFilename() << ":\t"; - } - m_free_text << "UNOPENED SLUR\tline:" << tok->getLineIndex()+1 - << "\tfield:" << tok->getFieldIndex()+1 << "\ttoken:" << tok << endl; - } else if (!countQ) { - string data = *tok; - data += "j"; - tok->setText(data); - } - } - } - tok = tok->getNextToken(); - } - } - - if (countQ) { - int sum = opencount + closecount; - if ((!zeroQ) && (sum == 0)) { - return; - } - if (filenameQ) { - m_free_text << infile.getFilename() << ":\t"; - } - m_free_text << (opencount + closecount) << "\t(:" << opencount << "\t):" << closecount << endl; - } - - if (countQ || listQ) { - return; +string Tool_tandeminfo::checkForFont(const string& tok) { + if (tok == "italic") { + return "use italic font style"; } - - if (opencount + closecount == 0) { - return; + if (tok == "Xitalic") { + return "stop using italic font style"; } - - if (opencount) { - infile.appendLine("!!!RDF**kern: i = marked note, color=\"hotpink\", text=\"extra(\""); + if (tok == "bold") { + return "use bold font style"; } - - if (closecount) { - infile.appendLine("!!!RDF**kern: j = marked note, color=\"magenta\", text=\"extra)\""); + if (tok == "Xbold") { + return "stop using bold font style"; } - infile.createLinesFromTokens(); + return m_unknown; } - - -///////////////////////////////// +////////////////////////////// // -// Tool_gridtest::Tool_spinetrace -- Set the recognized options for the tool. +// Tool_tandeminfo::checkForStria -- Humdrum Toolkit interpretation. // -Tool_spinetrace::Tool_spinetrace(void) { - define("a|append=b", "append analysis to input data lines"); - define("p|prepend=b", "prepend analysis to input data lines"); +string Tool_tandeminfo::checkForStria(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^stria(\\d+)$")) { + string output = "number of staff lines:" + hre.getMatch(1); + return output; + } + + return m_unknown; } -/////////////////////////////// +////////////////////////////// // -// Tool_spinetrace::run -- Primary interfaces to the tool. +// Tool_tandeminfo::checkForGrp -- Polyrhythm project interpretations for +// polyrhythm group assignments. Related to humlib composite tool. // -bool Tool_spinetrace::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; icompare(0, 2, "**") == 0) { - m_humdrum_text << "**spine"; - } else { - m_humdrum_text << token; - } - if (j < fieldcount - 1) { - m_humdrum_text << "\t"; - } - } - } else { - m_humdrum_text << infile[i]; - } - } else { - int fieldcount = infile[i].getFieldCount(); - for (int j=0; jgetSpineInfo(); - if (j < fieldcount - 1) { - m_humdrum_text << '\t'; - } - } - } - - if (prependQ) { - m_humdrum_text << "\t" << infile[i]; - } - m_humdrum_text << "\n"; +string Tool_tandeminfo::checkForTimebase(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^tb(\\d+)$")) { + string number = hre.getMatch(1); + string output = "timebase: all data lines (should) have a duration of " + number; + return output; } -} + return m_unknown; +} -///////////////////////////////// +////////////////////////////// // -// Tool_strophe::Tool_strophe -- Set the recognized options for the tool. +// Tool_tandeminfo::checkForRscale -- Extended interpretation for adjusting the visual +// display of note durations when they do not match the logical +// note durations (such as show a quarter note as if it were a +// half note, which would be indicated by "*rscale:2". Or a +// half note as if it were a quarter note with "*rscale:1/2". +// Also related to the rscale tool from Humdrum Extras and humlib. +// Used in verovio. // -Tool_strophe::Tool_strophe(void) { - define("l|list=b", "list all possible variants"); - define("m=b", "mark strophe music"); - define("mark|marker=s:@", "character to mark with"); - define("c|color=s:red", "character to mark with"); +string Tool_tandeminfo::checkForRscale(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^rscale:(\\d+)(/\\d+)?$")) { + string fraction = hre.getMatch(1) + hre.getMatch(2); + string output = "visual rhythmic scaling factor " + fraction; + return output; + } + + return m_unknown; } -///////////////////////////////// +////////////////////////////// // -// Tool_strophe::run -- Do the main work of the tool. +// Tool_tandeminfo::checkForBracket -- Extended interpretations for displaying +// various bracket lines in visual music notation. // -bool Tool_strophe::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisData() && !current->isNull()) { - // Think about multiple marking for individual notes in chords. - string value = current->getText(); - value += m_marker; - current->setText(value); - output++; - } - current = current->getNextToken(); +string Tool_tandeminfo::checkForFlip(const string& tok) { + if (tok == "flip") { + return "switch order of subspines, specific to flipper tool"; } - return output; + if (tok == "Xflip") { + return "cancel flipping of subspine, specific to flipper tool"; + } + return m_unknown; } ////////////////////////////// // -// displayStropheVariants -- +// Tool_tandeminfo::checkForCue -- Extended interpretations for visual rendering +// *cue means display as cue-sized notes. Probably change +// this so that *cue means following notes are cue notes +// and add *cuesz for cue-sized notes (that are not cues +// from other instruments). // -void Tool_strophe::displayStropheVariants(HumdrumFile& infile) { - for (int i=0; icompare(0, 3, "*S/") != 0) { - continue; - } - string variant = token->substr(3); - m_variants.insert(variant); - } +string Tool_tandeminfo::checkForCue(const string& tok) { + if (tok == "cue") { + return "cue-sized notation follows"; } + if (tok == "Xcue") { + return "cancel cue-sized notation"; + } + return m_unknown; } +////////////////////////////// +// +// Tool_tandeminfo::checkForPosition -- Extended interpretations for visual rendering +// data above/below staff. Useful in particular for **dynam. +// Staff number in part (relative to top staff) can be given +// as a number following a colon after the placement. +// +string Tool_tandeminfo::checkForPosition(const string& tok) { + if (tok == "above") { + return "place items above staff"; + } + if (tok == "above:1") { + return "place items above first staff of part"; + } + if (tok == "above:2") { + return "place items above second staff of part"; + } + if (tok == "below") { + return "place items below staff"; + } + if (tok == "below:1") { + return "place items below first staff of part"; + } + if (tok == "below:2") { + return "place items below second staff of part"; + } + if (tok == "center") { + return "centered items between two staves"; + } + return m_unknown; +} -///////////////////////////////// +////////////////////////////// // -// Tool_synco::Tool_synco -- Set the recognized options for the tool. +// Tool_tandeminfo::checkForHands -- Extended interpretations to indicate which +// hand is playing the notes (for grand-staff keyboard in particular). // -Tool_synco::Tool_synco(void) { - define("c|color=s:skyblue", "SVG color to highlight syncopation notes"); - define("i|info=b", "display only statistics info"); - define("f|filename=b", "add filename to statistics info"); - define("a|all=b", "average all statistics info"); +string Tool_tandeminfo::checkForHands(const string& tok) { + if (tok == "LH") { + return "notes played by left hand"; + } + if (tok == "RH") { + return "notes played by right hand"; + } + return m_unknown; } -///////////////////////////////// +////////////////////////////// // -// Tool_synco::run -- Do the main work of the tool. +// Tool_tandeminfo::checkForTuplet -- Extended interpretations for **kern data to control +// visual stylings of tuplet numbers and brackets. // -bool Tool_synco::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; iisKern()) { - continue; +string Tool_tandeminfo::checkForTimeSignature(const string& tok) { + HumRegex hre; + if (tok == "MX") { + return "unmeasured music time signature"; + } + if (hre.search(tok, "^MX/(\\d+)(%\\d+)?(yy)?")) { + string output = "unmeasured music with beat " + hre.getMatch(1) + hre.getMatch(2); + if (hre.getMatch(3) == "yy") { + output += ", invisible"; + return output; } - HTp etok = infile.getStrandEnd(i); - processStrand(stok, etok); } + if (hre.search(tok, "^M(\\d+)/(\\d+)(%\\d+)?(yy)?$")) { + string top = hre.getMatch(1); + string bot = hre.getMatch(2) + hre.getMatch(3); + string invisible = hre.getMatch(4); + string output = "time signature: top="; + output += ""; + output += top; + output += ""; + output += ", bottom="; + output += ""; + output += bot; + output += ""; + if (bot == "3%2") { + output += " (triplet semibreve)"; + } else if (bot == "3%4") { + output += " (triplet breve)"; + } + if (invisible == "yy") { + output += ", invisible"; + } + return output; + } + + return m_unknown; } ////////////////////////////// // -// Tool_synco::processStrand -- +// Tool_tandeminfo::checkForMeter -- Humdrum Toolkit interpretations. Extended for use +// with mensural signs. // -void Tool_synco::processStrand(HTp stok, HTp etok) { - HTp current = stok; - while (current && (current != etok)) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->isRest()) { - current = current->getNextToken(); - continue; - } - if (current->isSecondaryTiedNote()) { - current = current->getNextToken(); - continue; - } - if (isSyncopated(current)) { - m_hasSyncoQ = true; - m_scount++; - markNote(current); +string Tool_tandeminfo::checkForMeter(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^(m|o)?met\\((.*?)\\)$")) { + string modori = hre.getMatch(1); + string meter = hre.getMatch(2); + if (meter == "c") { + return "meter: common time"; + } + if (meter == "c|") { + return "meter: cut time"; + } + if (meter == "") { + return "meter: empty"; + } + string output = "mensuration sign: "; + if (meter == "O") { + output += "circle"; + } else if (meter == "O|") { + output += "cut-circle"; + } else if (meter == "C") { + output += "c"; + } else if (meter == "C|") { + output += "cut-c"; + } else if (meter == "Cr") { + output += "reverse-c"; + } else if (meter == "C.") { + output += "c-dot"; + } else if (meter == "O.") { + output += "circle-dot"; + } else { + output += meter; } - current = current->getNextToken(); + return output; } + + return m_unknown; } ////////////////////////////// // -// Tool_synco::isSyncopated -- +// Tool_tandeminfo::checkForTempoMarking -- Humdrum Toolit interpretations. // -bool Tool_synco::isSyncopated(HTp token) { - double metlev = getMetricLevel(token); - HumNum duration = token->getTiedDuration(); - double logDur = log2(duration.getFloat()); - if (metlev == 2) { - return false; +string Tool_tandeminfo::checkForTempoMarking(const string& tok) { + HumRegex hre; + if (hre.search(tok, "^MM(\\d+)(\\.\\d*)?$")) { + string tempo = hre.getMatch(1) + hre.getMatch(2); + string output = "tempo: " + tempo + " quarter notes per minute"; + return output; } - if (logDur > metlev) { - return true; - } else { - return false; + + if (hre.search(tok, "^MM\\[(.*?)\\]$")) { + string text = hre.getMatch(1); + string output = "text-based tempo: " + text; + return output; } + + return m_unknown; } ////////////////////////////// // -// Tool_synco::getMetricLevel -- Assuming whole-note beats for now. +// Tool_tandeminfo::checkForLabelInfo -- Humdrum Toolkit interpretations. +// Used by the thru command. // -double Tool_synco::getMetricLevel(HTp token) { - HumNum durbar = token->getDurationFromBarline(); - if (!durbar.isInteger()) { - return -1.0; +string Tool_tandeminfo::checkForLabelInfo(const string& tok) { + HumRegex hre; + if (!hre.search(tok, "^>")) { + return m_unknown; } - if (durbar.getNumerator() % 4 == 0) { - return 2.0; + + if (hre.search(tok, "^>(\\[.*\\]$)")) { + string list = hre.getMatch(1); + string output = "default expansion list: "; + output += ""; + output += list; + output += ""; + return output; } - if (durbar.getNumerator() % 2 == 0) { - return 1.0; + + if (hre.search(tok, "^>([^[\\[\\]]+)(\\[.*\\]$)")) { + string expansionName = hre.getMatch(1); + string list = hre.getMatch(2); + string output = "alternate expansion list: label="; + output += "" + expansionName + ""; + if (expansionName == "norep") { + output += " (meaning: no repeats, i.e., take only second endings)"; + } + output += ", expansion list: " + list; + return output; } - return 0.0; + + if (hre.search(tok, "^>([^\\[\\]]+)$")) { + string label = hre.getMatch(1); + string output = "expansion label: "; + output += ""; + output += label; + output += ""; + return output; + } + + return m_unknown; + } ////////////////////////////// // -// Tool_synco::markNote -- Currently ignoring chords. +// Tool_tandeminfo::checkForInstrumentInfo -- Humdrum Toolkit and extended interpretations. +// Humdrum Tookit: +// instrument group *IG +// instrument class *IC +// instrument code *I +// Extended: +// instrument name *I" +// instrument number *I# +// instrument abbreviation *I' +// +// modori tool extensions: +// *mI == modernized +// *oI == original // -void Tool_synco::markNote(HTp token) { - token->setText(token->getText() + "|"); - if ((token->find('[') != string::npos) || (token->find('_') != string::npos)) { - HTp current = token->getNextToken(); - while (current) { - if (!current->isData()) { - current = current->getNextToken(); - continue; - } - if (current->isNull()) { - current = current->getNextToken(); - continue; - } - if (current->isRest()) { - break; - } - if (current->find("_") != string::npos) { - current->setText(current->getText() + "|"); - } else if (current->find("]") != string::npos) { - current->setText(current->getText() + "|"); - break; - } - current = current->getNextToken(); +string Tool_tandeminfo::checkForInstrumentInfo(const string& tok) { + HumRegex hre; + + if (hre.search(tok, "^(m|o)?I\"(.*)$")) { + string modori = hre.getMatch(1); + string name = hre.getMatch(2); + string output = "text to display in fromt of staff on first system (usually instrument name): \""; + output += name; + output += "\""; + if (modori == "o") { + output += " (original)"; + } else if (modori == "m") { + output += " (modern)"; + } + if (hre.search(tok, "\\\\n")) { + output += ", \"\\n\" means a line break"; + } + return output; + } + + if (hre.search(tok, "^(m|o)?I'(.*)$")) { + string modori = hre.getMatch(1); + string abbr = hre.getMatch(2); + string output = "text to display in front of staff on secondary systems (usually instrument abbreviation): \""; + output += abbr; + output += "\""; + if (modori == "o") { + output += " (original)"; + } else if (modori == "m") { + output += " (modern)"; } + if (hre.search(tok, "\\\\n")) { + output += ", \"\\n\" means a line break"; + } + return output; + } + + + if (hre.search(tok, "^(m|o)?IC([^\\s]*)$")) { + string modori = hre.getMatch(1); + string iclass = hre.getMatch(2); + bool andy = false; + bool ory = false; + vector iclasses; + string tok2 = tok; + hre.replaceDestructive(tok2, "", "IC", "g"); + if (hre.search(tok2, "&")) { + hre.split(iclasses, tok2, "&+"); + andy = true; + } else if (hre.search(tok2, "\\|")) { + hre.split(iclasses, tok2, "\\++"); + ory = true; + } else { + iclasses.push_back(tok2); + } + string output; + if (modori == "o") { + output += "(original) "; + } else if (modori == "m") { + output += "(modern) "; + } + output += "instrument class"; + if (iclasses.size() != 1) { + output += "es"; + } + output += ":"; + for (int i=0; i<(int)iclasses.size(); i++) { + output += " "; + output += ""; + output += iclasses[i]; + output += ""; + HumInstrument inst; + inst.setHumdrum(iclasses[i]); + string name; + if (iclasses[i] == "bras") { + name = "brass"; + } else if (iclasses[i] == "idio") { + name = "percussion"; + } else if (iclasses[i] == "klav") { + name = "keyboards"; + } else if (iclasses[i] == "str") { + name = "strings"; + } else if (iclasses[i] == "vox") { + name = "voices"; + } else if (iclasses[i] == "ww") { + name = "woodwinds"; + } else if (iclasses[i] != "") { + name = "unknown"; + } + if (!name.empty()) { + output += "=\"" + name + "\""; + } + if (i < (int)iclasses.size() - 1) { + if (andy) { + output += " and "; + } else if (ory) { + output += " or "; + } + } + } + if (modori == "o") { + output += " (original)"; + } else if (modori == "m") { + output += " (modern)"; + } + return output; } -} + if (hre.search(tok, "^(m|o)?IG([^\\s]*)$")) { + string modori = hre.getMatch(1); + string group = hre.getMatch(2); + bool andy = false; + bool ory = false; + vector groups; + string tok2 = tok; + hre.replaceDestructive(tok2, "", "IG", "g"); + if (hre.search(tok2, "&")) { + hre.split(groups, tok2, "&+"); + andy = true; + } else if (hre.search(tok2, "\\|")) { + hre.split(groups, tok2, "\\++"); + ory = true; + } else { + groups.push_back(tok2); + } + string output; + if (modori == "o") { + output += "(original) "; + } else if (modori == "m") { + output += "(modern) "; + } + output += "instrument group"; + if (groups.size() != 1) { + output += "s"; + } + output += ":"; + for (int i=0; i<(int)groups.size(); i++) { + output += " "; + output += groups[i]; + HumInstrument inst; + inst.setHumdrum(groups[i]); + string name; + if (groups[i] == "acmp") { + name = "=accompaniment"; + } else if (groups[i] == "solo") { + name = "=solo"; + } else if (groups[i] == "cont") { + name = "=basso-continuo"; + } else if (groups[i] == "ripn") { + name = "=ripieno"; + } else if (groups[i] == "conc") { + name = "=concertino"; + } else if (groups[i] != "") { + name = "=unknown"; + } + if (!name.empty()) { + output += "=\"" + name + "\""; + } + if (i < (int)groups.size() - 1) { + if (andy) { + output += " and "; + } else if (ory) { + output += " or "; + } + } + } + if (modori == "o") { + output += " (original)"; + } else if (modori == "m") { + output += " (modern)"; + } + return output; + } + if (hre.search(tok, "^(m|o)?I#(\\d+)$")) { + string modori = hre.getMatch(1); + string number = hre.getMatch(2); + string output = "sub-instrument number: "; + output += number; + if (modori == "o") { + output += " (original)"; + } else if (modori == "m") { + output += " (modern)"; + } + return output; + } + if (hre.search(tok, "^(m|o)?I([a-z][a-zA-Z0-9_|&-]+)$")) { + string modori = hre.getMatch(1); + string code = hre.getMatch(2); + bool andy = false; + bool ory = false; + vector codes; + string tok2 = tok; + hre.replaceDestructive(tok2, "", "I", "g"); + if (hre.search(tok2, "&")) { + hre.split(codes, tok2, "&+"); + andy = true; + } else if (hre.search(tok2, "\\|")) { + hre.split(codes, tok2, "\\++"); + ory = true; + } else { + codes.push_back(tok2); + } + string output; + if (modori == "o") { + output += "(original) "; + } else if (modori == "m") { + output += "(modern) "; + } + output += "instrument code"; + if (codes.size() != 1) { + output += "s"; + } + output += ":"; + for (int i=0; i<(int)codes.size(); i++) { + output += " "; + output += codes[i]; + output += ""; + HumInstrument inst; + inst.setHumdrum(codes[i]); + string text = inst.getName(); + if (!text.empty()) { + output += "= \"" + text + "\""; + } else { + output += "= unknown code"; + } + output += ""; + if (i < (int)codes.size() - 1) { + if (andy) { + output += " and "; + } else if (ory) { + output += " or "; + } + } + } -///////////////////////////////// -// -// Tool_gridtest::Tool_tabber -- Set the recognized options for the tool. -// + return output; + } -Tool_tabber::Tool_tabber(void) { - // do nothing for now. - define("r|remove=b", "remove any extra tabs"); + return m_unknown; } -/////////////////////////////// +////////////////////////////// // -// Tool_tabber::run -- Primary interfaces to the tool. +// Tool_tandeminfo::checkForKeySignature -- Standard Humdrum Toolkit interpretations. +// Extended key signatures are possible (and detected by this function), +// but typically the standard ones are in circle-of-fifths orderings. +// This function also allows double sharps/flats in the key signature +// which are very uncommon in real music. Standard key signatures: +// +// *k[f#c#g#d#a#e#b#] +// *k[f#c#g#d#a#e#] +// *k[f#c#g#d#a#] +// *k[f#c#g#d#] +// *k[f#c#g#] +// *k[f#c#] +// *k[f#] +// *k[] +// *k[b-] +// *k[b-e-] +// *k[b-e-a-] +// *k[b-e-a-d-] +// *k[b-e-a-d-g-] +// *k[b-e-a-d-g-c-] +// *k[b-e-a-d-g-c-f-] // -bool Tool_tabber::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i accidentals; + hre.split(accidentals, pcs, "[a-gA-G]"); + for (int i=0; i<(int)accidentals.size(); i++) { + if (accidentals[i] == "##") { + doublesharps++; + } else if (accidentals[i] == "--") { + doubleflats++; + } else if (accidentals[i] == "#") { + sharps++; + } else if (accidentals[i] == "-") { + flats++; + } else if (accidentals[i] == "n") { + naturals++; + } + } -bool Tool_tabber::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - out << m_free_text.str(); - return status; -} + bool foundQ = false; + if (sharps) { + if (foundQ) { + output += ", "; + } + foundQ = true; + if (sharps == 1) { + output += "1 sharp"; + } else { + output += to_string(sharps) + " sharps"; + } + } + if (flats) { + if (foundQ) { + output += ", "; + } + foundQ = true; + if (flats == 1) { + output += "1 flat"; + } else { + output += to_string(flats) + " flats"; + } + } -bool Tool_tabber::run(HumdrumFile& infile) { - initialize(infile); - processFile(infile); - return true; + if (naturals) { + if (foundQ) { + output += ", "; + } + foundQ = true; + if (naturals == 1) { + output += "1 natural"; + } else { + output += to_string(naturals) + " naturals"; + } + } + + if (doublesharps) { + if (foundQ) { + output += ", "; + } + foundQ = true; + if (doublesharps == 1) { + output += "1 double sharp"; + } else { + output += to_string(doublesharps) + " double sharps"; + } + } + + if (doubleflats) { + if (foundQ) { + output += ", "; + } + foundQ = true; + if (doubleflats == 1) { + output += "1 double flat"; + } else { + output += to_string(doubleflats) + " double flats"; + } + } + + return output; + } + return m_unknown; } ////////////////////////////// // -// Tool_tabber::initialize -- +// Tool_tandeminfo::checkForKeyDesignation -- Standard Humdrum Toolkit interpretations, plus +// modal extensions by Brett Arden. Typically only used in **kern data. // -void Tool_tabber::initialize(HumdrumFile& infile) { - // do nothing for now -} +string Tool_tandeminfo::checkForKeyDesignation(const string& tok) { + HumRegex hre; + if (tok == "?:") { + return "key designation, unknown/unassigned key"; + } + if (hre.search(tok, "^([a-gA-G])([-#]*):(ion|dor|phr|lyd|mix|aeo|loc)?(-hypo|-auth)?$")) { + string tonic = hre.getMatch(1); + string accid = hre.getMatch(2); + string mode = hre.getMatch(3); + string older = hre.getMatch(4); + bool isUpper = isupper(tonic[0]); + string output = "key designation: "; + if (mode.empty()) { + output += toupper(tonic[0]); + + if (accid == "") { + // do nothing + } else if (accid == "#") { + output += "-sharp"; + } else if (accid == "-") { + output += "-flat"; + } else if (accid == "##") { + output += "-double-sharp"; + } else if (accid == "--") { + output += "-double-flat"; + } else if (accid == "###") { + output += "-triple-sharp"; + } else if (accid == "---") { + output += "-triple-flat"; + } else { + return m_unknown; + } + if (isUpper) { + output += " major"; + } else { + output += " minor"; + } + return output; + } else { + // Modal key + if (isUpper && ((mode == "dor") || (mode == "phr") || (mode == "aeo") || (mode == "loc"))) { + // need a lower-case letter for these modes (minor third above tonic) + return m_unknown; + } + if ((!isUpper) && ((mode == "ion") || (mode == "lyd") || (mode == "mix"))) { + // need an upper-case letter for these modes (major third above tonic) + return m_unknown; + } + output += toupper(tonic[0]); + if (accid == "") { + // do nothing + } else if (accid == "#") { + output += "-sharp"; + } else if (accid == "-") { + output += "-flat"; + } else if (accid == "##") { + output += "-double-sharp"; + } else if (accid == "--") { + output += "-double-flat"; + } else if (accid == "###") { + output += "-triple-sharp"; + } else if (accid == "---") { + output += "-triple-flat"; + } else { + return m_unknown; + } + + if (mode == "ion") { + output += " ionian"; + } else if (mode == "dor") { + output += " dorian"; + } else if (mode == "phr") { + output += " phrygian"; + } else if (mode == "lyd") { + output += " lydian"; + } else if (mode == "mix") { + output += " mixolydian"; + } else if (mode == "aeo") { + output += " aeolian"; + } else if (mode == "loc") { + output += " locrian"; + } else { + return m_unknown; + } -////////////////////////////// -// -// Tool_tabber::processFile -- -// + if (!older.empty()) { + if (older == "-plag") { + output += " (plagal)"; + } else if (older == "-auth") { + output += " (authentic)"; + } else { + output += " (unknown mode type: should be -auth (authentic), or -plag (plagal) for hypo modes)"; + } + } -void Tool_tabber::processFile(HumdrumFile& infile) { - if (getBoolean("remove")) { - infile.removeExtraTabs(); - } else { - infile.addExtraTabs(); + return output; + } } - infile.createLinesFromTokens(); + + return m_unknown; } From d22b1aef4fbbe395225d38f462f01bbfe00f6820 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sat, 7 Sep 2024 08:33:17 -0700 Subject: [PATCH 371/383] Fix for issue https://github.com/rism-digital/verovio/issues/3766 --- src/iohumdrum.cpp | 332 ++++++++++++++++++++++++++++++---------------- 1 file changed, 216 insertions(+), 116 deletions(-) diff --git a/src/iohumdrum.cpp b/src/iohumdrum.cpp index 7eb039bc283..5feedb4b297 100644 --- a/src/iohumdrum.cpp +++ b/src/iohumdrum.cpp @@ -499,46 +499,46 @@ namespace humaux { ostream &StaffStateVariables::print(ostream &out, const std::string &prefix) { - out << prefix << "ADDRESS ================== " << (long long)this << endl; - out << prefix << "verse = " << verse << endl; - out << prefix << "suppress_tuplet_number = " << suppress_tuplet_number << endl; - out << prefix << "suppress_tuplet_bracket = " << suppress_tuplet_bracket << endl; - out << prefix << "suppress_articulations = " << suppress_articulations << endl; - out << prefix << "tremolo = " << tremolo << endl; + out << prefix << "ADDRESS ================== " << (long long)this << std::endl; + out << prefix << "verse = " << verse << std::endl; + out << prefix << "suppress_tuplet_number = " << suppress_tuplet_number << std::endl; + out << prefix << "suppress_tuplet_bracket = " << suppress_tuplet_bracket << std::endl; + out << prefix << "suppress_articulations = " << suppress_articulations << std::endl; + out << prefix << "tremolo = " << tremolo << std::endl; // std::vector cue_size; // std::vector stem_type; // std::vector stem_visible; - out << prefix << "ligature_recta = " << ligature_recta << endl; - out << prefix << "ligature_obliqua = " << ligature_obliqua << endl; - out << prefix << "last_clef = " << last_clef << endl; - out << prefix << "acclev = " << acclev << endl; - out << prefix << "righthalfstem = " << righthalfstem << endl; + out << prefix << "ligature_recta = " << ligature_recta << std::endl; + out << prefix << "ligature_obliqua = " << ligature_obliqua << std::endl; + out << prefix << "last_clef = " << last_clef << std::endl; + out << prefix << "acclev = " << acclev << std::endl; + out << prefix << "righthalfstem = " << righthalfstem << std::endl; // Note *ottavanotestart; // Note *ottavanoteend; - out << prefix << "ottavaendtimestamp = " << ottavaendtimestamp << endl; + out << prefix << "ottavaendtimestamp = " << ottavaendtimestamp << std::endl; // Measure *ottavameasure; // Note *ottavadownnotestart; // Note *ottavadownnoteend; - out << prefix << "ottavadownendtimestamp = " << ottavadownendtimestamp << endl; + out << prefix << "ottavadownendtimestamp = " << ottavadownendtimestamp << std::endl; // Measure *ottavadownmeasure; // Note *ottava2notestart; // Note *ottava2noteend; - out << prefix << "ottava2endtimestamp = " << ottava2endtimestamp << endl; + out << prefix << "ottava2endtimestamp = " << ottava2endtimestamp << std::endl; // Measure *ottava2measure; // Note *ottava2downnotestart; // Note *ottava2downnoteend; - out << prefix << "ottava2downendtimestamp = " << ottava2downendtimestamp << endl; + out << prefix << "ottava2downendtimestamp = " << ottava2downendtimestamp << std::endl; // Measure *ottava2downmeasure; - out << prefix << "meter_top = " << meter_top << endl; - out << prefix << "meter_bottom = " << meter_bottom << endl; + out << prefix << "meter_top = " << meter_top << std::endl; + out << prefix << "meter_bottom = " << meter_bottom << std::endl; // std::list ties; - out << prefix << "m_dynampos = " << m_dynampos << endl; - out << prefix << "m_dynamstaffadj = " << m_dynamstaffadj << endl; - out << prefix << "m_dynamposdefined = " << m_dynamposdefined << endl; - out << prefix << "auto_custos = " << auto_custos << endl; - out << prefix << "suppress_manual_custos = " << suppress_manual_custos << endl; - out << prefix << "mensuration_type = " << mensuration_type << endl; - out << prefix << "join = " << join << endl; + out << prefix << "m_dynampos = " << m_dynampos << std::endl; + out << prefix << "m_dynamstaffadj = " << m_dynamstaffadj << std::endl; + out << prefix << "m_dynamposdefined = " << m_dynamposdefined << std::endl; + out << prefix << "auto_custos = " << auto_custos << std::endl; + out << prefix << "suppress_manual_custos = " << suppress_manual_custos << std::endl; + out << prefix << "mensuration_type = " << mensuration_type << std::endl; + out << prefix << "join = " << join << std::endl; return out; } @@ -982,7 +982,7 @@ bool HumdrumInput::convertHumdrum() processMeiOptions(infile); if (m_debug) { - cout << GetMeiString(); + std::cout << GetMeiString(); } // If the document has and elements you can call: @@ -2011,7 +2011,7 @@ void HumdrumInput::processHangingTieStart(humaux::HumdrumTie &tieinfo) int subindex = tieinfo.getStartSubindex(); Measure *measure = tieinfo.getStartMeasure(); if (measure == NULL) { - cerr << "Problem with start measure being NULL" << endl; + LogWarning("In HumdrumInput::processHangingTieStart: Start measure is NULL"); return; } // int metercount = tieinfo.getMeterTop(); @@ -2668,7 +2668,9 @@ void HumdrumInput::parseEmbeddedOptions(Doc *doc) continue; } if (value.empty()) { - cerr << "Warning: value is empty for parameter " << key << endl; + std::stringstream warning; + warning << "In HumdrumInput::parseEmbeddedOptions: value is empty for parameter " << key; + LogWarning(warning.str().c_str()); continue; } inputparameters[pkey] = pvalue; @@ -2687,7 +2689,9 @@ void HumdrumInput::parseEmbeddedOptions(Doc *doc) std::string pkey = hre.getMatch(1); std::string pvalue = hre.getMatch(2); if (value.empty()) { - cerr << "Warning: value is empty for parameter " << key << endl; + std::stringstream warning; + warning << "In HumdrumInput::parseEmbeddedOptions: Value is empty for parameter " << key; + LogWarning(warning.str().c_str()); continue; } inputparameters[pkey] = pvalue; @@ -2700,7 +2704,10 @@ void HumdrumInput::parseEmbeddedOptions(Doc *doc) for (auto inputoption : inputparameters) { auto entry = optionlist->find(inputoption.first); if (entry == optionlist->end()) { - cerr << "Warning: option " << inputoption.first << " is not recognized" << endl; + std::stringstream warning; + warning << "In HumdrumInput::parseEmbeddedOptions: option "; + warning << inputoption.first << " is not recognized"; + LogWarning(warning.str().c_str()); continue; } @@ -4954,8 +4961,13 @@ void HumdrumInput::createHumdrumVerbatimExtMeta(pugi::xml_node meiHead) pugi::xml_parse_result result = tmpdoc.load_string(xmldata.str().c_str()); if (!result) { // some sort of error, so give up; - cerr << "ExtMeta parse error: " << result.description() << endl; - cerr << xmldata.str(); + std::stringstream warning; + warning << "In HumdrumInput::createHumdrumVerbatimExtMeta: ExtMeta parse error: "; + warning << result.description(); + LogWarning(warning.str().c_str()); + std::stringstream warning2; + warning2 << " xmldata string is: " << xmldata.str(); + LogWarning(warning2.str().c_str()); return; } @@ -5879,8 +5891,8 @@ bool HumdrumInput::processStaffDecoration(const std::string &decoration) return false; } if ((0)) { - cerr << "INPUT DECORATION: " << decoration << endl; - cerr << " PROCESSED: " << d << endl; + std::cerr << "INPUT DECORATION: " << decoration << std::endl; + std::cerr << " PROCESSED: " << d << std::endl; } // Remove any staff numbers that are no longer present (or invalid): @@ -6010,7 +6022,7 @@ bool HumdrumInput::processStaffDecoration(const std::string &decoration) if ((0)) { // print analysis: for (int i = 0; i < (int)d.size(); ++i) { - cerr << "D[" << i << "] =\t" << d[i] << " pairing: " << pairing[i] << endl; + std::cerr << "D[" << i << "] =\t" << d[i] << " pairing: " << pairing[i] << std::endl; } } @@ -6238,13 +6250,13 @@ bool HumdrumInput::processStaffDecoration(const std::string &decoration) } if ((0)) { - cerr << "BAR GROUPS" << endl; + std::cerr << "BAR GROUPS" << std::endl; for (int i = 0; i < (int)bargroups.size(); ++i) { - cerr << "\tgroup_style=" << groupstyle[i] << "\tgroup = " << i << ":\t"; + std::cerr << "\tgroup_style=" << groupstyle[i] << "\tgroup = " << i << ":\t"; for (int j = 0; j < (int)bargroups[i].size(); j++) { - cerr << " " << bargroups[i][j]; + std::cerr << " " << bargroups[i][j]; } - cerr << endl; + std::cerr << std::endl; } } @@ -6275,7 +6287,7 @@ bool HumdrumInput::processStaffDecoration(const std::string &decoration) for (int i = 0; i < (int)found.size(); ++i) { if (found[i] != 1) { - cerr << "I:" << i << "\t=\t" << found[i] << endl; + std::cerr << "I:" << i << "\t=\t" << found[i] << std::endl; validQ = false; break; } @@ -6283,9 +6295,13 @@ bool HumdrumInput::processStaffDecoration(const std::string &decoration) } if (!validQ) { - cerr << "DECORATION IS INVALID " << decoration << endl; + std::stringstream warning; + warning << "In HumdrumInput::processStaffDecoration: Decoration is invalid: " << decoration; + LogWarning(warning.str().c_str()); if (d != decoration) { - cerr << "\tSTAFF VERSION: " << d << endl; + std::stringstream warning; + warning << "In HumdrumInput::processStaffDecoration: Staff version: " << d; + LogWarning(warning.str().c_str()); } StaffGrp *sg = new StaffGrp(); setGroupSymbol(sg, staffGroupingSym_SYMBOL_bracket); @@ -6890,7 +6906,7 @@ bool HumdrumInput::prepareFooter( } Object *detached = pgfoot->GetParent()->DetachChild(index); if (detached != pgfoot) { - std::cerr << "Detached element is not the pgHead" << std::endl; + LogWarning("In HumdrumInput::prepareFooter: Detached element is not the pgHead."); if (detached) { delete detached; } @@ -6911,7 +6927,7 @@ bool HumdrumInput::prepareFooter( } detached = pgfoot2->GetParent()->DetachChild(index); if (detached != pgfoot2) { - std::cerr << "Detached element is not a pgFoot element" << std::endl; + LogWarning("In HumdrumInput::prepareFooter: Detached element is not a pgFoot element"); if (detached) { delete detached; } @@ -7065,7 +7081,7 @@ bool HumdrumInput::prepareHeader( } Object *detached = pghead->GetParent()->DetachChild(index); if (detached != pghead) { - std::cerr << "Detached element is not the pgHead" << std::endl; + LogWarning("In HumdrumInput::prepareHeader: Detached element is not the pgHead"); if (detached) { delete detached; } @@ -8874,7 +8890,10 @@ void HumdrumInput::setMensurationSymbol( } } else { - std::cerr << "Warning: do not understand mensuration " << metdata << std::endl; + std::stringstream warning; + warning << "In HumdrumInput::setMensurationSymbol: Problem parsing mensuration: "; + warning << metdata; + LogWarning(warning.str().c_str()); return; } @@ -8929,41 +8948,63 @@ void HumdrumInput::setMensurationSymbol( prolatio = stoi(num4); } + std::stringstream warning; switch (prolatio) { case 2: vrvmensur->SetProlatio(PROLATIO_2); break; case 3: vrvmensur->SetProlatio(PROLATIO_3); break; case 0: break; - default: cerr << "Warning: unknown prolation " << prolatio << " in " << mensurtok << endl; + default: + warning.str(""); + warning << "In HumdrumInput::setMensurationSymbol: Unknown prolation "; + warning << prolatio << " in " << mensurtok; + LogWarning(warning.str().c_str()); } switch (tempus) { case 2: vrvmensur->SetTempus(TEMPUS_2); break; case 3: vrvmensur->SetTempus(TEMPUS_3); break; case 0: break; - default: cerr << "Warning: unknown tempus " << tempus << " in " << mensurtok << endl; + default: + warning.str(""); + warning << "In HumdrumInput::setMensurationSymbol: Unknown tempus "; + warning << tempus << " in " << mensurtok; + LogWarning(warning.str().c_str()); } switch (modus) { case 2: vrvmensur->SetModusminor(MODUSMINOR_2); break; case 3: vrvmensur->SetModusminor(MODUSMINOR_3); break; case 0: break; - default: cerr << "Warning: unknown modus " << modus << " in " << mensurtok << endl; + default: + warning.str(""); + warning << "In HumdrumInput::setMensurationSymbol: Unknown modus "; + warning << modus << " in " << mensurtok; + LogWarning(warning.str().c_str()); } switch (maximodus) { case 2: vrvmensur->SetModusmaior(MODUSMAIOR_2); break; case 3: vrvmensur->SetModusmaior(MODUSMAIOR_3); break; case 0: break; - default: cerr << "Warning: unknown maximodus " << maximodus << " in " << mensurtok << endl; + default: + warning.str(""); + warning << "In HumdrumInput::setMensurationSymbol: Unknown maximodus "; + warning << maximodus << " in " << mensurtok; + LogWarning(warning.str().c_str()); } } std::vector &ss = m_staffstates; if (staffindex < 0) { - cerr << "Initialization problem, not setting mensuration information" << endl; - cerr << "STAFF INDEX = " << staffindex << endl; + std::stringstream warning; + warning << "In HumdrumInput::setMensrationSymbol: "; + warning << "Initialization problem, not setting mensuration information"; + LogWarning(warning.str().c_str()); + std::stringstream warning2; + warning2 << " Staff index = " << staffindex; + LogWarning(warning2.str().c_str()); return; } if (staffindex >= (int)ss.size()) { - cerr << "Problem with staff indexing in mensuration processing" << endl; + LogWarning("InHumdrumInput::setMensurationSymbol: Problem with staff indexing in mensuration processing"); return; } @@ -10147,7 +10188,10 @@ void HumdrumInput::storeStaffLayerTokensForMeasure(int startline, int endline) } staffindex = rkern[track]; if (staffindex < 0) { - cerr << "STAFF INDEX PROBLEM FOR TRACK " << track << endl; + std::stringstream warning; + warning << "In HumdrumInput::storeStaffLayerTokensForMeasure:"; + warning << "Staff inex problem for track " << track; + LogWarning(warning.str().c_str()); } if ((int)lt[staffindex].size() < layerindex + 1) { lt[staffindex].resize(lt[staffindex].size() + 1); @@ -11153,7 +11197,10 @@ void HumdrumInput::addHarmFloatsForMeasure(int startline, int endline) text->SetText(UTF8to32(*token)); } else { - cerr << "Unknown type of harm data: " << datatype << endl; + std::stringstream warning; + warning << "In HumdrumInput::addHarmFloatsForMeasure: "; + warning << "Unknown type of harm data " << datatype; + LogWarning(warning.str().c_str()); continue; } } @@ -12531,7 +12578,10 @@ void HumdrumInput::setMxHarmContent(Rend *rend, const std::string &content) } } else { - cerr << "should not get here with correct input " << content << endl; + std::stringstream warning; + warning << "In HumdrumInput::setMxHarmContent: "; + warning << "Should not get here if correct input: " << content; + LogWarning(warning.str().c_str()); } } @@ -12883,29 +12933,29 @@ void HumdrumInput::fixLargeTuplets(std::vector &tg void HumdrumInput::printGroupInfo(const std::vector &tg) { - cerr << "TOK\t\tGRP\tBRAK\tNUM\tNBASE\tNSCAL\tBSTART\tBEND"; - cerr << "\tGBST\tGBEND\tTSTART\tTEND\tFORCE\tPRIORITY\n"; + std::cerr << "TOK\t\tGRP\tBRAK\tNUM\tNBASE\tNSCAL\tBSTART\tBEND"; + std::cerr << "\tGBST\tGBEND\tTSTART\tTEND\tFORCE\tPRIORITY\n"; for (int i = 0; i < (int)tg.size(); ++i) { - cerr << tg.at(i).token << "\t"; + std::cerr << tg.at(i).token << "\t"; if (tg.at(i).token && (tg.at(i).token->size() < 8)) { - cerr << "\t"; + std::cerr << "\t"; } - cerr << tg.at(i).group << "\t"; - cerr << tg.at(i).bracket << "\t"; - cerr << tg.at(i).num << "\t"; - cerr << tg.at(i).numbase << "\t"; - cerr << tg.at(i).numscale << "\t"; - cerr << tg.at(i).beamstart << "\t"; - cerr << tg.at(i).beamend << "\t"; - cerr << tg.at(i).gbeamstart << "\t"; - cerr << tg.at(i).gbeamend << "\t"; - cerr << "TS:" << tg.at(i).tupletstart << "\t"; - cerr << "TE:" << tg.at(i).tupletend << "\t"; - cerr << tg.at(i).force << "\t"; - cerr << tg.at(i).priority; - cerr << endl; + std::cerr << tg.at(i).group << "\t"; + std::cerr << tg.at(i).bracket << "\t"; + std::cerr << tg.at(i).num << "\t"; + std::cerr << tg.at(i).numbase << "\t"; + std::cerr << tg.at(i).numscale << "\t"; + std::cerr << tg.at(i).beamstart << "\t"; + std::cerr << tg.at(i).beamend << "\t"; + std::cerr << tg.at(i).gbeamstart << "\t"; + std::cerr << tg.at(i).gbeamend << "\t"; + std::cerr << "TS:" << tg.at(i).tupletstart << "\t"; + std::cerr << "TE:" << tg.at(i).tupletend << "\t"; + std::cerr << tg.at(i).force << "\t"; + std::cerr << tg.at(i).priority; + std::cerr << std::endl; } - cerr << "============================================" << endl; + std::cerr << "============================================" << std::endl; } ////////////////////////////// @@ -13197,7 +13247,10 @@ bool HumdrumInput::checkForTremolo( int beams = -log(duration.getFloat()) / log(2.0); if (beams <= 0) { // something went wrong calculating durations. - cerr << "PROBLEM WITH TREMOLO2 CALCULATION: " << beams << endl; + std::stringstream warning; + warning << "In HumdrumInput::checkForTremolo: "; + warning << "Problem with tremolo2 calculation, beam count: " << beams; + LogWarning(warning.str().c_str()); return false; } @@ -13238,7 +13291,7 @@ bool HumdrumInput::checkForInvisibleBeam( int beamnum = tgs.at(layerindex).beamstart; for (int i = layerindex; i < (int)tgs.size(); ++i) { if (!tgs.at(i).token) { - cerr << "WARNING in checkForInvisibleBeam: NULL token\n"; + LogWarning("In HumdrumInput::checkForInvisibleBeam: Encountered NULL token"); return false; } int len = (int)tgs.at(i).token->size(); @@ -13966,9 +14019,13 @@ bool HumdrumInput::fillContentsOfLayer(int track, int startline, int endline, in } } else { - std::cerr << "Strange error for adding rest " << trest << std::endl; - std::cerr << "LINE: " << trest->getLineNumber() << ", FIELD: " << trest->getFieldNumber() - << std::endl; + std::stringstream warning; + warning << "In HumdrumInput::fillContentsOfLayer: "; + warning << "Strange error when adding rest " << trest; + LogWarning(warning.str().c_str()); + std::stringstream warning2; + warning2 << " Line: " << trest->getLineNumber() << ", Field: " << trest->getFieldNumber(); + LogWarning(warning2.str().c_str()); } } @@ -16035,7 +16092,7 @@ void HumdrumInput::convertMensuralToken( } } else { - std::cerr << "WARNING: unmatched ligature ending" << std::endl; + LogWarning("In HumdrumInput::convertMensuralToken: Unmatched ligature ending"); } } if (roff) { @@ -17899,7 +17956,10 @@ void HumdrumInput::processLinkedDirection(int index, hum::HTp token, int staffin setLocationId(tempo, dirtok); } else { - cerr << "DIRTOK FOR " << token << " IS EMPTY " << endl; + std::stringstream warning; + warning << "In HumdrumInput::processLinkedDirection: dirtok for "; + warning << token << " is empty"; + LogWarning(warning.str().c_str()); } hum::HumNum tstamp = getMeasureTstamp(token, staffindex); if (token->isMensLike()) { @@ -17944,7 +18004,10 @@ void HumdrumInput::processLinkedDirection(int index, hum::HTp token, int staffin setLocationId(dir, dirtok); } else { - cerr << "DIRTOK FOR " << token << " IS EMPTY " << endl; + std::stringstream warning; + warning << "In HumdrumInput::processLinkedDirection: (2) dirtok for "; + warning << token << " is empty"; + LogWarning(warning.str().c_str()); } if (token->isMensLike()) { @@ -19884,7 +19947,7 @@ template void HumdrumInput::setAttachmentType(ELEMENT *element, template void HumdrumInput::attachToToken(ELEMENT *element, hum::HTp token) { if (token->isNull()) { - cerr << "ERROR: Cannot input null tokens into HumdrumInput::attachToToken() function." << endl; + LogWarning("In HumdrumInput::attachToToken: Cannot input null tokens into this function"); return; } if (token->isChord()) { @@ -22709,9 +22772,9 @@ void HumdrumInput::analyzeLayerBeams( if (m_debug) { for (int i = 0; i < (int)beamstate.size(); ++i) { - cerr << layerdata[i] << "(" << beamstate[i] << ") "; + std::cerr << layerdata[i] << "(" << beamstate[i] << ") "; } - cerr << endl; + std::cerr << std::endl; } // int beamstartindex = -1; @@ -23152,10 +23215,14 @@ Beam *HumdrumInput::insertGBeam( void HumdrumInput::removeBeam(std::vector &elements, std::vector &pointers) { if (elements.back() != "beam") { - cerr << "ERROR REMOVING BEAM" << endl; - cerr << "ELEMENT STACK:" << endl; + LogWarning("In HumdrumInput::removeBeam: Error removing beam"); + std::stringstream warning; + LogWarning(" Element stack: "); for (int i = (int)elements.size() - 1; i >= 0; i--) { - cerr << i << ":\t" << elements[i] << endl; + std::stringstream warning; + warning.str(""); + warning << " " << i << ":\t" << elements[i]; + LogWarning(warning.str().c_str()); } return; } @@ -23170,10 +23237,13 @@ void HumdrumInput::removeBeam(std::vector &elements, std::vector &elements, std::vector &pointers) { if (elements.back() != "gbeam") { - cerr << "ERROR REMOVING GBEAM" << endl; - cerr << "ELEMENT STACK:" << endl; + LogWarning("In HumdrumInput::removeGBeam: Error removing gbeam"); + LogWarning(" Element stack: "); for (int i = (int)elements.size() - 1; i >= 0; i--) { - cerr << i << ":\t" << elements[i] << endl; + std::stringstream warning; + warning.str(""); + warning << " " << i << ":\t" << elements[i]; + LogWarning(warning.str().c_str()); } return; } @@ -23188,11 +23258,16 @@ void HumdrumInput::removeGBeam(std::vector &elements, std::vector &elements, std::vector &pointers) { if (elements.back() != "tuplet") { - cerr << "ERROR REMOVING TUPLET" << endl; - cerr << "ELEMENT BACK IS " << elements.back() << endl; - cerr << "ELEMENT STACK:" << endl; + LogWarning("In HumdrumInput::removeTuplet: Error removing tuplet"); + std::stringstream warning; + warning << " Last element is: " << elements.back(); + LogWarning(warning.str().c_str()); + LogWarning(" Element stack: "); for (int i = (int)elements.size() - 1; i >= 0; i--) { - cerr << i << ":\t" << elements[i] << endl; + std::stringstream warning; + warning.str(""); + warning << " " << i << ":\t" << elements[i]; + LogWarning(warning.str().c_str()); } return; } @@ -24065,7 +24140,7 @@ void HumdrumInput::mergeTupletsCuttingBeam(std::vectortupletstart; scaleadj.at(i) = 2; @@ -24082,7 +24157,7 @@ void HumdrumInput::mergeTupletsCuttingBeam(std::vectortupletend = 0; @@ -24110,11 +24185,11 @@ void HumdrumInput::mergeTupletsCuttingBeam(std::vectortupletstart << "\t" - << newtg.at(i)->tupletend << "\t" << newtg.at(i)->num << "\t" << newtg.at(i)->numbase - << "\tSA=" << scaleadj.at(i) << endl; + std::cerr << "I " << i << ":\t" << inbeam.at(i) << "\t" << newtg.at(i)->tupletstart << "\t" + << newtg.at(i)->tupletend << "\t" << newtg.at(i)->num << "\t" << newtg.at(i)->numbase + << "\tSA=" << scaleadj.at(i) << std::endl; } } @@ -25521,6 +25596,7 @@ void HumdrumInput::adjustChordNoteDuration(Note *note, hum::HumNum hdur, int mei void HumdrumInput::setNoteMeiDur(Note *note, int meidur) { + std::stringstream warning; switch (meidur) { case -1: note->SetDur(DURATION_maxima); break; case 0: note->SetDur(DURATION_long); break; @@ -25536,7 +25612,9 @@ void HumdrumInput::setNoteMeiDur(Note *note, int meidur) case 10: note->SetDur(DURATION_256); break; case 11: note->SetDur(DURATION_512); break; case 12: note->SetDur(DURATION_1024); break; - default: cerr << "UNKNOWN MEI DUR: " << meidur << endl; + default: + warning << "In HumdrumInput::setNoteMeiDur: Unknown MEI @dur: " << meidur; + LogWarning(warning.str().c_str()); } } @@ -25718,7 +25796,10 @@ void HumdrumInput::appendElement(const std::vector &name, const std appendElement((Ligature *)pointers.back(), child); } else { - std::cerr << "WARNING: Cannot append to unknown element: " << name.back() << std::endl; + std::stringstream warning; + warning << "In HumdrumInput::appendElement: "; + warning << "Cannot append to unknown element: " << name.back(); + LogWarning(warning.str().c_str()); } } @@ -26515,7 +26596,12 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta case -1: myaccid->SetAccid(ACCIDENTAL_WRITTEN_f); break; case -2: myaccid->SetAccid(ACCIDENTAL_WRITTEN_ff); break; case -3: myaccid->SetAccid(ACCIDENTAL_WRITTEN_tf); break; - default: std::cerr << "Do not know how to convert accidental: " << accidCount << endl; + default: { + std::stringstream warning; + warning << "In HumdrumInput::convertNote: "; + warning << "Do not know how to convert accidental: " << accidCount; + LogWarning(warning.str().c_str()); + } } if (accidlevel != 0) { @@ -26563,7 +26649,12 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta case -1: accid->SetAccid(ACCIDENTAL_WRITTEN_f); break; case -2: accid->SetAccid(ACCIDENTAL_WRITTEN_ff); break; case -3: accid->SetAccid(ACCIDENTAL_WRITTEN_tf); break; - default: std::cerr << "Do not know how to convert accidental: " << accidCount << endl; + default: { + std::stringstream warning; + warning << "In HumdrumInput::convertNote: "; + warning << "Do not know how to convert accidental: " << accidCount; + LogWarning(warning.str().c_str()); + } } } else if (!loaccid.empty()) { @@ -26611,7 +26702,10 @@ void HumdrumInput::convertNote(Note *note, hum::HTp token, int staffadj, int sta showInAccidGes = true; } else { - std::cerr << "Warning: unknown accidental type " << std::endl; + std::stringstream warning; + warning << "In HumdrumInput::convertNote: "; + warning << "Unknown accidental type: " << loaccid; + LogWarning(warning.str().c_str()); } // add more accidentals here as necessary. Mostly left are quarter tones // which are not dealt with directly in **kern data: su, sd, fu, fd, nu, @@ -28615,8 +28709,11 @@ void HumdrumInput::addTurn(hum::HTp token, const string &tok, int noteIndex) } bool singleQ = false; if (turnstart == turnend) { - LogWarning( - "Humdrum: Single turn character on line %d, field, %d\n", token->getLineNumber(), token->getFieldNumber()); + std::stringstream warning; + warning << "In HumdrumInput::addTurn: Single turn character "; + warning << "on line " << token->getLineNumber() << ", "; + warning << "field, " << token->getFieldNumber() << "."; + LogWarning(warning.str().c_str()); singleQ = true; } @@ -29651,15 +29748,15 @@ void HumdrumInput::printMeasureTokens() { std::vector>> < = m_layertokens; int i, j, k; - cerr << endl; + std::cerr << std::endl; for (i = 0; i < (int)lt.size(); ++i) { - cerr << "STAFF " << i + 1 << "\t"; + std::cerr << "STAFF " << i + 1 << "\t"; for (j = 0; j < (int)lt[i].size(); ++j) { - cerr << "LAYER " << j + 1 << ":\t"; + std::cerr << "LAYER " << j + 1 << ":\t"; for (k = 0; k < (int)lt[i][j].size(); ++k) { - cout << " " << *lt[i][j][k]; + std::cout << " " << *lt[i][j][k]; } - cerr << endl; + std::cerr << std::endl; } } } @@ -29739,7 +29836,10 @@ template hum::HumNum HumdrumInput::setDuration(ELEMENT element, } // Don't know what to do, so return duration // There will be an error in the data. - cerr << "Unprintable rhythm: " << duration << endl; + std::stringstream warning; + warning << "In HumdrumInput::setDuration: "; + warning << "Unprintable duration" << duration << " quarter notes"; + LogWarning(warning.str().c_str()); return duration; } @@ -29807,7 +29907,7 @@ Tie *HumdrumInput::tieToPreviousItem(hum::HTp token, int subindex, hum::HumNum m tstamp += 1; } else { - cerr << "STRANGE CASE IN TIE INSERTION" << endl; + LogWarning("In HumdrumInput::tieToPreviousItem: Strange case for tie insertion."); } tie->SetTstamp(tstamp.getFloat()); // attach start to beginning of measure @@ -31665,7 +31765,7 @@ std::vector HumdrumInput::analyzeMultiRest(hum::HumdrumFile &infile) } // for (int i = 0; i < infile.getLineCount(); ++i) { - // cout << infile[i] << "\t" << output[i] << "\n"; + // std:: cout << infile[i] << "\t" << output[i] << "\n"; //} // Example analysis, with measure 4 staring a rest with num="6". // Measures 5-9 marked as whole-measure rests which will be merged into From 744c1da4de88d044a07be818f77b14a405a275e5 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sat, 7 Sep 2024 08:45:19 -0700 Subject: [PATCH 372/383] Fix for issue https://github.com/rism-digital/verovio/issues/3766 --- src/toolkit.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/toolkit.cpp b/src/toolkit.cpp index cfb0720caaf..cf2c81513db 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -609,7 +609,18 @@ bool Toolkit::LoadData(const std::string &data) pugi::xml_document xmlfile; xmlfile.load_string(data.c_str()); stringstream conversion; + + // Temporarily redirect cerr: + std::stringstream captured_cerr; + std::streambuf *cerr_buf = std::cerr.rdbuf(); + std::cerr.rdbuf(captured_cerr.rdbuf()); + bool status = converter.convert(conversion, xmlfile); + LogWarning(captured_cerr.str().c_str()); + + // Restore cerr: + std::cerr.rdbuf(cerr_buf); + if (!status) { LogError("Error converting MusicXML data"); return false; @@ -657,7 +668,18 @@ bool Toolkit::LoadData(const std::string &data) // This is the indirect converter from MuseData to MEI using iohumdrum: hum::Tool_musedata2hum converter; stringstream conversion; + + // Temporarily redirect cerr: + std::stringstream captured_cerr; + std::streambuf *cerr_buf = std::cerr.rdbuf(); + std::cerr.rdbuf(captured_cerr.rdbuf()); + bool status = converter.convertString(conversion, data); + LogWarning(captured_cerr.str().c_str()); + + // Restore cerr: + std::cerr.rdbuf(cerr_buf); + if (!status) { LogError("Error converting MuseData data"); return false; @@ -684,8 +706,19 @@ bool Toolkit::LoadData(const std::string &data) else if (inputFormat == ESAC) { // This is the indirect converter from EsAC to MEI using iohumdrum: hum::Tool_esac2hum converter; + + // Temporarily redirect cerr: + std::stringstream captured_cerr; + std::streambuf *cerr_buf = std::cerr.rdbuf(); + std::cerr.rdbuf(captured_cerr.rdbuf()); stringstream conversion; + bool status = converter.convert(conversion, data); + LogWarning(captured_cerr.str().c_str()); + + // Restore cerr: + std::cerr.rdbuf(cerr_buf); + if (!status) { LogError("Error converting EsAC data"); return false; @@ -2040,7 +2073,18 @@ const char *Toolkit::GetHumdrumBuffer() infile.load_string(meidata.c_str()); stringstream out; hum::Tool_mei2hum converter; + + // Temporarily redirect cerr: + std::stringstream captured_cerr; + std::streambuf *cerr_buf = std::cerr.rdbuf(); + std::cerr.rdbuf(captured_cerr.rdbuf()); + converter.convert(out, infile); + LogWarning(captured_cerr.str().c_str()); + + // Restore cerr: + std::cerr.rdbuf(cerr_buf); + this->SetHumdrumBuffer(out.str().c_str()); #endif if (m_humdrumBuffer) { @@ -2101,7 +2145,18 @@ std::string Toolkit::ConvertMEIToHumdrum(const std::string &meiData) pugi::xml_document xmlfile; xmlfile.load_string(meiData.c_str()); std::stringstream conversion; + + // Temporarily redirect cerr: + std::stringstream captured_cerr; + std::streambuf *cerr_buf = std::cerr.rdbuf(); + std::cerr.rdbuf(captured_cerr.rdbuf()); + bool status = converter.convert(conversion, xmlfile); + LogWarning(captured_cerr.str().c_str()); + + // Restore cerr: + std::cerr.rdbuf(cerr_buf); + if (!status) { LogError("Error converting MEI data to Humdrum: %s", conversion.str().c_str()); } From 4eb98d659155378905b711fa3764c52ba31bd014 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sat, 7 Sep 2024 14:12:48 -0700 Subject: [PATCH 373/383] Remove exit() from MusicXML-to-Humdrum converter. --- include/hum/humlib.h | 2 +- src/hum/humlib.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 374437551e4..2a722232a2c 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Sep 5 14:41:50 PDT 2024 +// Last Modified: Sat Sep 7 13:38:03 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 0e37f29e2d9..74a9708af41 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Sep 5 14:41:50 PDT 2024 +// Last Modified: Sat Sep 7 13:38:03 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -106421,10 +106421,10 @@ bool Tool_musicxml2hum::convertFile(ostream& out, const char* filename) { xml_document doc; auto result = doc.load_file(filename); if (!result) { - cerr << "\nXML file [" << filename << "] has syntax errors\n"; - cerr << "Error description:\t" << result.description() << "\n"; - cerr << "Error offset:\t" << result.offset << "\n\n"; - exit(1); + cerr << "\nXML file [" << filename << "] has syntax errors "; + cerr << "Error description:\t" << result.description() << endl; + cerr << "Error offset:\t" << result.offset << "\n"; + return false; } return convert(out, doc); @@ -106441,10 +106441,10 @@ bool Tool_musicxml2hum::convert(ostream& out, const char* input) { xml_document doc; auto result = doc.load_string(input); if (!result) { - cout << "\nXML content has syntax errors\n"; - cout << "Error description:\t" << result.description() << "\n"; + cout << "\nXML content has syntax errors"; + cout << " Error description:\t" << result.description() << "\n"; cout << "Error offset:\t" << result.offset << "\n\n"; - exit(1); + return false; } return convert(out, doc); @@ -107389,10 +107389,10 @@ bool Tool_musicxml2hum::stitchParts(HumGrid& outdata, // i used to start at 1 for some strange reason. for (i=0; i<(int)partdata.size(); i++) { if (measurecount != partdata[i].getMeasureCount()) { - cerr << "ERROR: cannot handle parts with different measure\n"; + cerr << "ERROR: cannot handle parts with different measure "; cerr << "counts yet. Compare MM" << measurecount << " to MM"; cerr << partdata[i].getMeasureCount() << endl; - exit(1); + return false; } } From 0ede4fa0e7f77fd4973c08fd9172cceb009032ef Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Sat, 7 Sep 2024 14:59:44 -0700 Subject: [PATCH 374/383] Add "esac" as in input-from option. --- src/options.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/options.cpp b/src/options.cpp index 94b640dc35b..ba0265ef74a 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -914,8 +914,8 @@ Options::Options() m_baseOptions.AddOption(&m_allPages); m_inputFrom.SetInfo("Input from", - "Select input format from: \"abc\", \"darms\", \"humdrum\", \"mei\", \"pae\", \"volpiano\", \"xml\" " - "(musicxml)"); + "Select input format from: \"abc\", \"darms\", \"esac\", \"humdrum\", \"mei\", \"pae\", \"volpiano\", \"xml\" " + "(musicxml), \"musicxml-hum\" (musicxml via humdrum)"); m_inputFrom.Init("mei"); m_inputFrom.SetKey("inputFrom"); m_inputFrom.SetShortOption('f', false); From 27887f0904ccf851bb2bb407beeef7e5c2c751ed Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 9 Sep 2024 10:54:27 +0200 Subject: [PATCH 375/383] Make LoadFile, LoadZipData and LoadZipFile reset the log buffer --- include/vrv/toolkit.h | 5 +++++ src/toolkit.cpp | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 8ec0376e71d..66bc2b4595e 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -770,6 +770,11 @@ class Toolkit { */ void ResetLogBuffer(); + /** + * Load a string data with or without resetting the log buffer + */ + bool LoadData(const std::string &data, bool resetLogBuffer); + private: bool SetFont(const std::string &fontName); bool IsUTF16(const std::string &filename); diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 7b4edae8ea2..0056ffcd2b5 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -311,6 +311,8 @@ FileFormat Toolkit::IdentifyInputFrom(const std::string &data) bool Toolkit::LoadFile(const std::string &filename) { + this->ResetLogBuffer(); + if (this->IsUTF16(filename)) { return this->LoadUTF16File(filename); } @@ -332,7 +334,7 @@ bool Toolkit::LoadFile(const std::string &filename) std::string content(fileSize, 0); in.read(&content[0], fileSize); - return this->LoadData(content); + return this->LoadData(content, false); } bool Toolkit::IsUTF16(const std::string &filename) @@ -393,7 +395,7 @@ bool Toolkit::LoadUTF16File(const std::string &filename) std::wstring_convert, char16_t> convert; std::string utf8line = convert.to_bytes(u16data); - return this->LoadData(utf8line); + return this->LoadData(utf8line, false); } bool Toolkit::IsZip(const std::string &filename) @@ -438,6 +440,7 @@ bool Toolkit::LoadZipFile(const std::string &filename) bool Toolkit::LoadZipData(const std::vector &bytes) { + this->ResetLogBuffer(); #ifndef NO_MXL_SUPPORT ZipFileReader zipFileReader; zipFileReader.LoadBytes(bytes); @@ -456,7 +459,7 @@ bool Toolkit::LoadZipData(const std::vector &bytes) if (!filename.empty()) { LogInfo("Loading file '%s' in the archive", filename.c_str()); - return this->LoadData(zipFileReader.ReadTextFile(filename)); + return this->LoadData(zipFileReader.ReadTextFile(filename), false); } else { LogError("No file to load found in the archive"); @@ -481,10 +484,19 @@ bool Toolkit::LoadZipDataBuffer(const unsigned char *data, int length) } bool Toolkit::LoadData(const std::string &data) +{ + return this->LoadData(data, true); +} + +bool Toolkit::LoadData(const std::string &data, bool resetLogBuffer) { std::string newData; Input *input = NULL; + if (resetLogBuffer) { + this->ResetLogBuffer(); + } + m_doc.m_expansionMap.Reset(); if (m_options->m_xmlIdChecksum.GetValue()) { @@ -1423,6 +1435,7 @@ std::string Toolkit::GetLog() for (const std::string &logStr : logBuffer) { str += logStr; } + this->ResetLogBuffer(); return str; } @@ -1560,7 +1573,7 @@ bool Toolkit::RenderToDeviceContext(int pageNo, DeviceContext *deviceContext) std::string Toolkit::RenderData(const std::string &data, const std::string &jsonOptions) { - if (this->SetOptions(jsonOptions) && this->LoadData(data)) return this->RenderToSVG(1); + if (this->SetOptions(jsonOptions) && this->LoadData(data, false)) return this->RenderToSVG(1); // Otherwise just return an empty string. return ""; From dc2c72a437ac9902a9fdf083aea8d0dd0b8be5d2 Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 9 Sep 2024 11:12:13 -0700 Subject: [PATCH 376/383] Humlib updates. --- include/hum/humlib.h | 54 +++----- src/hum/humlib.cpp | 304 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 280 insertions(+), 78 deletions(-) diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 2a722232a2c..d9b39732d30 100644 --- a/include/hum/humlib.h +++ b/include/hum/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Sat Sep 7 13:38:03 PDT 2024 +// Last Modified: Sun Sep 8 23:07:16 PDT 2024 // Filename: min/humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.h // Syntax: C++11 @@ -7502,6 +7502,9 @@ class Tool_esac2hum : public HumTool { void processSong (void); void printScoreContents (std::ostream& output); void embedAnalyses (std::ostream& output); + void printPdfUrl (std::ostream& output); + std::string getKolbergUrl (int volume); + void printKolbergPdfUrl (std::ostream& output); private: bool m_debugQ = false; // used with --debug option @@ -7524,43 +7527,28 @@ class Tool_esac2hum : public HumTool { std::string m_cutline; std::vector m_globalComments; + bool m_initialized = false; int m_minrhy = 0; Tool_esac2hum::Score m_score; - std::map m_bem_translation = { - {"Czwarta zwrotka pieśni Niech będzie Jezus Chrystus pochwalony", "Fourth verse of the song \"Let Jesus Christ be praised\""}, - {"Do oczepin, zakodować w A?", "For the unveiling, encode in A?"}, - {"Do oczepin", "For the unveiling"}, - {"Druga zwrotka poprzedniej pieśni", "Second verse of the previous song"}, - {"Gdy zdejmują wianek", "When they remove the wreath"}, - {"Jak do ślubu odjeżdżają (na wozie)", "As they depart for the wedding (on a wagon)"}, - {"Krakowiak", "Krakowiak"}, - {"Marsz (konfederatów Barskich) Przygrywka na trąbie", "March (of the Bar Confederates) Prelude on trumpet"}, - {"Marsz konfederatów Barskich", "March of the Bar Confederates"}, - {"Mazur", "Mazur"}, - {"Na przodziek", "At the front"}, - {"Owczarek gładki, szybko tańczony", "Smooth shepherd's dance, danced quickly"}, - {"Owczarek", "Shepherd's dance"}, - {"Piosenka żniwiarska", "Harvest song"}, - {"Polonez (pokutującego wojaka)", "Polonaise (of the penitent soldier)"}, - {"Polonez", "Polonaise"}, - {"Polski chodzony", "Polish walking dance"}, - {"Prawdopodobnie ośmiomiar 2+3+3", "Probably an eight-measure 2+3+3"}, - {"Prawdopodobnie rytm jambiczny", "Probably iambic rhythm"}, - {"Przy przenosinach", "During the moving"}, - {"Tańczy na przodziek (na weselu)", "Dances at the front (at the wedding)"}, - {"W sobotę gdy wianki wiją", "On Saturday when they weave the wreaths"}, - {"Wesele do ślubu", "Wedding to the church"}, - {"Wesele", "Wedding"}, - {"Weselna gdy zbierają składkę", "Wedding song when collecting contributions"}, - {"Weselna", "Wedding song"}, - {"Wielkanoc", "Easter"}, - {"Wieniec", "Wreath"}, - {"Zakodować w C?", "Encode in C?"}, - {"w t. 10 wpisany tryl dziadowski 43b21", "in measure 10, a beggar's trill written 43b21"}, - {"żniwiarska", "Harvest song"} + class KolbergInfo { + public: + std::string titlePL; + std::string titleEN; + int firstPrintPage; + int firstScanPage; + std::vector plates; + + KolbergInfo(void) { firstPrintPage = 0; firstScanPage = 0; } + KolbergInfo( + const std::string& pl, const std::string& en, int fpp, int fsp, const std::vector& plts) + : titlePL(pl), titleEN(en), firstPrintPage(fpp), firstScanPage(fsp), plates(plts) {} }; + std::map m_kinfo; + KolbergInfo getKolbergInfo(int volume); + std::string getKolbergUrl(int volume, int printPage); + int calculateScanPage(int inputPrintPage, int printPage, int scanPage, const std::vector& platePages); }; diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 74a9708af41..ed7780c0419 100644 --- a/src/hum/humlib.cpp +++ b/src/hum/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Sat Sep 7 13:38:03 PDT 2024 +// Last Modified: Sun Sep 8 23:07:16 PDT 2024 // Filename: min/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp // Syntax: C++11 @@ -55582,6 +55582,7 @@ bool Tool_autobeam::run(HumdrumFile& infile) { } // Re-load the text for each line from their tokens. infile.createLinesFromTokens(); + m_humdrum_text << infile; return true; } @@ -56410,9 +56411,9 @@ void Tool_autobeam::processMeasure(vector& measure) { if ((current.first % 3 == 0) && (current.first != 3)) { // compound meter, so shift the beat to 3x the demoniator beatdur *= 3; - } else if (current.first == 3 && (current.second > 4)) { + } else if (current.first == 3 && (current.second < 4)) { // time signatures such as 3/8 and 3/16 which should - // beam together at the measure level (3/4 not included). + // beam together at the measure level (3/4 and 3/2 not included). beatdur *= 3; } } @@ -78526,7 +78527,7 @@ void Tool_esac2hum::cleanText(std::string& buffer) { // Ż: c4 b9 c5 a5 -> c5 bb hre.replaceDestructive(buffer, "\xc5\xbb", "\xc4\xb9\xc5\xa5", "g"); - + // ż: c4 b9 c5 ba -> c5 bc hre.replaceDestructive(buffer, "\xc5\xbc", "\xc4\xb9\xc5\xba", "g"); @@ -79014,7 +79015,9 @@ void Tool_esac2hum::Score::calculateTimeSignatures(void) { vector timesigs; hre.split(timesigs, ts, "\\s+"); if (timesigs.size() < 2) { - m_errors.push_back("ERROR: strange format for time signatures."); + string error = "ERROR: Cannot find time signature(s) in KEY[] field: "; + error += m_params["KEY"]; + m_errors.push_back(error); return; } @@ -79073,7 +79076,6 @@ void Tool_esac2hum::Score::prepareMultipleTimeSignatures(const string& ts) { measure.setComplete(); } } - } } @@ -79347,15 +79349,15 @@ void Tool_esac2hum::Score::calculateKeyInformation(void) { } else if (m_keydesignation == "*b:") { m_keysignature = "*k[f#c#]"; } else if (m_keydesignation == "*f#:") { - m_keysignature = "*k[f#c#g$]"; + m_keysignature = "*k[f#c#g#]"; } else if (m_keydesignation == "*c#:") { - m_keysignature = "*k[f#c#g$d#]"; + m_keysignature = "*k[f#c#g#d#]"; } else if (m_keydesignation == "*g#:") { - m_keysignature = "*k[f#c#g$d#a#]"; + m_keysignature = "*k[f#c#g#d#a#]"; } else if (m_keydesignation == "*d#:") { - m_keysignature = "*k[f#c#g$d#a#e#]"; + m_keysignature = "*k[f#c#g#d#a#e#]"; } else if (m_keydesignation == "*a#:") { - m_keysignature = "*k[f#c#g$d#a#e#b#]"; + m_keysignature = "*k[f#c#g#d#a#e#b#]"; } else if (m_keydesignation == "*d:") { m_keysignature = "*k[b-]"; } else if (m_keydesignation == "*g:") { @@ -79369,7 +79371,7 @@ void Tool_esac2hum::Score::calculateKeyInformation(void) { } else if (m_keydesignation == "*e-:") { m_keysignature = "*k[b-e-a-d-g-c-]"; } else if (m_keydesignation == "*a-:") { - m_keysignature = "*k[b-e-a-d-g-f-]"; + m_keysignature = "*k[b-e-a-d-g-c-f-]"; } else { m_errors.push_back("ERROR: invalid/exotic key signature required."); } @@ -79435,7 +79437,9 @@ void Tool_esac2hum::Score::generateHumdrumNotes(void) { string tonic = m_params["_tonic"]; if (tonic.empty()) { - m_errors.push_back("Error: cannot find KEY[] tonic pitch"); + string error = "Error: cannot find tonic pitch in KEY[] field: "; + error += m_params["KEY"]; + m_errors.push_back(error); return; } char letter = std::tolower(tonic[0]); @@ -80162,7 +80166,7 @@ void Tool_esac2hum::getParameters(vector& infile) { } string key = m_score.m_params["KEY"]; - if (hre.search(key, "^\\s*([^\\s]+)\\s+(\\d+)\\s+([A-Gacdefg][bs]*)\\s+(.*?)\\s*$")) { + if (hre.search(key, "^\\s*([^\\s]+)\\s+(\\d+)\\s+([A-Gacdefg][b#]*)\\s+(.*?)\\s*$")) { string id = hre.getMatch(1); string minrhy = hre.getMatch(2); string tonic = hre.getMatch(3); @@ -80186,13 +80190,16 @@ void Tool_esac2hum::getParameters(vector& infile) { cerr << "Problem parsing KEY parameter: " << key << endl; } - string trd; + string trd = m_score.m_params["TRD"]; if (hre.search(trd, "^\\s*(.*)\\ss\\.")) { m_score.m_params["_source_trd"] = hre.getMatch(1); } - if (hre.search(trd, "s\\.\\s*(\\d+-?\\d*)")) { - // Could be text aftewards about the origin of the song. + if (hre.search(trd, "\\bs\\.\\s*(\\d+)\\s*-\\s*(\\d+)?")) { + m_score.m_params["_page"] = hre.getMatch(1) + "-" + hre.getMatch(2); + } else if (hre.search(trd, "\\bs\\.\\s*(\\d+)")) { m_score.m_params["_page"] = hre.getMatch(1); + } else { + cerr << "CANNOT FIND PAGE NUMBER IN " << trd << endl; } if (m_debugQ) { @@ -80236,13 +80243,7 @@ void Tool_esac2hum::printBemComment(ostream& output) { if (bem.empty()) { return; } - string english = m_bem_translation[bem]; - if (english.empty()) { - output << "!!!ONB: " << bem << endl; - } else { - output << "!!!ONB@@PL: " << bem << endl; - output << "!!!ONB@@EN: " << english << endl; - } + output << "!!!ONB: " << bem << endl; } @@ -80290,9 +80291,9 @@ void Tool_esac2hum::printFooter(ostream& output, vector& infile) { void Tool_esac2hum::printPageNumbers(ostream& output) { HumRegex hre; string trd = m_score.m_params["TRD"]; - if (hre.search(trd, "\\bs\\.\\s*(\\d+)\\s*-\\s*(\\d+)", "i")) { + if (hre.search(trd, "\\bs\\.\\s*(\\d+)\\s*-\\s*(\\d+)", "im")) { output << "!!!page: " << hre.getMatch(1) << "-" << hre.getMatch(2) << endl; - } else if (hre.search(trd, "\\bs\\.\\s*(\\d+)", "i")) { + } else if (hre.search(trd, "\\bs\\.\\s*(\\d+)", "im")) { output << "!!!page: " << hre.getMatch(1) << endl; } } @@ -80362,25 +80363,7 @@ void Tool_esac2hum::printPdfLinks(ostream& output) { output << "!!!URL: https::kolberg.ispan.pl/dwok/tomy Oskar Kolberg: Complete Works digital edition" << endl; - string source = m_score.m_params["_source"]; - HumRegex hre; - if (!hre.search(source, "^DWOK(\\d+)")) { - return; - } - string volume = hre.getMatch(1); - if (volume.size() == 1) { - volume = "0" + volume; - } - if (volume.size() == 2) { - volume = "0" + volume; - } - if (volume.size() > 3) { - return; - } - string nozero = volume; - hre.replaceDestructive(nozero, "" , "^0+"); - // need http:// not https:// for the following PDF link: - output << "!!!URL-pdf: http://oskarkolberg.pl/MediaFiles/" << volume << "dwok.pdf" << " Oskar Kolberg: Complete Works, volume " << nozero << endl; + printKolbergPdfUrl(output); } @@ -80815,6 +80798,237 @@ void Tool_esac2hum::Score::analyzeACC(void) { +////////////////////////////// +// +// Tool_esac2hum::getKolbergInfo -- +// + +Tool_esac2hum::KolbergInfo Tool_esac2hum::getKolbergInfo(int volume) { + + if (!m_initialized) { + m_initialized = true; + // Parameters: Polish volume title, English translation, print start page, Equivalent start scan (pdf page), Plate scan page vector + m_kinfo.emplace( 1, KolbergInfo("Pieśni ludu polskego", "Polish folk songs", 3, 99, {149, 150, 167, 168, 233, 234, 251, 252, 317, 318, 335, 336, 401, 402, 419, 420, 485, 486, 503, 504})); + m_kinfo.emplace( 2, KolbergInfo("Sandomierskie", "Sandomierz", 23, 34, {})); + m_kinfo.emplace( 3, KolbergInfo("Kujawy I", "Kuyavia I (north central Poland)", 209, 221, {})); + m_kinfo.emplace( 4, KolbergInfo("Kujawy II", "Kuyavia II (north central Poland)", 69, 83, {})); + m_kinfo.emplace( 5, KolbergInfo("Krakowskie I", "Crakow I", 194, 222, {})); + m_kinfo.emplace( 6, KolbergInfo("Krakowskie II", "Crakow II", 5, 29, {49, 50})); + // 7: Krakowskie III/Crakow III: no music + m_kinfo.emplace( 8, KolbergInfo("Krakowskie IV", "Crakow IV", 162, 182, {})); + m_kinfo.emplace( 9, KolbergInfo("W. Ks. Poznańskie I", "Grand Duchy of Poznań I", 117, 141, {})); + m_kinfo.emplace(10, KolbergInfo("W. Ks. Poznańskie II", "Grand Duchy of Poznań II", 60, 76, {})); + m_kinfo.emplace(11, KolbergInfo("W. Ks. Poznańskie III", "Grand Duchy of Poznań III", 39, 57, {})); + m_kinfo.emplace(12, KolbergInfo("W. Ks. Poznańskie IV", "Grand Duchy of Poznań IV", 3, 19, {})); + m_kinfo.emplace(13, KolbergInfo("W. Ks. Poznańskie V", "Grand Duchy of Poznań V", 3, 27, {})); + m_kinfo.emplace(14, KolbergInfo("W. Ks. Poznańskie VI", "Grand Duchy of Poznań VI", 157, 165, {})); + m_kinfo.emplace(15, KolbergInfo("W. Ks. Poznańskie VII", "Grand Duchy of Poznań VII", 317, 327, {})); + m_kinfo.emplace(16, KolbergInfo("Lubelskie I", "Lublin Voivodeship I", 105, 125, {})); + m_kinfo.emplace(17, KolbergInfo("Lubelskie II", "Lublin Voivodeship II", 1, 23, {})); + m_kinfo.emplace(18, KolbergInfo("Kieleckie I", "Kielce Voivodeship I", 49, 65, {})); + m_kinfo.emplace(19, KolbergInfo("Kieleckie II", "Kielce Voivodeship II", 1, 15, {})); + m_kinfo.emplace(20, KolbergInfo("Radomskie I", "Radom Voivodeship I", 75, 95, {})); + m_kinfo.emplace(21, KolbergInfo("Radomskie II", "Radom Voivodeship II", 1, 19, {})); + m_kinfo.emplace(22, KolbergInfo("Łęczyckie", "Łęczyca Voivodeship", 18, 36, {})); + m_kinfo.emplace(23, KolbergInfo("Kaliskie", "Kalisz Region", 54, 68, {})); + m_kinfo.emplace(24, KolbergInfo("Mazowsze I", "Mazovia I", 79, 103, {})); + m_kinfo.emplace(25, KolbergInfo("Mazowsze II", "Mazovia II", 1, 26, {})); + // 26: Mazowsze III/Mazovia III: no music + m_kinfo.emplace(27, KolbergInfo("Mazowsze IV", "Mazovia IV", 115, 134, {})); + m_kinfo.emplace(28, KolbergInfo("Mazowsze V", "Mazovia V", 64, 83, {})); + m_kinfo.emplace(29, KolbergInfo("Pokucie I", "Pokuttia I", 90, 122, {})); + m_kinfo.emplace(30, KolbergInfo("Pokucie II", "Pokuttia II", 1, 14, {})); + m_kinfo.emplace(31, KolbergInfo("Pokucie III", "Pokuttia III", 10, 31, {})); + // 32: Pokucie IV/Pokuttia IV: no music + m_kinfo.emplace(33, KolbergInfo("Chełmskie I", "Chełm Voivodeship I", 114, 150, {163, 164, 175, 176, 177, 178})); + m_kinfo.emplace(34, KolbergInfo("Chełmskie II", "Chełm Voivodeship II", 4, 21, {})); + m_kinfo.emplace(35, KolbergInfo("Przemyskie", "Przemyśl Voivodeship", 11, 47, {93, 94, 115, 116, 167, 168})); + // 36: Wołyń/Volhynia: complications + // 37: Miscellanea I/Miscellanea I: no music + // 38, Miscellanea II/Miscellanea II: no music + m_kinfo.emplace(39, KolbergInfo("Pomorze", "Pomerania", 67, 115, {129, 130, 147, 148})); + m_kinfo.emplace(40, KolbergInfo("Mazury Pruskie", "Prussian Masuria", 96, 155, {356, 357, 358, 359, 464, 465, 482, 483})); + + m_kinfo.emplace(41, KolbergInfo("Mazowsze VI", "Mazovia VI", 20, 95, {108, 109, 126, 127, 288, 289, 306, 307, 388, 389, 406, 407})); + m_kinfo.emplace(42, KolbergInfo("Mazowsze VII", "Mazovia VII", 6, 15, {})); + m_kinfo.emplace(43, KolbergInfo("Śląsk", "Silesia", 21, 62, {74, 75})); + m_kinfo.emplace(44, KolbergInfo("Góry i Podgórze I", "Mountains and Foothills I", 64, 110, {111, 112, 129, 130, 195, 196, 213, 214, 343, 344, 361, 362})); + m_kinfo.emplace(45, KolbergInfo("Góry i Podgórze II", "Mountains and Foothills II", 1, 11, {91, 92, 109, 110, 335, 336, 353, 354, 499, 500})); + m_kinfo.emplace(46, KolbergInfo("Kaliskie i Sieradzkie", "Kalisz Region and Sieradz Voivodeship", 3, 29, {43, 44, 61, 62, 175, 176, 193, 194})); + m_kinfo.emplace(47, KolbergInfo("Podole", "Podolia", 59, 105, {151, 152, 153, 154, 155, 156, 157, 158})); + m_kinfo.emplace(48, KolbergInfo("Tarnowskie-Rzeszowskie", "Tarnów-Rzeszów Voivodeship", 65, 103, {119, 120, 233, 234, 251, 252})); + m_kinfo.emplace(49, KolbergInfo("Sanockie-Krośnieńskie I", "Sanok-Krosno Voivodeship I", 109, 185, {189, 190, 239, 240, 257, 258, 387, 388, 405, 406, 455, 456, 473, 474})); + m_kinfo.emplace(50, KolbergInfo("Sanockie-Krośnieńskie II", "Sanok-Krosno Voivodeship II", 1, 14, {30, 31, 48, 49, 114, 115, 132, 133, 198, 199, 216 ,217, 282, 283, 300, 301, 366, 367, 384, 385})); + // 51: Sanockie-Krośnieńskie III/Sanok-Krosno Voivodeship III: no music + m_kinfo.emplace(52, KolbergInfo("Białoruś-Polesie", "Belarus-Polesia", 116, 169, {182, 183, 200, 201, 266, 267, 284, 285, 382, 383, 400, 401, 530, 531, 548, 549})); + m_kinfo.emplace(53, KolbergInfo("Litwa", "Lithuania", 142, 176, {195, 196, 325, 326, 359, 360, 441, 442, 459, 460})); + m_kinfo.emplace(54, KolbergInfo("Ruś karpacka I", "Carpathian Ruthenia I", 267, 365, {371, 372})); + m_kinfo.emplace(55, KolbergInfo("Ruś karpacka II", "Carpathian Ruthenia II", 22, 37, {48, 49, 146, 147, 164, 165, 214, 215, 232, 233, 426, 427, 444, 445})); + m_kinfo.emplace(56, KolbergInfo("Ruś czerwona I", "Red Ruthenia I", 61, 157, {173, 174, 191, 192, 209, 210, 259, 260, 27, 278, 311, 312, 329, 330, 443, 444, 461, 462})); + // 57: Ruś czerwona II/Red Ruthenia II, 14, 19, {70, 71, 88, 89}: complications + // 58: Materiały do etnografii Słowian wschodnich/Materials for the ethnography of the Eastern Slavs + // 59/I, Materiały do etnografii Słowian zachodnich i południowych. Cz. I Łużyce/Materials for the ethnography of western and southern Slavs. Part I Lusatia + // 59/II, Materiały do etnografii Słowian zachodnich i południowych. Cz. II Czechy, Słowacja/Materials for the ethnography of western and southern Slavs. Part II Czech Republic, Slovakia + // 59/III, Materiały do etnografii Słowian zachodnich i południowych. Cz. III Słowiańszczyzna południowa/Materials for the ethnography of western and southern Slavs. Part III Southern Slavs + // 60: Przysłowia/Proverbs: no music + // 61, Pisma muzyczne, cz. I/Musical writings, part I: no music + // 62, Pisma muzyczne, cz. II/"Musical writings, part II, 25, 33: not in EsAC + // 63, Studia, rozprawy i artykuły/Studies, dissertations and articles", 55, 113: not in EsAC + m_kinfo.emplace(64, KolbergInfo("Korespondencja Oskara Kolberga, cz. I (1837-1876)", "Correspondence of Oskar Kolberg, Part I (1837-1876)", 216, 261, {})); + // 65: Korespondencja Oskara Kolberga, cz. II (1877-1882)/Correspondence of Oskar Kolberg, Part II (1877-1882): no music + // 66: Korespondencja Oskara Kolberga, cz. III (1883-1890)/Correspondence of Oskar Kolberg, Part III (1883-1890): no music + /// 67: Pieśni i melodie ludowe w opracowaniu fortepianowym, cz. I-II/Folk songs and melodies in piano arrangement, part I-II, 3, 22, not in EsAC + // 68: Kompozycje wokalno-instrumentalne/Vocal and instrumental compositions, 3, 49, not in EsAC + // 69: Kompozycje fortepianowe/Piano compositions: 3, 39, not in EsAC + // 70: Pieśni ludu polskiego. Supl. do t.1/Polish folk songs: Supplement to Volume 1: no music + m_kinfo.emplace(71, KolbergInfo("Sandomierskie. Suplement do t. 2 Dzieła Wszystkie", "Sandomierz Voivodeship. Supplement to vol. 2 of The Complete Works", 3, 40, {116, 117, 134, 135})); + m_kinfo.emplace(72.1, KolbergInfo("Kujawy. Suplement do t. 3 i 4, cz. I, Kuyavia", "Supplement to vol. 3 and 4, part I", 3, 30, {292, 293, 310, 311})); + // 72.2, Kujawy. Suplement do t. 3 i 4, cz. II, Kuyavia/Supplement to vol. 3 and 4, part II,: missing information about pages? + m_kinfo.emplace(73, KolbergInfo("Krakowsike. Suplement do T. 5-8", "Cracow: Supplement to Volumes 5-8", 39, 163, {297, 298, 407, 408})); + // 74: Wielkie Księstwo Poznańskie. Suplement do t. 9-15/The Grand Duchy of Poznan. Supplement to vol. 9-15: no music + m_kinfo.emplace(75, KolbergInfo("Lubelskie. Supplement do tomów 16-17", "Lublin: Supplement to volumes 16-17", 4, 60, {281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324})); + m_kinfo.emplace(76, KolbergInfo("Kieleckie. Supplement do T. 18-19", "Kielce: Supplement to Volumes 18-19", 5, 41, {})); + // 77: Radomskie. Suplement do t. 20 i 21. I/Radomskie: Supplement to Volumes 20-21. I: complications + m_kinfo.emplace(78, KolbergInfo("Łęczyckie. Suplement do t. 22", "Łęczyca Voivodeship: Supplement to Volume 22", 3, 1, {})); + m_kinfo.emplace(79, KolbergInfo("Kaliskie. Suplement do t. 23", "Kalisz Region. Supplement to vol. 23", 3, 38, {})); + m_kinfo.emplace(80, KolbergInfo("Mazowsze. Suplement do t. 24-28, cz. I", "Mazovia. Supplement to vol. 24-28, part I", 7, 89, {})); + m_kinfo.emplace(81, KolbergInfo("Pokucie. Suplement do tomów 29-32", "Corrections: Supplement to Volumes 29-32", 3, 73, {121, 122, 139, 140})); + m_kinfo.emplace(82, KolbergInfo("Chełmskie. Suplement do T. 33 i 34", "Chełm supplement to Volumes 33 and 34", 7, 105, {})); + m_kinfo.emplace(83, KolbergInfo("Przemyskie. Suplement do tomu 35 DWOK", "Przemyśl Voivodeship: Supplement to Volume 35 DWOK", 9, 112, {380, 381, 382, 383, 384, 385, 386, 387})); + m_kinfo.emplace(84, KolbergInfo("Wołyń. Suplement do t. 36., Volhynia", "Supplement to Volume 36", 35, 97, {313, 314, 315, 316, 317, 318, 319, 320})); + + + } + + auto it = m_kinfo.find(volume); + if (it != m_kinfo.end()) { + return it->second; + } else { + return KolbergInfo(); + } +} + + + +////////////////////////////// +// +// Tool_esac::getKolbergUrl -- +// + +string Tool_esac2hum::getKolbergUrl(int volume) { + if ((volume < 1) || (volume > 84)) { + // Such a volume does not exist, return empty string. + return ""; + } + + stringstream ss; + ss << std::setw(3) << std::setfill('0') << volume; + // not https:// + string url = "http://www.oskarkolberg.pl/MediaFiles/"; + url += ss.str(); + url += "dwok.pdf"; + + KolbergInfo kinfo = getKolbergInfo(volume); + if (kinfo.titlePL.empty()) { + // Do not have the page number info for volume, so just give URL for the volume. + return url; + } + + string pageinfo = m_score.m_params["_page"]; + int printPage = 0; + if (!pageinfo.empty()) { + HumRegex hre; + if (hre.search(pageinfo, "(\\d+)")) { + printPage = hre.getMatchInt(1); + } else { + cerr << "XX PRINT PAGE: " << printPage << "\t_page: " << pageinfo << endl; + } + } else { + cerr << "YY PRINT PAGE: " << printPage << "\t_page: IS EMPTY: " << pageinfo << endl; + } + + // Calculate the scan page that matches with the print page: + int startPrintPage = kinfo.firstPrintPage; + int startScanPage = kinfo.firstScanPage; + int scanPage = calculateScanPage(printPage, startPrintPage, startScanPage, kinfo.plates); + + url += "#page=" + to_string(scanPage); + + if (!kinfo.titlePL.empty()) { + url += " @PL{Oskar Kolberg Dzieła Wszystkie " + to_string(volume) + ": " + kinfo.titlePL; + url += ", s. " + pageinfo; + url += "}"; + } + + if (!kinfo.titleEN.empty()) { + url += " @EN{Oskar Kolberg Complete Works " + to_string(volume) + ": " + kinfo.titleEN; + url += ", p"; + if (pageinfo.find("-") != string::npos) { + url += "p"; + } + url += "." + pageinfo; + url += "}"; + } + + if (kinfo.titlePL.empty() && kinfo.titleEN.empty()) { + url += " @PL{Oskar Kolberg Dzieła Wszystike " + to_string(volume); + url += " @PL{Oskar Kolberg Complete Works " + to_string(volume); + } + + return url; +} + + + +////////////////////////////// +// +// Tool_esac2hum::printKolbergPdfUrl -- +// + +void Tool_esac2hum::printKolbergPdfUrl(ostream& output) { + string source = m_score.m_params["_source"]; + HumRegex hre; + if (!hre.search(source, "^DWOK(\\d+)")) { + return; + } + int volume = hre.getMatchInt(1); + string url = getKolbergUrl(volume); + if (!url.empty()) { + output << "!!!URL-pdf: " << url << endl; + } +} + + + +////////////////////////////// +// +// Tool_esac2hum::calculateScanPage -- +// + +int Tool_esac2hum::calculateScanPage(int inputPrintPage, int printPage, int scanPage, const std::vector& platePages) { + int currentPrintPage = printPage; + int currentScanPage = scanPage; + size_t plateIndex = 0; + + // Iterate until we reach the input print page + while (currentPrintPage < inputPrintPage) { + ++currentScanPage; // Increment the scan page + + // Check if the current scan page matches the current plate page in the vector + if (plateIndex < platePages.size() && currentScanPage == platePages[plateIndex]) { + // Skip the plate page (increment scanPage but not printPage) + ++plateIndex; + } else { + // If not a plate page, increment the print page + ++currentPrintPage; + } + } + + return currentScanPage; +} + + + ///////////////////////////////// // From f79e85010724d42791bf4197257a056b79dbd7ce Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 9 Sep 2024 12:45:32 -0700 Subject: [PATCH 377/383] Implement LogRedirectStart/LogRedirectEnd in Toolkit. --- include/vrv/toolkit.h | 23 ++++++++++ src/toolkit.cpp | 100 ++++++++++++++++++++++-------------------- 2 files changed, 76 insertions(+), 47 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 8ec0376e71d..3e53fa61352 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -770,6 +770,17 @@ class Toolkit { */ void ResetLogBuffer(); + /** + * Start capturing std::cerr from an external codebase for redirection to vrv::logBuffer. + * Only one capture should be active at a given time. Finish by calling LogRedirectEnd. + */ + void LogRedirectStart(); + + /** + * End capturing std::cerr from an external codebase for redirection to vrv::logBuffer. + */ + void LogRedirectEnd(); + private: bool SetFont(const std::string &fontName); bool IsUTF16(const std::string &filename); @@ -805,6 +816,18 @@ class Toolkit { */ char *m_cString; + /** + * Temporary capture buffer for redirecting std::cerr to vrv::LogWarning. + * Used to coordinate between LogRedirectStart()/LogRedirectEnd(). + */ + std::stringstream m_captured_cerr; + + /** + * Temporary storage of the std::cerr read buffer during LogCapture. NULL when not in use. + * Used to coordinate between LogRedirectStart()/LogRedirectEnd(). + */ + std::streambuf *m_original_cerr_buf = NULL; + EditorToolkit *m_editorToolkit; #ifndef NO_RUNTIME diff --git a/src/toolkit.cpp b/src/toolkit.cpp index cf2c81513db..1387c611599 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -610,16 +610,12 @@ bool Toolkit::LoadData(const std::string &data) xmlfile.load_string(data.c_str()); stringstream conversion; - // Temporarily redirect cerr: - std::stringstream captured_cerr; - std::streambuf *cerr_buf = std::cerr.rdbuf(); - std::cerr.rdbuf(captured_cerr.rdbuf()); - + LogRedirectStart(); bool status = converter.convert(conversion, xmlfile); - LogWarning(captured_cerr.str().c_str()); - - // Restore cerr: - std::cerr.rdbuf(cerr_buf); + LogRedirectEnd(); + if (!status) { + LogWarning("Problem converting MusicXML to Humdrum (see warning above this line for possible reasons"); + } if (!status) { LogError("Error converting MusicXML data"); @@ -669,16 +665,12 @@ bool Toolkit::LoadData(const std::string &data) hum::Tool_musedata2hum converter; stringstream conversion; - // Temporarily redirect cerr: - std::stringstream captured_cerr; - std::streambuf *cerr_buf = std::cerr.rdbuf(); - std::cerr.rdbuf(captured_cerr.rdbuf()); - + LogRedirectStart(); bool status = converter.convertString(conversion, data); - LogWarning(captured_cerr.str().c_str()); - - // Restore cerr: - std::cerr.rdbuf(cerr_buf); + LogRedirectEnd(); + if (!status) { + LogWarning("Problem converting MuseData to Humdrum (see warning above this line for possible reasons"); + } if (!status) { LogError("Error converting MuseData data"); @@ -706,18 +698,14 @@ bool Toolkit::LoadData(const std::string &data) else if (inputFormat == ESAC) { // This is the indirect converter from EsAC to MEI using iohumdrum: hum::Tool_esac2hum converter; + std::stringstream conversion; - // Temporarily redirect cerr: - std::stringstream captured_cerr; - std::streambuf *cerr_buf = std::cerr.rdbuf(); - std::cerr.rdbuf(captured_cerr.rdbuf()); - stringstream conversion; - + LogRedirectStart(); bool status = converter.convert(conversion, data); - LogWarning(captured_cerr.str().c_str()); - - // Restore cerr: - std::cerr.rdbuf(cerr_buf); + LogRedirectEnd(); + if (!status) { + LogWarning("Problem converting EsAC to Humdrum (see warning above this line for possible reasons"); + } if (!status) { LogError("Error converting EsAC data"); @@ -1481,6 +1469,35 @@ void Toolkit::ResetLogBuffer() logBuffer.clear(); } +void Toolkit::LogRedirectStart() +{ + if (m_original_cerr_buf) { + vrv::LogError("In Toolkit::LogRedirectStart: Only one log redirect can be active at a time."); + return; + } + if (!m_captured_cerr.str().empty()) { + vrv::LogWarning("In Toolkit::LogRedirectStart: Log capture buffer not empty, sending current contents to " + "LogWarning and resetting."); + vrv::LogWarning(m_captured_cerr.str().c_str()); + m_captured_cerr.str(""); + } + m_original_cerr_buf = std::cerr.rdbuf(); + std::cerr.rdbuf(m_captured_cerr.rdbuf()); +} + +void Toolkit::LogRedirectEnd() +{ + if (!m_captured_cerr.str().empty()) { + vrv::LogWarning(m_captured_cerr.str().c_str()); + m_captured_cerr.str(""); + } + + if (m_original_cerr_buf) { + std::cerr.rdbuf(m_original_cerr_buf); + m_original_cerr_buf = NULL; + } +} + void Toolkit::RedoLayout(const std::string &jsonOptions) { bool resetCache = true; @@ -2074,16 +2091,12 @@ const char *Toolkit::GetHumdrumBuffer() stringstream out; hum::Tool_mei2hum converter; - // Temporarily redirect cerr: - std::stringstream captured_cerr; - std::streambuf *cerr_buf = std::cerr.rdbuf(); - std::cerr.rdbuf(captured_cerr.rdbuf()); - - converter.convert(out, infile); - LogWarning(captured_cerr.str().c_str()); - - // Restore cerr: - std::cerr.rdbuf(cerr_buf); + LogRedirectStart(); + bool status = converter.convert(out, infile); + LogRedirectEnd(); + if (!status) { + LogWarning("Problem converting MEI to Humdrum (see warning above this line for possible reasons"); + } this->SetHumdrumBuffer(out.str().c_str()); #endif @@ -2146,16 +2159,9 @@ std::string Toolkit::ConvertMEIToHumdrum(const std::string &meiData) xmlfile.load_string(meiData.c_str()); std::stringstream conversion; - // Temporarily redirect cerr: - std::stringstream captured_cerr; - std::streambuf *cerr_buf = std::cerr.rdbuf(); - std::cerr.rdbuf(captured_cerr.rdbuf()); - + LogRedirectStart(); bool status = converter.convert(conversion, xmlfile); - LogWarning(captured_cerr.str().c_str()); - - // Restore cerr: - std::cerr.rdbuf(cerr_buf); + LogRedirectEnd(); if (!status) { LogError("Error converting MEI data to Humdrum: %s", conversion.str().c_str()); From 73f8773025a82f0ba9adb4aa96cd118223c9337e Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Mon, 9 Sep 2024 23:38:04 -0700 Subject: [PATCH 378/383] Rename LogRedirectEnd() to LogRedirectStop(). --- include/vrv/toolkit.h | 8 ++++---- src/toolkit.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 3e53fa61352..3fb40f7c703 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -772,14 +772,14 @@ class Toolkit { /** * Start capturing std::cerr from an external codebase for redirection to vrv::logBuffer. - * Only one capture should be active at a given time. Finish by calling LogRedirectEnd. + * Only one capture should be active at a given time. Finish by calling LogRedirectStop. */ void LogRedirectStart(); /** * End capturing std::cerr from an external codebase for redirection to vrv::logBuffer. */ - void LogRedirectEnd(); + void LogRedirectStop(); private: bool SetFont(const std::string &fontName); @@ -818,13 +818,13 @@ class Toolkit { /** * Temporary capture buffer for redirecting std::cerr to vrv::LogWarning. - * Used to coordinate between LogRedirectStart()/LogRedirectEnd(). + * Used to coordinate between LogRedirectStart()/LogRedirectStop(). */ std::stringstream m_captured_cerr; /** * Temporary storage of the std::cerr read buffer during LogCapture. NULL when not in use. - * Used to coordinate between LogRedirectStart()/LogRedirectEnd(). + * Used to coordinate between LogRedirectStart()/LogRedirectStop(). */ std::streambuf *m_original_cerr_buf = NULL; diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 1387c611599..3deffe9607b 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -612,7 +612,7 @@ bool Toolkit::LoadData(const std::string &data) LogRedirectStart(); bool status = converter.convert(conversion, xmlfile); - LogRedirectEnd(); + LogRedirectStop(); if (!status) { LogWarning("Problem converting MusicXML to Humdrum (see warning above this line for possible reasons"); } @@ -667,7 +667,7 @@ bool Toolkit::LoadData(const std::string &data) LogRedirectStart(); bool status = converter.convertString(conversion, data); - LogRedirectEnd(); + LogRedirectStop(); if (!status) { LogWarning("Problem converting MuseData to Humdrum (see warning above this line for possible reasons"); } @@ -702,7 +702,7 @@ bool Toolkit::LoadData(const std::string &data) LogRedirectStart(); bool status = converter.convert(conversion, data); - LogRedirectEnd(); + LogRedirectStop(); if (!status) { LogWarning("Problem converting EsAC to Humdrum (see warning above this line for possible reasons"); } @@ -1485,7 +1485,7 @@ void Toolkit::LogRedirectStart() std::cerr.rdbuf(m_captured_cerr.rdbuf()); } -void Toolkit::LogRedirectEnd() +void Toolkit::LogRedirectStop() { if (!m_captured_cerr.str().empty()) { vrv::LogWarning(m_captured_cerr.str().c_str()); @@ -2093,7 +2093,7 @@ const char *Toolkit::GetHumdrumBuffer() LogRedirectStart(); bool status = converter.convert(out, infile); - LogRedirectEnd(); + LogRedirectStop(); if (!status) { LogWarning("Problem converting MEI to Humdrum (see warning above this line for possible reasons"); } @@ -2161,7 +2161,7 @@ std::string Toolkit::ConvertMEIToHumdrum(const std::string &meiData) LogRedirectStart(); bool status = converter.convert(conversion, xmlfile); - LogRedirectEnd(); + LogRedirectStop(); if (!status) { LogError("Error converting MEI data to Humdrum: %s", conversion.str().c_str()); From bf6b69710c6f6022faefd4e60a52be1f690371eb Mon Sep 17 00:00:00 2001 From: Craig Stuart Sapp Date: Tue, 10 Sep 2024 00:21:18 -0700 Subject: [PATCH 379/383] Initialize member variable in contructor. --- include/vrv/toolkit.h | 2 +- src/toolkit.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index 836d999febf..a537a3c2b0d 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -831,7 +831,7 @@ class Toolkit { * Temporary storage of the std::cerr read buffer during LogCapture. NULL when not in use. * Used to coordinate between LogRedirectStart()/LogRedirectStop(). */ - std::streambuf *m_original_cerr_buf = NULL; + std::streambuf *m_original_cerr_buf; EditorToolkit *m_editorToolkit; diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 427b849f752..6a1df8d030f 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -70,6 +70,8 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; + m_original_cerr_buf = NULL; + if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); resources.InitFonts(); From 80fcb824ad640d4e0317e8ad061a14815f75e307 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 10 Sep 2024 10:36:35 +0200 Subject: [PATCH 380/383] Rename LogElapsedTimeEnd --- include/vrv/vrv.h | 2 +- src/vrv.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/vrv/vrv.h b/include/vrv/vrv.h index 866458b9e70..daec31198e2 100644 --- a/include/vrv/vrv.h +++ b/include/vrv/vrv.h @@ -145,7 +145,7 @@ extern bool loggingToBuffer; */ extern struct timeval start; void LogElapsedTimeStart(); -void LogElapsedTimeEnd(const char *msg = "unspecified operation"); +void LogElapsedTimeStop(const char *msg = "unspecified operation"); //---------------------------------------------------------------------------- // Notation type checks diff --git a/src/vrv.cpp b/src/vrv.cpp index 8e4353b086c..3280b011de6 100644 --- a/src/vrv.cpp +++ b/src/vrv.cpp @@ -76,7 +76,7 @@ void LogElapsedTimeStart() gettimeofday(&start, NULL); } -void LogElapsedTimeEnd(const char *msg) +void LogElapsedTimeStop[(const char *msg) { double elapsedTime; struct timeval end; From 6b7f3edb665cd1bbaf30aa4e98684c4bae7ddaac Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 10 Sep 2024 10:39:33 +0200 Subject: [PATCH 381/383] Fix function typo --- src/vrv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vrv.cpp b/src/vrv.cpp index 3280b011de6..764e29370bf 100644 --- a/src/vrv.cpp +++ b/src/vrv.cpp @@ -76,7 +76,7 @@ void LogElapsedTimeStart() gettimeofday(&start, NULL); } -void LogElapsedTimeStop[(const char *msg) +void LogElapsedTimeStop(const char *msg) { double elapsedTime; struct timeval end; From f58643558dca7569e2930199aab4259726774635 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 10 Sep 2024 10:39:57 +0200 Subject: [PATCH 382/383] Rename members [skip-ci] --- include/vrv/toolkit.h | 4 ++-- src/toolkit.cpp | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/vrv/toolkit.h b/include/vrv/toolkit.h index a537a3c2b0d..61bea74b805 100644 --- a/include/vrv/toolkit.h +++ b/include/vrv/toolkit.h @@ -825,13 +825,13 @@ class Toolkit { * Temporary capture buffer for redirecting std::cerr to vrv::LogWarning. * Used to coordinate between LogRedirectStart()/LogRedirectStop(). */ - std::stringstream m_captured_cerr; + std::stringstream m_cerrCaptured; /** * Temporary storage of the std::cerr read buffer during LogCapture. NULL when not in use. * Used to coordinate between LogRedirectStart()/LogRedirectStop(). */ - std::streambuf *m_original_cerr_buf; + std::streambuf *m_cerrOriginalBuf; EditorToolkit *m_editorToolkit; diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 6a1df8d030f..a81a8c61034 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -70,7 +70,7 @@ Toolkit::Toolkit(bool initFont) m_humdrumBuffer = NULL; m_cString = NULL; - m_original_cerr_buf = NULL; + m_cerrOriginalBuf = NULL; if (initFont) { Resources &resources = m_doc.GetResourcesForModification(); @@ -1486,30 +1486,30 @@ void Toolkit::ResetLogBuffer() void Toolkit::LogRedirectStart() { - if (m_original_cerr_buf) { + if (m_cerrOriginalBuf) { vrv::LogError("In Toolkit::LogRedirectStart: Only one log redirect can be active at a time."); return; } - if (!m_captured_cerr.str().empty()) { + if (!m_cerrCaptured.str().empty()) { vrv::LogWarning("In Toolkit::LogRedirectStart: Log capture buffer not empty, sending current contents to " "LogWarning and resetting."); - vrv::LogWarning(m_captured_cerr.str().c_str()); - m_captured_cerr.str(""); + vrv::LogWarning(m_cerrCaptured.str().c_str()); + m_cerrCaptured.str(""); } - m_original_cerr_buf = std::cerr.rdbuf(); - std::cerr.rdbuf(m_captured_cerr.rdbuf()); + m_cerrOriginalBuf = std::cerr.rdbuf(); + std::cerr.rdbuf(m_cerrCaptured.rdbuf()); } void Toolkit::LogRedirectStop() { - if (!m_captured_cerr.str().empty()) { - vrv::LogWarning(m_captured_cerr.str().c_str()); - m_captured_cerr.str(""); + if (!m_cerrCaptured.str().empty()) { + vrv::LogWarning(m_cerrCaptured.str().c_str()); + m_cerrCaptured.str(""); } - if (m_original_cerr_buf) { - std::cerr.rdbuf(m_original_cerr_buf); - m_original_cerr_buf = NULL; + if (m_cerrOriginalBuf) { + std::cerr.rdbuf(m_cerrOriginalBuf); + m_cerrOriginalBuf = NULL; } } From 243728d201175cc849db64569257077486050e37 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Tue, 10 Sep 2024 10:42:21 +0200 Subject: [PATCH 383/383] Update changelog [skip-ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 691b852e5ec..a40484c6bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Support for `fTrem@unitdur` (@eNote-GmbH) * Upgrade to C++20 * Update of the Midifile library +* Improved logging * Fix lyric position in MIDI output * Fix string formatting output with some locale configurations (@ammatwain)