diff --git a/include/hum/humlib.h b/include/hum/humlib.h index 5dfe597853f..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: Thu Aug 8 21:55:11 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 @@ -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,201 @@ 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); + void printPdfUrl (std::ostream& output); + std::string getKolbergUrl (int volume); + void printKolbergPdfUrl (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; + + bool m_initialized = false; + int m_minrhy = 0; + + Tool_esac2hum::Score m_score; + + 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); + + +}; + + #define ND_NOTE 0 /* notes or rests + text and phrase markings */ #define ND_BAR 1 /* explicit barlines */ @@ -7387,10 +7583,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 +7634,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 +11137,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/include/vrv/toolkit.h b/include/vrv/toolkit.h index 66bc2b4595e..a537a3c2b0d 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 LogRedirectStop. + */ + void LogRedirectStart(); + + /** + * End capturing std::cerr from an external codebase for redirection to vrv::logBuffer. + */ + void LogRedirectStop(); + /** * Load a string data with or without resetting the log buffer */ @@ -810,6 +821,18 @@ class Toolkit { */ char *m_cString; + /** + * Temporary capture buffer for redirecting std::cerr to vrv::LogWarning. + * 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()/LogRedirectStop(). + */ + std::streambuf *m_original_cerr_buf; + EditorToolkit *m_editorToolkit; #ifndef NO_RUNTIME diff --git a/src/hum/humlib.cpp b/src/hum/humlib.cpp index 1b5e14d1d75..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: Thu Aug 8 21:55:11 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 @@ -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); @@ -55539,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; } @@ -56367,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; } } @@ -66283,13 +66327,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 +66734,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 +69015,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 +72959,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 +78242,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 +78257,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 +78280,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 +78338,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 +78468,1247 @@ 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) { + string error = "ERROR: Cannot find time signature(s) in KEY[] field: "; + error += m_params["KEY"]; + m_errors.push_back(error); + 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-c-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()) { + 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]); + 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 +79717,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,1064 +79837,1420 @@ 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 << "{"; + + m_ticks = 1 << m_underscores; + if (m_dots > 0) { + m_ticks = m_ticks * (2.0 - 1.0/(1 << m_dots)); } - if (data.slstart == 1) { - out << "("; + + if (b > 2) { + cerr << "!! ERROR: more than double flat not parseable, note: " << esac << endl; } - if (data.tiestart == 1) { - out << "["; + if (s > 2) { + cerr << "!! ERROR: more than double sharp not parseable, note: " << esac << endl; } - out << Convert::durationFloatToRecip(data.duration); - if (data.pitch < 0) { - out << "r"; - } else { - out << Convert::base40ToKern(data.pitch); + + 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.tiecont == 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.tieend == 1) { - out << "]"; + + string source = m_score.m_params["_source"]; + output << "!!!source:"; + if (!source.empty()) { + output << " " << source; } - if (data.slend == 1) { - out << ")"; + output << endl; + + string id = m_score.m_params["_id"]; + output << "!!!id:"; + if (!id.empty()) { + output << " " << id; } - if (data.phend == 1) { - out << "}"; + output << endl; + + string signature = m_score.m_params["SIG"]; + output << "!!!signature:"; + if (!signature.empty()) { + output << " " << signature; } + 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 = "%*"; + output << "**kern" << endl; +} + + + +////////////////////////////// +// +// 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 { - data.text = "|*"; + output += source + "-"; } - } - if (data.text == "^") { - data.text = "|^"; - } - printString(data.text, out); - if (data.phend == 1) { - out << "}"; + } else { + output += source + "-"; } } + output += sig; + if (!(sig.empty() || title.empty())) { + output += "-"; + } + output += title; + if (output.empty()) { + output = "file"; + } + output += m_filePostfix; - out << "\n"; + return output; +} - // print barline information - if (data.bar == 1) { - out << "="; - if (data.barnum > 0) { - out << data.barnum; + +////////////////////////////// +// +// Tool_esac2hum::getParameters -- +// + +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 (data.barinterp) { - // out << "yy"; + if ((i == 0) && hre.search(infile[i], "^([A-Z_a-z][^\\]\\[]*)\\s*$")) { + m_score.m_params["_source"] = hre.getMatch(1); + continue; } - if (debugQ) { - if (data.bardur > 0.0) { - out << "[" << data.bardur << "]"; + 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 { + 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; + } + } else { + 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); + + // 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; + 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; } - 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 << "]"; + } + + // 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"] = ""; + } + + string key = m_score.m_params["KEY"]; + 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); + 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; + } - out << "\n"; - } else if (data.bar == 2) { - out << "=="; - if (textQ) { - out << "\t=="; - } - out << "\n"; + 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, "\\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) { + printParameters(); + } + + 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; } + } ////////////////////////////// // -// Tool_esac2hum::getKeyInfo -- look for a KEY[] entry and extract the data. +// Tool_esac2hum::printParameters -- // -// ggg fix this function + +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::printBemComment -- // -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; - } +void Tool_esac2hum::printBemComment(ostream& output) { + string bem = m_score.m_params["BEM"]; + if (bem.empty()) { + return; + } + output << "!!!ONB: " << bem << 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] = '-'; - } else { - tonicstr[1] = song[i][15]; - } - tonicstr[2] = '\0'; - } else { - tonicstr = song[i][15]; - } - // 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"; - } +////////////////////////////// +// +// Tool_esac2hum::printFooter -- +// - 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; +void Tool_esac2hum::printFooter(ostream& output, vector& infile) { + output << "*-" << endl; + + printBemComment(output); + printPdfLinks(output); + printPageNumbers(output); + printConversionDate(output); + + + 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; + } + + if (!m_globalComments.empty()) { + for (int i=0; i<(int)m_globalComments.size(); i++) { + output << m_globalComments.at(i) << endl; } } - cerr << "Error: did not find a KEY field" << endl; - return false; } /////////////////////////////// // -// Tool_esac2hum::getFileContents -- read a file into the array. +// Tool_esac2hum::printPageNumbers -- // -bool Tool_esac2hum::getFileContents(vector& array, const string& filename) { - ifstream infile(filename.c_str()); - array.reserve(100); - array.resize(0); - - if (!infile.is_open()) { - cerr << "Error: cannot open file: " << filename << endl; - return false; +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+)", "im")) { + output << "!!!page: " << hre.getMatch(1) << "-" << hre.getMatch(2) << endl; + } else if (hre.search(trd, "\\bs\\.\\s*(\\d+)", "im")) { + output << "!!!page: " << hre.getMatch(1) << 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_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_esac2hum::example -- +// Tool_esac2hum::printPdfLinks -- // -void Tool_esac2hum::example(void) { +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; + printKolbergPdfUrl(output); } -////////////////////////////// +/////////////////////////////// // -// Tool_esac2hum::usage -- +// Tool_esac2hum::printCoversionDate -- // -void Tool_esac2hum::usage(const string& command) { +void Tool_esac2hum::printConversionDate(ostream& output) { + std::time_t t = std::time(nullptr); + std::tm* now = std::localtime(&t); + output << "!!!ONB: Converted on "; + output << std::put_time(now, "%Y/%m/%d"); + output << " with esac2hum" << endl; +} + + + +////////////////////////////// +// +// Tool_esac2hum::Score::doAnalyses -- +// +void Tool_esac2hum::Score::doAnalyses(void) { + analyzeMEL_SEM(); + analyzeMEL_RAW(); + analyzeNO_REP(); + analyzeRTM(); + analyzeSCL_DEG(); + analyzeSCL_SEM(); + analyzePHR_NO(); + analyzePHR_BARS(); + analyzePHR_CAD(); + analyzeACC(); } ////////////////////////////// // -// Tool_esac2hum::printBibInfo -- +// Tool_esac2hum::Score::analyzeMEL_SEM -- Current +// algorithm: calculate intervals across rest, ignore tied notes. +// values are differences between m_b12 of notes. // -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; +void Tool_esac2hum::Score::analyzeMEL_SEM(void) { + vector notelist; + getNoteList(notelist); - for (i=0; i<(int)song.size(); i++) { - if (song[i] == "") { + vector b12s; // list of notes to calculate intervals between + + for (int i=0; i<(int)notelist.size(); i++) { + if (notelist[i]->isRest()) { 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 (notelist[i]->m_tieEnd) { + continue; + } + b12s.push_back(notelist[i]->m_b12); + } - // don't print CUT field if only one line. !!!OTL: will contain CUT[] - // if (strcmp(buffer, "CUT") == 0 && start == stop) continue; + 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 += " "; + } + } - buffer[0] = tolower(buffer[0]); - buffer[1] = tolower(buffer[1]); - buffer[2] = tolower(buffer[2]); + m_params["MEL_SEM"] = output; +} - 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"; - } - } 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"; - } - } + +////////////////////////////// +// +// 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. +// + +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_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_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; } + + HumRegex hre; + hre.replaceDestructive(output, "\n!!", "\n", "g"); + + m_params["NO_REP"] = output; } ////////////////////////////// // -// Tool_esac2hum::printString -- print characters in string. +// 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. // -void Tool_esac2hum::printString(const string& string, ostream& out) { - for (int i=0; i<(int)string.size(); i++) { - printChar(string[i], out); +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; + } } + return output; } +////////////////////////////// +// +// 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_extract::Tool_extract -- Set the recognized options for the tool. +// Tool_esac2hum::Score::analyzeSCL_DEG -- List of scale degrees +// present in melody from lowest to highest with no spaces between +// the scale degrees. // -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::Score::analyzeSCL_DEG(void) { + vector notelist; + getNoteList(notelist); + map list; + for (int i=0; i<(int)notelist.size(); i++) { + if (notelist[i]->isRest()) { + continue; + } + if (notelist[i]->m_tieEnd) { + continue; + } + int b40 = notelist[i]->m_b40; + list[b40] = notelist[i]; + } - 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"); + string output; + for (const auto& pair : list) { + output += pair.second->getScaleDegree(); + } + m_params["SCL_DEG"] = output; } -///////////////////////////////// +////////////////////////////// // -// Tool_extract::run -- Primary interfaces to the tool. +// Tool_esac2hum::Score::analyzeSCL_SEM -- Get the semitone +// between scale degrees in SCL_DEG analysis. // -bool Tool_extract::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i notelist; + getNoteList(notelist); + map list; + for (int i=0; i<(int)notelist.size(); i++) { + if (notelist[i]->isRest()) { + continue; + } + if (notelist[i]->m_tieEnd) { + continue; + } + int b40 = notelist[i]->m_b40; + list[b40] = notelist[i]; } - return status; -} - -bool Tool_extract::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + 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; } - return status; + m_params["SCL_SEM"] = output; } -bool Tool_extract::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; + +////////////////////////////// +// +// Tool_esac2hum::Score::analyzePHR_NO -- +// + +void Tool_esac2hum::Score::analyzePHR_NO(void) { + int phraseCount = (int)size(); + m_params["PHR_NO"] = to_string(phraseCount); } + + +////////////////////////////// // -// In-place processing of file: +// Tool_esac2hum::Score::analyzePHR_BARS -- Return the number +// of measures in each phrase. // -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::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::processFile -- +// 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::processFile(HumdrumFile& infile) { - if (countQ) { - m_free_text << infile.getMaxTrack() << endl; - return; +int Tool_esac2hum::Phrase::getFullMeasureCount(void) { + int measureCount = (int)size(); + if (measureCount < 2) { + return measureCount; } - 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); + if (at(0).isPartialEnd() && back().isPartialBegin()) { + measureCount--; } - 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 << ","; - } - } - m_free_text << endl; - return; + // if the fist is partial and the last is not, also -1 + if (at(0).isPartialEnd() && back().isComplete()) { + measureCount--; } - 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]; - } - } - m_free_text << endl; + // if the fist is complete and the last is incomplete, also -1 + if (at(0).isComplete() && back().isPartialBegin()) { + measureCount--; } - // 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; - } + // what to do if first measure is pickup (and maybe last measure)? + return measureCount; } ////////////////////////////// // -// Tool_extract::getNullDataTracks -- +// Tool_esac2hum::Score::analyzePHR_CAD -- Give a space-delimited +// list of the last scale degree of each phrase. // -vector 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; - } +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 += " "; } - // maybe exit here if all tracks are non-null } - - return output; + m_params["PHR_CAD"] = output; } ////////////////////////////// // -// Tool_extract::fillFieldDataByEmpty -- Only keep the spines which contain only -// null data tokens. +// Tool_esac2hum::Phrase::getLastScaleDegree -- // -void Tool_extract::fillFieldDataByEmpty(vector& field, vector& subfield, - vector& model, HumdrumFile& infile, int negate) { - - 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); +string Tool_esac2hum::Phrase::getLastScaleDegree(void) { + vector notelist; + getNoteList(notelist); - 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); - } + for (int i=(int)notelist.size() - 1; i>=0; i--) { + if (notelist[i]->isPitch()) { + return notelist[i]->getScaleDegree(); } } + return "?"; } - ////////////////////////////// // -// Tool_extract::fillFieldDataByNoEmpty -- Only keep spines which are not all -// null data tokens. +// Tool_esac2hum::Note::getScaleDegree -- return the scale degree +// string for the note, such as: 6, -6, +7b, 5#. // -void Tool_extract::fillFieldDataByNoEmpty(vector& field, vector& subfield, - vector& model, HumdrumFile& infile, int negate) { - - 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]; +string Tool_esac2hum::Note::getScaleDegree(void) { + string output; + if (m_octave < 0) { + for (int i=0; i<-m_octave; i++) { + output += "-"; + } + } 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) { +bool Tool_esac2hum::Note::isPitch(void) { + return (m_degree > 0); +} - 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; iisKern()) { - continue; - } - if (token->isNull()) { - continue; - } - if (token->isRest()) { - continue; - } - track = token->getTrack(); - tracks[track] = 1; - } - } +bool Tool_esac2hum::Note::isRest(void) { + return (m_degree <= 0); +} - // 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; - } - } - } - } - // 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::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. +// - // 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; +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(); } - track = sstarts[j]->getTrack(); - tracks[track] = 0; } - } - - 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); + if (i < (int)size() -1) { + output += " "; } } - + m_params["ACC"] = output; +} + + + +////////////////////////////// +// +// 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_extract::fillFieldDataByGrep -- +// Tool_esac::getKolbergUrl -- // -void Tool_extract::fillFieldDataByGrep(vector& field, vector& subfield, - vector& model, const string& searchstring, HumdrumFile& infile, - int state) { +string Tool_esac2hum::getKolbergUrl(int volume) { + if ((volume < 1) || (volume > 84)) { + // Such a volume does not exist, return empty string. + return ""; + } - field.reserve(infile.getMaxTrack()+1); - subfield.reserve(infile.getMaxTrack()+1); - model.reserve(infile.getMaxTrack()+1); - field.resize(0); - subfield.resize(0); - model.resize(0); + 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"; - vector tracks; - tracks.resize(infile.getMaxTrack()+1); - fill(tracks.begin(), tracks.end(), 0); - HumRegex hre; - int track; + 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; + } - int i, j; - for (i=0; igetTrack(); - tracks[track] = 1; - } + 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; } - 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); + // 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_extract::getInterpretationFields -- +// Tool_esac2hum::printKolbergPdfUrl -- // -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; - +void Tool_esac2hum::printKolbergPdfUrl(ostream& output) { + string source = m_score.m_params["_source"]; HumRegex hre; - hre.replaceDestructive(buffer, "", "\\s+", "g"); - - int start = 0; - while (hre.search(buffer, start, "^([^,]+)")) { - sstrings.push_back(hre.getMatch(1)); - start = hre.getMatchEndIndex(1); + if (!hre.search(source, "^DWOK(\\d+)")) { + return; } - - 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; - } + int volume = hre.getMatchInt(1); + string url = getKolbergUrl(volume); + if (!url.empty()) { + output << "!!!URL-pdf: " << url << endl; } +} - vector tracks; - tracks.resize(infile.getMaxTrack()+1); - fill(tracks.begin(), tracks.end(), 0); - // Algorithm below could be made more efficient by - // not searching the entire file... - for (i=0; igetTrack()] = 1; - } - } - } - } - field.reserve(tracks.size()); - subfield.reserve(tracks.size()); - model.reserve(tracks.size()); +////////////////////////////// +// +// Tool_esac2hum::calculateScanPage -- +// - field.resize(0); - subfield.resize(0); - model.resize(0); +int Tool_esac2hum::calculateScanPage(int inputPrintPage, int printPage, int scanPage, const std::vector& platePages) { + int currentPrintPage = printPage; + int currentScanPage = scanPage; + size_t plateIndex = 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); + // 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; } -////////////////////////////// + +///////////////////////////////// // -// Tool_extract::expandSpines -- +// Tool_esac2humold::Tool_esac2humold -- Set the recognized options for the tool. // -void Tool_extract::expandSpines(vector& field, vector& subfield, vector& model, - HumdrumFile& infile, string& interp) { +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"); +} - 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; - } - } +////////////////////////////// +// +// Tool_esac2humold::convert -- Convert a MusicXML file into +// Humdrum content. +// + +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()); +} - field.reserve(infile.getMaxTrack()*2); - field.resize(0); - subfield.reserve(infile.getMaxTrack()*2); - subfield.resize(0); +bool Tool_esac2humold::convert(ostream& out, istream& input) { + convertEsacToHumdrum(out, input); + return true; +} - model.reserve(infile.getMaxTrack()*2); - model.resize(0); - bool allQ = interp.empty(); +bool Tool_esac2humold::convert(ostream& out, const string& input) { + stringstream ss; + ss << input; + convertEsacToHumdrum(out, ss); + return true; +} - vector dummyfield; - vector dummysubfield; - vector dummymodel; - getInterpretationFields(dummyfield, dummysubfield, model, infile, interp, 1); - vector interptracks; - interptracks.resize(infile.getMaxTrack()+1); - fill(interptracks.begin(), interptracks.end(), 0); - for (i=0; i<(int)dummyfield.size(); i++) { - interptracks[dummyfield[i]] = 1; +////////////////////////////// +// +// Tool_esac2humold::initialize -- +// + +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; } - 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); + debugQ = getBoolean("debug"); + verboseQ = getBoolean("verbose"); + + if (getBoolean("header")) { + if (!getFileContents(header, getString("header"))) { + return false; } + } else { + header.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 << ","; - } + if (getBoolean("trailer")) { + if (!getFileContents(trailer, getString("trailer"))) { + return false; } - m_humdrum_text << endl; + } else { + trailer.resize(0); + } + + 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(); +void Tool_esac2humold::chopExtraInfo(string& buffer) { + HumRegex hre; + hre.replaceDestructive(buffer, "", "^\\s+"); + hre.replaceDestructive(buffer, "", "\\s+$"); +} - field.reserve(maxtrack); - field.resize(0); - subfield.reserve(maxtrack); - subfield.resize(0); - model.reserve(maxtrack); - model.resize(0); +////////////////////////////// +// +// Tool_esac2humold::printHumdrumHeaderInfo -- +// - 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()); +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; } } @@ -80565,605 +81258,440 @@ void Tool_extract::fillFieldData(vector& field, vector& subfield, ////////////////////////////// // -// 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::printHumdrumFooterInfo -- // -void Tool_extract::processFieldEntry(vector& field, - vector& subfield, vector& model, const string& astring, - HumdrumFile& infile) { - - int finitsize = (int)field.size(); - int maxtrack = infile.getMaxTrack(); +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 (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"; + } + } +} - vector ktracks; - infile.getKernSpineStartList(ktracks); - int maxkerntrack = (int)ktracks.size(); - int modletter; - int subletter; - HumRegex hre; - string buffer = astring; +////////////////////////////// +// +// Tool_esac2humold::convertSong -- +// - // remove any comma left at end of input astring (or anywhere else) - hre.replaceDestructive(buffer, "", ",", "g"); +void Tool_esac2humold::convertSong(vector& song, ostream& out) { - // first remove $ symbols and replace with the correct values - if (kernQ) { - removeDollarsFromString(buffer, maxkerntrack); - } else { - removeDollarsFromString(buffer, maxtrack); + int i; + if (verboseQ) { + for (i=0; i<(int)song.size(); i++) { + out << song[i] << "\n"; + } } - int zero = 0; - if (hre.search(buffer, "^(\\d+)-(\\d+)$")) { - int firstone = hre.getMatchInt(1); - int lastone = hre.getMatchInt(2); + printHumdrumHeaderInfo(out, song); - 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 key; + double mindur = 1.0; + string meter; + int tonic = 0; + getKeyInfo(song, key, mindur, tonic, meter, out); - 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'; - } + vector songdata; + songdata.resize(0); + songdata.reserve(1000); + getNoteList(song, songdata, mindur, tonic); + placeLyrics(song, songdata); - 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); + vector numerator; + vector denominator; + getMeterInfo(meter, numerator, denominator); + + postProcessSongData(songdata, numerator, denominator); + + 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; } } - if (!kernQ) { - return; + for (i=0; i<(int)header.size(); i++) { + out << header[i] << "\n"; } - // Insert fields to next **kern spine. - vector newfield; - vector newsubfield; - vector newmodel; - - vector trackstarts; - infile.getTrackStartList(trackstarts); - int spine; - - // 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; - } + out << "**kern"; + if (textQ) { + out << "\t**text"; } + out << "\n"; - 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]); + 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"; - // search for non **kern spines after specified **kern spine: - startspineindex = field[i] + 1 - 1; - stopspineindex = maxtrack; - for (int j=startspineindex; jisKern()) { - break; + 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 << ", "; } - newfield.push_back(j+1); - newsubfield.push_back(zero); - newmodel.push_back(zero); } + out << "]"; } + out << "\n"; - field = newfield; - subfield = newsubfield; - model = newmodel; -} - - - -////////////////////////////// -// -// Tool_extract::removeDollarsFromString -- substitute $ sign for maximum track count. -// - -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()) { - continue; - } - kerntok = token; - break; - } +void Tool_esac2humold::getLyrics(vector& lyrics, const string& buffer) { + lyrics.resize(0); + int zero1 = 0; + string current; + int zero2 = 0; + zero2 = zero1 + zero2; - if (kerntok == NULL) { - m_humdrum_text << "*"; - return; - } + int length = (int)buffer.size(); + int i; - if (*kerntok == "*") { - m_humdrum_text << kerntok; - return; - } + 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; + while (i < length && buffer[i] != ' ') { + current += buffer[i++]; + } + lyrics.push_back(current); + i++; } - 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 +81699,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"; + } + out << "\n"; + } else { + out << "*clefF4"; + if (textQ) { + out << "\t*clefF4"; } + out << "\n"; } - 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++; + // 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; } - i++; } - 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]]; - } + 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; } - 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); - - 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; - } - 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; - } + // number the barline + firstdur = dur; + barnum = 1; + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].bar == 1) { + songdata[i].barnum = barnum++; } } - } - // remove single *v spines: + } else { + // handle multiple time signatures - for (i=0; i<(int)vsplit.size()-1; i++) { - if (vsplit[i] && vsplit[i+1]) { - if (tempout[i] == "*v") { - tempout[i] = "*"; - vsplit[i] = 0; - } + // 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 (debugQ) { - m_humdrum_text << "!!vsplit array: "; - for (i=0; i<(int)vsplit.size(); i++) { - m_humdrum_text << " " << vsplit[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; + } } - m_humdrum_text << endl; - } + firstdur = dur; - 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; + // 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 - int vcount = 0; - for (i=0; i<(int)vsplit.size(); i++) { - vcount += 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; + } + } + } - if (vcount) { - printMultiLines(vsplit, vserial, tempout); - } + // perhaps sum duration of measures again and search for error here? - int start = 0; - for (i=0; i<(int)tempout.size(); i++) { - if (tempout[i] != "") { - if (start != 0) { - m_humdrum_text << "\t"; + // finally, number the barlines: + barnum = 1; + for (i=0; i<(int)numerator.size(); i++) { + if (fabs(firstdur - meterdurs[i]) < 0.001) { + barnum = 2; + break; } - m_humdrum_text << tempout[i]; - start++; } + for (i=0; i<(int)songdata.size(); i++) { + if (songdata[i].bar == 1) { + songdata[i].barnum = barnum++; + } + } + + } - if (start) { - m_humdrum_text << '\n'; - } + } ////////////////////////////// // -// Tool_extract::printMultiLines -- print separate *v lines. +// Tool_esac2humold::getMeterInfo -- // -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; - break; - } - } - - if (debugQ) { - m_humdrum_text << "!!tempout: "; - for (i=0; i<(int)tempout.size(); i++) { - m_humdrum_text << tempout[i] << " "; - } - m_humdrum_text << endl; +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 (splitpoint == -1) { + if (hre.search(meter, "^frei$", "i")) { + numerator.push_back(-1); + denominator.push_back(-1); return; } + cerr << "NEED TO DEAL WITH METER: " << meter << endl; +} - int start = 0; - int printv = 0; - for (i=0; i& 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; } - m_humdrum_text << "*"; + } else if ((start >= 0) && (loc != string::npos)) { + stop = i; + break; } } - - if (start) { - m_humdrum_text << "\n"; - } - - vsplit[splitpoint] = 0; - - printMultiLines(vsplit, vserial, tempout); } ////////////////////////////// // -// Tool_extract::storeToken -- +// Tool_esac2humold::getNoteList -- get a list of the notes and rests and barlines in +// the MEL field. // -void Tool_extract::storeToken(vector& storage, const string& string) { - storage.push_back(string); -} +bool Tool_esac2humold::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; -void storeToken(vector& storage, int index, const string& string) { - storage[index] = string; -} + getLineRange(song, "MEL", melstart, melstop); + + 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::isInList -- returns true if first number found in list of numbers. -// returns the matching index plus one. -// + // Duration information: + case '_': duration *= 2.0; state = STATE_DUR; break; + case '.': duration *= 1.5; state = STATE_DUR; break; -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; + // Accidental information: + case 'b': accidental--; state = STATE_ACC; break; + case '#': accidental++; state = STATE_ACC; break; + + // 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; + } + + 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; + + 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 numbers; - - vector kernspines = infile.getKernSpineStartList(); - - int maxTrack = infile.getMaxTrack(); - - // Do nothing if base track not withing kern track range - if (m_baseTrackQ < 1 || m_baseTrackQ > maxTrack) { - return; - } - - 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())) { +vector Tool_extract::getNullDataTracks(HumdrumFile& infile) { + vector output(infile.getMaxTrack() + 1, 1); + for (int i=0; igetTrack(); + if (!output[track]) { continue; } - int track = kernspines.at(ktracks[i] - 1)->getTrack(); - m_selectedKernSpines.at(track) = true; + if (!token->isNull()) { + output[track] = 0; + } } - } else if (!m_spineTracks.empty()) { - infile.makeBooleanTrackList(m_selectedKernSpines, m_spineTracks); + // maybe exit here if all tracks are non-null } - vector> lastNumbers = {}; - lastNumbers.resize((int)grid.getVoiceCount()); - vector> currentNumbers = {}; + return output; +} - // 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::fillFieldDataByEmpty -- Only keep the spines which contain only +// null data tokens. +// - // Handle spine splits - do { - HTp resolvedToken = currentToken->resolveNull(); - int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); +void Tool_extract::fillFieldDataByEmpty(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); - 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::fillFieldDataByNoEmpty -- Only keep spines which are not all +// null data tokens. +// +void Tool_extract::fillFieldDataByNoEmpty(vector& field, vector& subfield, + vector& model, HumdrumFile& infile, int negate) { - 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()); + 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]; + } - // Ignore if base is a rest or silent note - if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) { - if(abs(lowest) < lowestBaseNoteBase40Pitch) { - lowestBaseNoteBase40Pitch = abs(lowest); - } + 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); } - - HTp nextToken = currentToken->getNextField(); - if (nextToken && (initialTokenTrack == nextToken->getTrack())) { - currentToken = nextToken; - } else { - // Break loop if nextToken is not the same track as initialTokenTrack - break; + } else { + if (nullTrack[i]) { + field.push_back(i); + subfield.push_back(zero); + model.push_back(zero); } - } while (currentToken); - - // Ignore if base is a rest or silent note - if ((lowestBaseNoteBase40Pitch == 0) || (lowestBaseNoteBase40Pitch == -1000) || (lowestBaseNoteBase40Pitch == -2000) || (lowestBaseNoteBase40Pitch == 9999)) { - continue; } + } +} - // 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_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. +// - // Handle spine splits - do { - HTp resolvedToken = currentToken->resolveNull(); - for (int subtokenBase40: resolvedToken->getBase40Pitches()) { +void Tool_extract::fillFieldDataByNoRest(vector& field, vector& subfield, + vector& model, const string& searchstring, HumdrumFile& infile, + int state) { - // Ignore if target is a rest or silent note - if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) { - continue; - } + field.clear(); + subfield.clear(); + model.clear(); - // 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); + // 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; im_number); - chordNumbers.push_back(number); - } + } + 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; + } + } - HTp nextToken = currentToken->getNextField(); - if (nextToken && (initialTokenTrack == nextToken->getTrack())) { - currentToken = nextToken; - } else { - // Break loop if nextToken is not the same track as initialTokenTrack - break; + // 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; } - } 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(); + 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; } - 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); + model.reserve(infile.getMaxTrack()*2); + model.resize(0); -////////////////////////////// -// -// Tool_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music. -// + bool allQ = interp.empty(); -vector Tool_fb::filterFiguredBassNumbersForLine(vector numbers, int lineIndex) { + vector dummyfield; + vector dummysubfield; + vector dummymodel; + getInterpretationFields(dummyfield, dummysubfield, model, infile, interp, 1); - vector filteredNumbers; + vector interptracks; - // filter numbers with passed lineIndex - copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex](FiguredBassNumber* num) { - return num->m_lineIndex == lineIndex; - }); + interptracks.resize(infile.getMaxTrack()+1); + fill(interptracks.begin(), interptracks.end(), 0); - // sort by voiceIndex - sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool { - return a->m_voiceIndex > b->m_voiceIndex; - }); + for (i=0; i<(int)dummyfield.size(); i++) { + interptracks[dummyfield[i]] = 1; + } - return filterNegativeNumbers(filteredNumbers); + 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) { - - 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); -} - - - -////////////////////////////// -// -// Tool_fb::formatFiguredBassNumbers -- Create a **fb data record string out of the passed FiguredBassNumber objects -// - -string Tool_fb::formatFiguredBassNumbers(const vector& numbers) { +void Tool_extract::reverseSpines(vector& field, vector& subfield, + vector& model, HumdrumFile& infile, const string& exinterp) { - vector formattedNumbers; + vector target; + target.resize(infile.getMaxTrack()+1); + fill(target.begin(), target.end(), 0); - // 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; - } + vector trackstarts; + infile.getSpineStartList(trackstarts); - // 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; + for (int t=0; t<(int)trackstarts.size(); t++) { + if (trackstarts[t]->isDataType(exinterp)) { + target.at(t + 1) = 1; + } } - // Analysze before sorting - if (m_compoundQ) { - formattedNumbers = analyzeChordNumbers(formattedNumbers); - } + field.reserve(infile.getMaxTrack()*2); + field.resize(0); - // 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; - }); + 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; + } + } + } } - if (m_reduceQ) { - // Overwrite formattedNumbers with abbreviated numbers - formattedNumbers = getAbbreviatedNumbers(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; itoString(m_compoundQ, m_accidentalsQ, m_hideThreeQ); - if (num.length() > 0) { - if (!first) str += " "; - first = false; - str += num; + if (debugQ) { + m_humdrum_text << "!!reverse: "; + for (int i=0; i<(int)field.size(); i++) { + m_humdrum_text << field[i] << " "; } + m_humdrum_text << endl; } - return str; + + subfield.resize(field.size()); + fill(subfield.begin(), subfield.end(), 0); + + 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; + } + // Insert fields to next **kern spine. + vector newfield; + vector newsubfield; + vector newmodel; -////////////////////////////// -// -// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent -// TODO: Handle negative values and sustained notes -// + vector trackstarts; + infile.getTrackStartList(trackstarts); + int spine; -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); - }); + // 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; + } + } - if (filteredBase40Pitches.size() == 0) { - return -2000; + 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); + } } - return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches)); + 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; +void Tool_extract::removeDollarsFromString(string& buffer, int maxtrack) { + HumRegex hre; + char buf2[128] = {0}; + int value2; - // 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; + if (hre.search(buffer, "\\$$")) { + snprintf(buf2, 128, "%d", maxtrack); + hre.replaceDestructive(buffer, buf2, "\\$$"); + } - // 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; + 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"); + } - // 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; + if (hre.search(buffer, "\\$0")) { + // replace $0 with maxtrack (used for reverse orderings) + snprintf(buf2, 128, "%d", maxtrack); + hre.replaceDestructive(buffer, buf2, "\\$0", "g"); + } - case 20: quality = ""; break; + while (hre.search(buffer, "\\$(-?\\d+)")) { + value2 = maxtrack - abs(hre.getMatchInt(1)); + snprintf(buf2, 128, "%d", value2); + hre.replaceDestructive(buffer, buf2, "\\$-?\\d+"); + } +} - // 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; +////////////////////////////// +// +// Tool_extract::excludeFields -- print all spines except the ones in the list of fields. +// - default: quality = "?"; break; +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; + } + } } - - return quality; - } ////////////////////////////// // -// FiguredBassNumber::FiguredBassNumber -- Constructor +// Tool_extract::extractFields -- print all spines 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::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 0) { - return accid + to_string(num); - } - if (num < 0) { - return accid + "~" + to_string(abs(num)); + if (infile[i].isBarline()) { + foundBarline = true; + } + + 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); + } + } + } + } + + if (infile[i].isData()) { + foundBarline = false; + } + + if (start != 0) { + m_humdrum_text << endl; + } } - return ""; } ////////////////////////////// // -// 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 +// Tool_extract::printInterpretationForKernSpine -- +// -int FiguredBassNumber::getNumberWithinOctave(void) { - int num = m_number % 7; +void Tool_extract::printInterpretationForKernSpine(HumdrumFile& infile, int index) { + HTp kerntok = NULL; + for (int j=0; jisKern()) { + continue; + } + kerntok = token; + break; + } - // Replace 0 with 7 and -7 - if ((abs(m_number) > 0) && (m_number % 7 == 0)) { - return m_number < 0 ? -7 : 7; + if (kerntok == NULL) { + m_humdrum_text << "*"; + return; } - // 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; - } - } - return m_number < 0 ? -8 : 8; + if (*kerntok == "*") { + m_humdrum_text << kerntok; + return; } - // 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 (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; } - return num; + m_humdrum_text << "*"; } ////////////////////////////// // -// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor -// Helper class to store the mappings for abbreviate figured bass numbers +// Tool_extract::dealWithCospine -- extract the required token(s) from a co-spine. // -FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector n) { - m_str = s; - m_numbers = n; -} +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; -////////////////////////////// -// -// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers -// + if (infile[line].isInterpretation()) { + m_humdrum_text << infile.token(line, cospine); + return; + } -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}), -}; + if (infile[line].isBarline()) { + m_humdrum_text << infile.token(line, cospine); + return; + } + if (infile[line].isLocalComment()) { + m_humdrum_text << infile.token(line, cospine); + return; + } + 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; + } -#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; + vector spineindex; + vector subspineindex; -#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; + spineindex.reserve(infile.getMaxTrack()*2); + spineindex.resize(0); -#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; + subspineindex.reserve(infile.getMaxTrack()*2); + subspineindex.resize(0); -#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; + 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::Tool_filter -- Set the recognized options for the tool. -// + int start = 0; + for (i=0; i<(int)field.size(); i++) { + if (infile.token(line, field[i])->isDataType(cointerp)) { + continue; + } -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; 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); + } + } + } } -///////////////////////////////// +////////////////////////////// // -// Tool_filter::run -- Primary interfaces to the tool. +// Tool_extract::printCotokenInfo -- // -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; -} - -bool Tool_filter::runUniversal(HumdrumFileSet& infiles) { - bool status = true; - vector > commands; - getUniversalCommandList(commands, infiles); - - 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); + HumRegex hre; + string buffer; + if (infile[line].isLocalComment()) { + if ((submodel == 'n') || (submodel == 'r')) { + m_humdrum_text << "!"; } else { - cerr << "UNKNOWN FILTER: " << commands[i].first << " OPTIONS: " << commands[i].second << endl; + m_humdrum_text << infile.token(i, j); } - - } - - 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].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 { - 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); } + } else { + m_error_text << "Should not get to this line of code" << endl; + return; } } + ////////////////////////////// // -// Tool_filter::removeUniversalFilterLines -- +// Tool_extract::getSearchPat -- // -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 (token->compare(0, maintag.size(), maintag) == 0) { - text = token->getText(); - hre.replaceDestructive(text, mainXtag, maintagQuery); - token->setText(text); - infile[j].createLineFromTokens(); - } - } - } +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::getCommandList -- +// Tool_extract::dealWithSpineManipulators -- check for proper Humdrum syntax of +// spine manipulators (**, *-, *x, *v, *^) when creating the output. // -void Tool_filter::getCommandList(vector >& commands, - HumdrumFile& infile) { +void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line, + vector& field, vector& subfield, vector& model) { - 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; + 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; } - 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); - } + if (*infile.token(line, j) == "*x") { + xmanip[j] = 1; } } -} - - - -////////////////////////////// -// -// Tool_filter::splitPipeline -- -// - -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; - } + 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 (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; + 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++; } + i++; + } - 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; - } - } + vector fieldoccur; // nth occurance of an input spine in the output + fieldoccur.resize(field.size()); + fill(fieldoccur.begin(), fieldoccur.end(), 0); - if (isspace(ch) && (!(inSingleQuotes > -1)) && (!(inDoubleQuotes > -1))) { - if (isspace(lastch)) { - // don't repeat spaces outside of quotes. - continue; - } - } + vector trackcounter; // counter of input spines occurances in output + trackcounter.resize(infile.getMaxTrack()+1); + fill(trackcounter.begin(), trackcounter.end(), 0); - // regular character - clist.back() += ch; + for (i=0; i<(int)field.size(); i++) { + if (field[i] != 0) { + trackcounter[field[i]]++; + fieldoccur[i] = trackcounter[field[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 tempout; + vector vserial; + vector xserial; + vector fpos; // input column of output spine -} + tempout.reserve(1000); + tempout.resize(0); + vserial.reserve(1000); + vserial.resize(0); + xserial.reserve(1000); + xserial.resize(0); -////////////////////////////// -// -// Tool_filter::getUniversalCommandList -- -// - -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; + 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; + } } - 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); + 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; + } + } + } -////////////////////////////// -// -// Tool_fixps::processFile -- -// + int vcount = 0; + for (i=0; i<(int)vsplit.size(); i++) { + vcount += vsplit[i]; + } -void Tool_fixps::processFile(HumdrumFile& infile) { - removeDuplicateDynamics(infile); - markEmptyVoices(infile); - vector> newlist; - removeEmpties(newlist, infile); - outputNewSpining(newlist, infile); + 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++; + } + } + 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]; - } +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; } - if (infile[i].isBarline()) { - barline = &infile[i]; - } - if (!infile[i].isData()) { + if (!hre.search(buffer, "(\\d+)")) { continue; } - if (!barline) { + 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; } - // 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(); - } - } + 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"); -} - +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] << " "; + } + m_humdrum_text << "\n"; + } + } -///////////////////////////////// -// -// Tool_flipper::run -- Do the main work of the tool. -// -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; + } } ////////////////////////////// // -// Tool_flipper::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_extract::example -- example usage of the sonority program // -void Tool_flipper::initialize(void) { - m_allQ = getBoolean("all"); - m_keepQ = getBoolean("keep"); - m_kernQ = true; - m_stropheQ = getBoolean("strophe"); - m_interp = getString("interp"); - if (m_interp != "kern") { - m_kernQ = false; - } +void Tool_extract::example(void) { + m_free_text << + " \n" + << endl; } ////////////////////////////// // -// Tool_flipper::processFile -- +// Tool_extract::usage -- gives the usage statement for the sonority program // -void Tool_flipper::processFile(HumdrumFile& infile) { +void Tool_extract::usage(const string& command) { + m_free_text << + " \n" + << endl; +} - 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); + +////////////////////////////// +// +// Tool_extract::initialize -- +// + +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; } - m_strophe.resize(infile.getMaxTrack()+1); - fill(m_strophe.begin(), m_strophe.end(), false); + excludeQ = getBoolean("x"); + interpQ = getBoolean("i"); + interps = getString("i"); + kernQ = getBoolean("k"); + rkernQ = getBoolean("K"); - for (int i=0; i 0) { + if (interps[0] != '*') { + // Automatically add ** if not given on exclusive interpretation + string tstring = "**"; + interps = tstring + interps; } } - if (m_keepQ) { - m_humdrum_text << infile; - } -} + 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; + } -////////////////////////////// -// -// Tool_flipper::checkForFlipChanges -- -// + if (emptyQ) { + fieldQ = true; + } -void Tool_flipper::checkForFlipChanges(HumdrumFile& infile, int index) { - if (!infile[index].isInterpretation()) { - return; + if (noEmptyQ) { + fieldQ = true; } - int track; + if (expandQ) { + fieldQ = true; + expandInterp = getString("expand-interp"); + } - for (int i=0; igetTrack(); - m_strophe[track] = true; - } else if (*token == "*Xstrophe") { - track = token->getTrack(); - m_strophe[track] = false; + if (!reverseQ) { + reverseQ = getBoolean("R"); + if (reverseQ) { + reverseInterp = getString("R"); } } + if (reverseQ) { + fieldQ = true; + } - if (m_allQ) { - // state always stays on in this case - return; + 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()); } - 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; + spineListQ = getBoolean("spine-list"); + grepQ = getBoolean("grep"); + grepString = getString("grep"); + + 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; } } } - ////////////////////////////// // -// Tool_flipper::processLine -- +// Tool_extract::reverseFieldString -- No dollar expansion for now. // -void Tool_flipper::processLine(HumdrumFile& infile, int index) { - if (!infile[index].hasSpines()) { - return; - } - if (infile[index].isInterpretation()) { - checkForFlipChanges(infile, index); - } - - vector> flipees; - extractFlipees(flipees, infile, index); - if (!flipees.empty()) { - int status = flipSubspines(flipees); - if (status) { - infile[index].createLineFromTokens(); +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); + } + return output; } ////////////////////////////// // -// Tool_flipper::flipSubspines -- +// Tool_fb::Tool_fb -- Set the recognized options for the tool. // -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_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_flipper::flipSpineTokens -- +// Tool_fb::run -- Do the main work of the tool. // -void Tool_flipper::flipSpineTokens(vector& subtokens) { - if (subtokens.size() < 2) { - return; +bool Tool_fb::run(HumdrumFileSet &infiles) { + bool status = true; + for (int i = 0; i < infiles.getCount(); i++) { + status &= run(infiles[i]); } - 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); + return status; +} + +bool Tool_fb::run(const string &indata, ostream &out) { + HumdrumFile infile(indata); + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; + } + return status; +} + +bool Tool_fb::run(HumdrumFile &infile, ostream &out) { + bool status = run(infile); + if (hasAnyText()) { + getAllText(out); + } else { + out << infile; } + return status; +} + +bool Tool_fb::run(HumdrumFile &infile) { + initialize(); + processFile(infile); + return true; } ////////////////////////////// // -// Tool_flipper::extractFlipees -- +// Tool_fb::initialize -- // -void Tool_flipper::extractFlipees(vector>& flipees, - HumdrumFile& infile, int index) { - flipees.clear(); +void Tool_fb::initialize(void) { + m_compoundQ = getBoolean("compound"); + m_accidentalsQ = getBoolean("accidentals"); + m_baseTrackQ = getInteger("base"); + m_intervallsatzQ = getBoolean("intervallsatz"); + m_sortQ = getBoolean("sort"); + m_lowestQ = getBoolean("lowest"); + m_normalizeQ = getBoolean("normalize"); + m_reduceQ = getBoolean("reduce"); + m_attackQ = getBoolean("ties"); + m_figuredbassQ = getBoolean("figuredbass"); + m_hideThreeQ = getBoolean("hide-three"); + m_showNegativeQ = getBoolean("negative"); + m_aboveQ = getBoolean("above"); + m_rateQ = getString("rate"); + m_hintQ = getBoolean("hint"); - 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); + if (getBoolean("spine-tracks")) { + m_spineTracks = getString("spine-tracks"); + } else if (getBoolean("kern-tracks")) { + m_kernTracks = getString("kern-tracks"); + } + + if (m_normalizeQ) { + m_compoundQ = true; + m_sortQ = true; + } + + if (m_reduceQ) { + m_normalizeQ = true; + m_compoundQ = true; + m_sortQ = true; } -} + if (m_figuredbassQ) { + m_reduceQ = true; + m_normalizeQ = true; + m_compoundQ = true; + m_sortQ = true; + m_accidentalsQ = true; + m_hideThreeQ = true; + } + if (m_hintQ) { + m_showNegativeQ = true; + // m_lowestQ = true; + } +} -///////////////////////////////// +////////////////////////////// // -// Tool_gasparize::Tool_gasparize -- Set the recognized options for the tool. +// Tool_fb::processFile -- // -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"); +void Tool_fb::processFile(HumdrumFile& infile) { - define("B|do-not-delete-breaks=b", "do not delete system/page break markers"); - define("b|only-delete-breaks=b", "only delete breaks"); + NoteGrid grid(infile); - define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations"); - define("a|only-fix-instrument-abbreviations=b", "only fix instrument abbreviations"); + vector numbers; - define("E|do-not-fix-editorial-accidentals=b", "do not fix instrument abbreviations"); - define("e|only-fix-editorial-accidentals=b", "only fix editorial accidentals"); + vector kernspines = infile.getKernSpineStartList(); - 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"); + int maxTrack = infile.getMaxTrack(); - define("no-ties=b", "do not fix tied notes"); + // Do nothing if base track not withing kern track range + if (m_baseTrackQ < 1 || m_baseTrackQ > maxTrack) { + return; + } - 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"); -} + 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; + } + 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::run -- Primary interfaces to the tool. -// + // 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()); -bool Tool_gasparize::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; igetToken(); + int initialTokenTrack = currentToken->getTrack(); -bool Tool_gasparize::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + if (abs(lowest) < lowestNotePitch) { + lowestNotePitch = abs(lowest); + usedBaseKernTrack = k + 1; + } -bool Tool_gasparize::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; - } - return status; -} + 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); + } + } -// -// In-place processing of file: -// + NoteCell* baseCell = grid.cell(usedBaseKernTrack - 1, i); -bool Tool_gasparize::run(HumdrumFile& infile) { - processFile(infile); + // Ignore grace notes + if (baseCell->getToken()->getOwner()->getDuration() == 0) { + continue; + } - // Re-load the text for each line from their tokens. - infile.createLinesFromTokens(); + string keySignature = getKeySignature(infile, baseCell->getLineIndex()); - // Need to adjust the line numbers for tokens for later - // processing. - m_humdrum_text << infile; - return true; -} + // 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; + } + } + HTp currentToken = baseCell->getToken(); + int initialTokenTrack = baseCell->getToken()->getTrack(); + int lowestBaseNoteBase40Pitch = 9999; -////////////////////////////// -// -// Tool_gasparize::processFile -- -// + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); -void Tool_gasparize::processFile(HumdrumFile& infile) { + // Ignore if base is a rest or silent note + if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) { + if(abs(lowest) < lowestBaseNoteBase40Pitch) { + lowestBaseNoteBase40Pitch = abs(lowest); + } + } - bool mensurationQ = true; - bool articulationsQ = true; - bool abbreviationsQ = true; - bool accidentalsQ = true; - bool referencesQ = true; - bool terminalsQ = true; - bool breaksQ = true; - bool transpositionsQ = true; - bool tieQ = true; - bool teditQ = true; - bool instrumentQ = true; - bool removekeydesigQ = true; - bool fixbarlinesQ = true; - bool parenthesesQ = true; + 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); - if (getBoolean("no-reference-records")) { referencesQ = false; } - if (getBoolean("only-add-reference-records")) { - abbreviationsQ = false; - accidentalsQ = false; - referencesQ = true; - terminalsQ = false; - breaksQ = false; - transpositionsQ = false; - } + // Ignore if base is a rest or silent note + if ((lowestBaseNoteBase40Pitch == 0) || (lowestBaseNoteBase40Pitch == -1000) || (lowestBaseNoteBase40Pitch == -2000) || (lowestBaseNoteBase40Pitch == 9999)) { + continue; + } - if (getBoolean("do-not-delete-breaks")) { breaksQ = false; } - if (getBoolean("only-delete-breaks")) { - abbreviationsQ = false; - accidentalsQ = false; - referencesQ = false; - terminalsQ = false; - breaksQ = true; - transpositionsQ = false; - } + // Interate through each voice + for (int j=0; j<(int)grid.getVoiceCount(); j++) { + NoteCell* targetCell = grid.cell(j, i); - if (getBoolean("do-not-fix-instrument-abbreviations")) { abbreviationsQ = false; } - if (getBoolean("only-fix-instrument-abbreviations")) { - abbreviationsQ = true; - accidentalsQ = false; - referencesQ = false; - terminalsQ = false; - breaksQ = false; - transpositionsQ = false; - } + // Ignore voice if track is not active by --kern-tracks or --spine-tracks + if (m_selectedKernSpines.at(targetCell->getToken()->getTrack()) == false) { + continue; + } - if (getBoolean("do-not-fix-editorial-accidentals")) { accidentalsQ = false; } - if (getBoolean("only-fix-editorial-accidentals")) { - abbreviationsQ = false; - accidentalsQ = true; - referencesQ = false; - terminalsQ = false; - breaksQ = false; - transpositionsQ = false; - } + HTp currentToken = targetCell->getToken(); + int initialTokenTrack = targetCell->getToken()->getTrack(); + vector chordNumbers = {}; - 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; - } + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + for (int subtokenBase40: resolvedToken->getBase40Pitches()) { - if (getBoolean("do-not-remove-empty-transpositions")) { transpositionsQ = false; } + // Ignore if target is a rest or silent note + if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) { + continue; + } - if (getBoolean("no-ties")) { tieQ = false; } + // Ignore if same pitch as base voice + if ((abs(lowestBaseNoteBase40Pitch) == abs(subtokenBase40)) && (baseCell->getToken()->getTrack() == initialTokenTrack)) { + continue; + } - if (getBoolean("only-remove-empty-transpositions")) { - abbreviationsQ = false; - accidentalsQ = false; - referencesQ = false; - terminalsQ = false; - breaksQ = false; - transpositionsQ = true; + // 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; } - 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); } + string exinterp = m_aboveQ ? "**fba" : "**fb"; - adjustSystemDecoration(infile); + if (m_hintQ) { + exinterp = "**hint"; + } - // 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); + 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); + } + } + } 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::removeArticulations -- +// Tool_fb::hideNumbersForTokenLine -- Checks if rhythmic position of line should display numbers // -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); - } +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::adjustSystemDecoration -- -// !!!system-decoration: [(s1)(s2)(s3)(s4)] -// to: -// !!!system-decoration: [*] +// Tool_fb::getTrackData -- Create **fb spine data with formatted numbers for all voices // -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; +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); } } + + return trackData; } ////////////////////////////// // -// Tool_gasparize::deleteDummyTranspositions -- Somehow empty -// transpositions that go to the same pitch can appear in the -// MusicXML data, so remove them here. Example: -// *Trd0c0 +// Tool_fb::getTrackDataForVoice -- Create **fb spine data with formatted numbers for passed voiceIndex // -void Tool_gasparize::deleteDummyTranspositions(HumdrumFile& infile) { - vector ldel; - for (int i=0; iisKern()) { - empty = false; - continue; - } - if (*token == "*Trd0c0") { - token->setText("*"); - } else { - empty = false; - } - } - if (empty) { - ldel.push_back(i); +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); } } - if (ldel.size() == 1) { - infile.deleteLine(ldel[0]); - } else if (ldel.size() > 1) { - cerr << "Warning: multiple transposition lines, not deleting them" << endl; - } - + return trackData; } + ////////////////////////////// // -// 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. +// 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::fixEditorialAccidentals(HumdrumFile& infile) { - removeDoubledAccidentals(infile); +FiguredBassNumber* Tool_fb::createFiguredBassNumber(int basePitchBase40, int targetPitchBase40, int voiceIndex, int lineIndex, bool isAttack, string keySignature) { - m_pstates.resize(infile.getMaxTrack() + 1); - m_estates.resize(infile.getMaxTrack() + 1); - m_kstates.resize(infile.getMaxTrack() + 1); + // Calculate figured bass number + int baseDiatonicPitch = Convert::base40ToDiatonic(basePitchBase40); + int targetDiatonicPitch = Convert::base40ToDiatonic(targetPitchBase40); + int diff = abs(targetDiatonicPitch) - abs(baseDiatonicPitch); + int num; - 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); + if ((baseDiatonicPitch == 0) || (targetDiatonicPitch == 0)) { + num = 0; + } else if (diff == 0) { + num = 1; + } else if (diff > 0) { + num = diff + 1; + } else { + num = diff - 1; } - 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"); - } - } - } +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::addTerminalLongs -- Convert all last notes to terminal longs -// Also probably add terminal longs before double barlines as in JRP. +// Tool_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music. // -void Tool_gasparize::addTerminalLongs(HumdrumFile& infile) { - int scount = infile.getStrandCount(); - for (int i=0; iisKern()) { - continue; - } - 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; - } - } +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::fixInstrumentAbbreviations -- +// Tool_fb::filterFiguredBassNumbersForLineAndVoice -- // -void Tool_gasparize::fixInstrumentAbbreviations(HumdrumFile& infile) { - int iline = -1; - int aline = -1; +vector Tool_fb::filterFiguredBassNumbersForLineAndVoice(vector numbers, int lineIndex, int voiceIndex) { - vector kerns = infile.getKernSpineStartList(); - if (kerns.empty()) { - return; + 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); +} + + + +////////////////////////////// +// +// 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; } - 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(); + // 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 (iline < 0) { - // no names to create abbreviations for - return; + // Analysze before sorting + if (m_compoundQ) { + formattedNumbers = analyzeChordNumbers(formattedNumbers); } - if (aline < 0) { - // not creating a new abbreviation for now - // (could add later). - return; + + // 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 (infile[iline].getFieldCount() != infile[aline].getFieldCount()) { - // no spine splitting between the two lines. - return; + + if (m_reduceQ) { + // Overwrite formattedNumbers with abbreviated numbers + formattedNumbers = getAbbreviatedNumbers(formattedNumbers); } - // 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 (!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"; - } else { - abbr += toupper(name[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; } - // check for numbers after the end of the name and add to abbreviation - infile.token(aline, j)->setText(abbr); } + return str; } ////////////////////////////// // -// Tool_gasparize::convertBreaks -- +// 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; + } + + 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. -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::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::deleteBreaks -- +// Tool_fb::getNumberString -- Get only the numbers (without accidentals) of passed FiguredBassNumbers // -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); +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; } -//////////////////////////////// + +////////////////////////////// // -// Tool_gasparize::addBibliographicRecords -- +// Tool_fb::getKeySignature -- Get the key signature for a line index of the input file // -// !!!COM: -// !!!CDT: -// !!!OTL: -// !!!AGN: -// !!!SCT: -// !!!SCA: -// !!!voices: + +string Tool_fb::getKeySignature(HumdrumFile& infile, int lineIndex) { + string keySignature = ""; + [&] { + for (int i = 0; i < infile.getLineCount(); i++) { + if (i > lineIndex) { + return; + } + HLp line = infile.getLine(i); + for (int j = 0; j < line->getFieldCount(); j++) { + if (line->token(j)->isKeySignature()) { + keySignature = line->getTokenString(j); + } + } + } + }(); + return keySignature; +} + + + +////////////////////////////// // -// At end: -// !!!RDF**kern: l = terminal long -// !!!RDF**kern: i = editorial accidental -// !!!EED: -// !!!EEV: $DATE +// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent +// TODO: Handle negative values and sustained notes // -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]; - } +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); + }); - // 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:"); - } - } - 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"); - } - - // 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; - } - } - } - 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 (filteredBase40Pitches.size() == 0) { + return -2000; } - 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); - } + return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches)); } -//////////////////////////////// +////////////////////////////// // -// Tool_gasparize::checkDataLine -- +// Tool_fb::getIntervalQuality -- Return interval quality prefix string // -void Tool_gasparize::checkDataLine(HumdrumFile& infile, int lineindex) { - HumdrumLine& line = infile[lineindex]; +string Tool_fb::getIntervalQuality(int basePitchBase40, int targetPitchBase40) { - 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; - } + int diff = (targetPitchBase40 - basePitchBase40) % 40; - base7 = Convert::kernToBase7(token); - accid = Convert::kernToAccidentalCount(token); - haseditQ = false; - removeQ = false; + diff = diff < -2 ? abs(diff) : diff; - // 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; - } + // 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; - if (removeQ) { - string temp = *token; - hre.replaceDestructive(temp, "", "X"); - token->setText(temp); - } + // 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; - 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); - } + // 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; - if (haseditQ) { - // Store new editorial pitch state. - m_estates.at(track).at(base7) = true; - m_pstates.at(track).at(base7) = accid; - continue; - } + // 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; - 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; - } + case 20: quality = ""; break; - 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; + // 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; - // 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); - } + // 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; - continue; - } + // 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; - // 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). + default: quality = "?"; break; + } - m_estates[track][base7] = true; - m_pstates[track][base7] = accid; + return quality; - 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]; - } - } +} - if (foundQ) { - token->setText(output); - continue; - } - // 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); - } + +////////////////////////////// +// +// FiguredBassNumber::FiguredBassNumber -- Constructor +// + +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::updateKeySignatures -- Fill in the accidental -// states for each diatonic pitch. +// FiguredBassNumber::toString -- Convert FiguredBassNumber to a string (accidental + number) // -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 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 ""; +} - 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; - case 'd': case 'D': - switch (text[j+1]) { - case '#': m_kstates[track][1] = +1; - break; - case '-': m_kstates[track][1] = -1; - break; - } - break; +////////////////////////////// +// +// 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 - case 'e': case 'E': - switch (text[j+1]) { - case '#': m_kstates[track][2] = +1; - break; - case '-': m_kstates[track][2] = -1; - break; - } - break; +int FiguredBassNumber::getNumberWithinOctave(void) { + int num = m_number % 7; - case 'f': case 'F': - switch (text[j+1]) { - case '#': m_kstates[track][3] = +1; - break; - case '-': m_kstates[track][3] = -1; - break; - } - break; + // Replace 0 with 7 and -7 + if ((abs(m_number) > 0) && (m_number % 7 == 0)) { + return m_number < 0 ? -7 : 7; + } - 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]; - } + // 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; } } + return m_number < 0 ? -8 : 8; } - // 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]; - } + // 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; } -//////////////////////////////// +////////////////////////////// // -// Tool_gasparize::clearStates -- +// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor +// Helper class to store the mappings for abbreviate figured bass numbers // -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); - } +FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector n) { + m_str = s; + m_numbers = n; } + ////////////////////////////// // -// Tool_gasparize::getDate -- +// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers // -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(); -} +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}), +}; -////////////////////////////// -// -// Tool_gasparize::fixTies -- -// If a tie is unclosed or if a note is followed by an invisible rest, then fix. +#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_filter::Tool_filter -- Set the recognized options for the tool. // -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); +Tool_filter::Tool_filter(void) { + define("debug=b", "print debug statement"); + define("v|variant=s:", "Run filters labeled with the given variant"); } -void Tool_gasparize::fixTieStartEnd(HumdrumFile& infile) { - int strands = infile.getStrandCount(); - for (int i=0; iisKern()) { - continue; - } - HTp send = infile.getStrandEnd(i); - fixTiesStartEnd(sstart, send); - } +///////////////////////////////// +// +// Tool_filter::run -- Primary interfaces to the tool. +// + +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; +} -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); +bool Tool_filter::runUniversal(HumdrumFileSet& infiles) { + bool status = true; + vector > commands; + getUniversalCommandList(commands, infiles); + + 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); } - current = current->getNextToken(); } + + removeUniversalFilterLines(infiles); + + return status; } -////////////////////////////// // -// Tool_gasparize::fixTiesForStrand -- +// In-place processing of file: // -void Tool_gasparize::fixTiesForStrand(HTp sstart, HTp send) { - if (!sstart) { - return; +bool Tool_filter::run(HumdrumFileSet& infiles) { + if (infiles.getCount() == 0) { + return false; } - 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(); + + initialize(infiles[0]); + + HumdrumFile& infile = infiles[0]; + + #ifdef __EMSCRIPTEN__ + bool optionList = getBoolean("options"); + if (optionList) { + printEmscripten(m_humdrum_text); + m_humdrum_text << infile; } -} + #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); -////////////////////////////// -// -// Tool_gasparize::fixTieToInvisibleRest -- -// + } 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); -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); -} + } 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); -////////////////////////////// -// -// Tool_gasparize::fixHangingTie -- Not dealing with chain of missing ties. -// + } 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); -void Tool_gasparize::fixHangingTie(HTp first, HTp second) { - string text = *second; - text += "]"; - second->setText(text); -} + } 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); -////////////////////////////// -// -// Tool_gasparize::addMensurations -- Add mensurations. -// + } 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); -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; +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 + ":"; } - 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=0; iisKern()) { - if (top == 2) { - line += "met(C|)"; - } else { - line += "met(O)"; + + if (m_variant.size() > 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); } } } - infile.insertLine(index+1, line); } -/////////////////////////////// + +////////////////////////////// // -// Tool_gasparize::createEditText -- Convert markers into *edit interps. +// Tool_filter::removeUniversalFilterLines -- // -void Tool_gasparize::createEditText(HumdrumFile& infile) { - // previous process manipulated the structure so reanalyze here for now: - infile.analyzeBaseFromTokens(); - infile.analyzeStructureNoRhythm(); +void Tool_filter::removeUniversalFilterLines(HumdrumFileSet& infiles) { + HumRegex hre; + string text; - int strands = infile.getStrandCount(); - for (int i=0; iisDataType("**text")) { - continue; - } - HTp send = infile.getStrandEnd(i); - bool status = addEditStylingForText(infile, sstart, send); - if (status) { - infile.analyzeBaseFromTokens(); - infile.analyzeStructureNoRhythm(); - } - } -} + string maintag = "!!!!filter:"; + string mainXtag = "!!!!Xfilter:"; + string maintagQuery = "^!!!!filter:"; + string maintagV; + string mainXtagV; + string maintagQueryV; -////////////////////////////// -// -// Tool_gasparize::addEditStylingForText -- -// + if (m_variant.size() > 0) { + maintagV = "!!!!filter-" + m_variant + ":"; + mainXtagV = "!!!!Xfilter-" + m_variant + ":"; + maintagQueryV = "^!!!!filter-" + m_variant + ":"; + } -bool Tool_gasparize::addEditStylingForText(HumdrumFile& infile, HTp sstart, HTp send) { - HTp current = send->getPreviousToken(); - bool output = false; - string state = ""; - string laststate = ""; - 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"; + 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 +86498,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 +86516,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; + } + } } - } else { - if (!match) { - 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. +// Tool_fixps::printNewManipulator -- // -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; } - return status; -} - - -bool Tool_half::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (token->compare(0, 2, "**") == 0) { + m_humdrum_text << infile[line] << endl; + return; } - return status; -} - -// -// In-place processing of file: -// - -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; -} - - - -////////////////////////////// -// -// Tool_half::processFile -- -// - -void Tool_half::processFile(HumdrumFile& infile) { - m_lyricBreakQ = getBoolean("lyric-beam-break"); - terminalLongToTerminalBreve(infile); - halfRhythms(infile); - adjustBeams(infile); + 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 +86722,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,54 +86923,114 @@ 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; +void Tool_flipper::checkForFlipChanges(HumdrumFile& infile, int index) { + if (!infile[index].isInterpretation()) { + return; + } - int counter = 0; - int scount = infile.getStrandCount(); - for (int i=0; igetExclusiveInterpretation(); - int hasHandMarkup = xtok->getValueInt("auto", "hand"); - if (!hasHandMarkup) { - continue; + int track; + + for (int i=0; igetTrack(); + m_strophe[track] = true; + } else if (*token == "*Xstrophe") { + track = token->getTrack(); + m_strophe[track] = false; } - 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(); + + if (m_allQ) { + // state always stays on in this case + return; } -} + 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; + } + } -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; +} + + + +////////////////////////////// +// +// Tool_flipper::processLine -- +// + +void Tool_flipper::processLine(HumdrumFile& infile, int index) { + if (!infile[index].hasSpines()) { + return; + } + if (infile[index].isInterpretation()) { + checkForFlipChanges(infile, index); + } + + vector> flipees; + extractFlipees(flipees, infile, index); + if (!flipees.empty()) { + int status = flipSubspines(flipees); + if (status) { + infile[index].createLineFromTokens(); } + } +} - 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; + + +////////////////////////////// +// +// 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; } - current->setText(text); - current = current->getNextToken(); + } + 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); } } @@ -86385,69 +87038,81 @@ void Tool_hands::markNotes(HTp sstart, HTp send) { ////////////////////////////// // -// Tool_hands::colorHands -- Convert for example *LH into *color:dodgerblue. +// Tool_flipper::extractFlipees -- // -void Tool_hands::colorHands(HumdrumFile& infile) { - string left = "*color:" + m_leftColor; - string right = "*color:" + m_rightColor; - for (int i=0; i>& 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); -} - - - -////////////////////////////// -// -// 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"); -} - +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); -////////////////////////////// -// -// Tool_homorhythm::addFractionAnalysis -- -// + 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); + } -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 -- +// Tool_gasparize::removeDoubledAccidentals -- Often caused by transposition +// differences between parts in the MusicXML export from Finale. Also some +// strange double sharps appear randomly. // -int Tool_homorhythm::getOriginalVoiceCount(HumdrumFile& infile) { +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"); } - return count; } } - return 0; -} - - - -////////////////////////////// -// -// Tool_homorhythm::getExtantVoiceCount -- -// - -int Tool_homorhythm::getExtantVoiceCount(HumdrumFile& infile) { - vector spines = infile.getKernSpineStartList(); - return (int)spines.size(); } ////////////////////////////// // -// Tool_homorhythm::analyzeLine -- +// Tool_gasparize::addTerminalLongs -- Convert all last notes to terminal longs +// Also probably add terminal longs before double barlines as in JRP. // -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()) { +void Tool_gasparize::addTerminalLongs(HumdrumFile& infile) { + int scount = infile.getStrandCount(); + for (int i=0; iisRest()) { + if (!cur->isKern()) { continue; } - if (token->isNull()) { - nullQ = 1; - token = token->resolveNull(); - if (!token) { + while (cur) { + if (!cur->isData()) { + cur = cur->getPreviousToken(); continue; } - if (token->isRest()) { + if (cur->isNull()) { + cur = cur->getPreviousToken(); 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; - } + if (cur->isRest()) { + cur = cur->getPreviousToken(); + continue; } - 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"; + 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; } - } 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_gasparize::fixInstrumentAbbreviations -- // -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. -// +void Tool_gasparize::fixInstrumentAbbreviations(HumdrumFile& infile) { + int iline = -1; + int aline = -1; -bool Tool_homorhythm2::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i kerns = infile.getKernSpineStartList(); + if (kerns.empty()) { + return; } - return status; -} - -bool Tool_homorhythm2::run(const string& indata, ostream& out) { - HumdrumFile infile; - infile.read(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + 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(); } - return status; -} - -bool Tool_homorhythm2::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (iline < 0) { + // no names to create abbreviations for + return; } - return status; -} - - -bool Tool_homorhythm2::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; -} - - - -////////////////////////////// -// -// Tool_homorhythm2::initialize -- -// - -void Tool_homorhythm2::initialize(void) { - m_threshold = getDouble("threshold"); - if (m_threshold < 0.0) { - m_threshold = 0.0; + if (aline < 0) { + // not creating a new abbreviation for now + // (could add later). + return; } - m_threshold2 = getDouble("threshold2"); - if (m_threshold2 < 0.0) { - m_threshold2 = 0.0; + if (infile[iline].getFieldCount() != infile[aline].getFieldCount()) { + // no spine splitting between the two lines. + return; } - if (m_threshold < m_threshold2) { - double temp = m_threshold; - m_threshold = m_threshold2; - m_threshold2 = temp; + // 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 (!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"; + } else { + abbr += toupper(name[0]); + } + // check for numbers after the end of the name and add to abbreviation + infile.token(aline, j)->setText(abbr); } - } ////////////////////////////// // -// Tool_homorhythm2::processFile -- +// Tool_gasparize::convertBreaks -- // -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"); - - 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; - } - } - } +void Tool_gasparize::convertBreaks(HumdrumFile& infile) { + HumRegex hre; + for (int i=infile.getLineCount()-1; i>= 0; i--) { + if (!infile[i].isGlobalComment()) { + continue; } - 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 (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); } - 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; - } +////////////////////////////// +// +// Tool_gasparize::deleteBreaks -- +// - vector color(infile.getLineCount());; - for (int i=0; i= 0; i--) { + if (!infile[i].isGlobalComment()) { continue; } - if (m_score[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(); - } + if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) { + infile.deleteLine(i); } - 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); + else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) { + infile.deleteLine(i); } - 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_gasparize::addBibliographicRecords -- // - -Tool_hproof::Tool_hproof(void) { - // put option definitions here -} - - - -/////////////////////////////// +// !!!COM: +// !!!CDT: +// !!!OTL: +// !!!AGN: +// !!!SCT: +// !!!SCA: +// !!!voices: // -// Tool_hproof::run -- Primary interfaces to the tool. +// At end: +// !!!RDF**kern: l = terminal long +// !!!RDF**kern: i = editorial accidental +// !!!EED: +// !!!EEV: $DATE // -bool Tool_hproof::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i refinfo = infile.getReferenceRecords(); + map refs; + for (int i=0; i<(int)refinfo.size(); i++) { + string key = refinfo[i]->getReferenceKey(); + refs[key] = refinfo[i]; } - return status; -} - -bool Tool_hproof::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - return run(infile, out); -} - - -bool Tool_hproof::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - out << infile; - return status; -} - - -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; -} - + // 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:"); + } + } + if (refs.find("AGN") == refs.end()) { + if (infile.token(0, 0)->find("!!!OTL") != string::npos) { + infile.insertLine(1, "!!!AGN:"); + } else { + infile.insertLine(0, "!!!AGN:"); + } + } -////////////////////////////// -// -// Tool_hproof::markNonChordTones -- Mark -// + 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"); + } -void Tool_hproof::markNonChordTones(HumdrumFile& infile) { - vector list; - infile.getSpineStartList(list); - vector hlist; - for (auto it : list) { - if (*it == "**harm") { - hlist.push_back(it); + // 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; + } } } - if (hlist.empty()) { - cerr << "Warning: No **harm or **rhrm spines in data" << endl; - return; + 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"); } - processHarmSpine(infile, hlist[0]); + 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); + } } -////////////////////////////// +//////////////////////////////// // -// processHarmSpine -- +// Tool_gasparize::checkDataLine -- // -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; +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 (ntoken && token) { - getNewKey(token, ntoken, key); + if (token->isNull()) { + continue; + } + if (token->isRest()) { + continue; + } + if (token->find('j') != string::npos) { + continue; + } + if (token->isSecondaryTiedNote()) { + continue; } - token = ntoken; - ntoken = ntoken->getNextNNDT(); - } -} - - -////////////////////////////// -// -// Tool_hproof::getNewKey -- -// + base7 = Convert::kernToBase7(token); + accid = Convert::kernToAccidentalCount(token); + haseditQ = false; + removeQ = false; -void Tool_hproof::getNewKey(HTp token, HTp ntoken, string& key) { - token = token->getNextToken(); - while (token && (token != ntoken)) { - if (token->isKeyDesignation()) { - key = *token; + // 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; } - token = token->getNextToken(); - } -} + 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); + } -////////////////////////////// -// -// Tool_hproof::markNotesInRange -- -// + if (haseditQ) { + // Store new editorial pitch state. + m_estates.at(track).at(base7) = true; + m_pstates.at(track).at(base7) = accid; + continue; + } -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; + + 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; + + // 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; } - HTp tok = infile.token(i, j); - if (tok->isNull()) { - continue; + int hashide = false; + if (token->find("-y") != string::npos) { + hashide = true; } - if (tok->isRest()) { - continue; + 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); } - markHarmonicTones(tok, cts); - } - } - -// cerr << "TOK\t" << ctoken << "\tLINES\t" << startline << "\t" << stopline << "\t"; -// for (int i=0; igetText(); + 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::markHarmonicTones(HTp tok, vector& 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 += " "; + if (foundQ) { + token->setText(output); + continue; } - bool found = false; - for (int j=0; j<(int)cts.size(); j++) { - if (pitch % 40 == cts[j] % 40) { - output += subtok; - output += "Z"; - found = true; + + // 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; } } - if (!found) { - output += subtok; - output += "N"; - } + token->setText(output); } - tok->setText(output); } - - -///////////////////////////////// +//////////////////////////////// // -// Tool_humbreak::Tool_humbreak -- Set the recognized options for the tool. +// Tool_gasparize::updateKeySignatures -- Fill in the accidental +// states for each diatonic pitch. // -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"); -} +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; + 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_humbreak::run -- Do the main work of the tool. -// + case 'd': case 'D': + switch (text[j+1]) { + case '#': m_kstates[track][1] = +1; + break; + case '-': m_kstates[track][1] = -1; + break; + } + break; -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; - } - } +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"; } - - 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; - } + ss << month << "/"; + if (day < 10) { + ss << "0"; } + ss << day; + return ss.str(); } ////////////////////////////// // -// Tool_humbreak::markLineBreakMeasures -- +// Tool_gasparize::fixTies -- +// If a tie is unclosed or if a note is followed by an invisible rest, then fix. // -void Tool_humbreak::markLineBreakMeasures(HumdrumFile& infile) { - vector pbreak; - vector lbreak; - HumRegex hre; - map used; - - for (int i=0; iisKern()) { 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); - } - } - - 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); - } - } + HTp send = infile.getStrandEnd(i); + fixTiesForStrand(sstart, send); } + fixTieStartEnd(infile); } -////////////////////////////// -// -// Tool_humbreak::addBreaks -- -// - -void Tool_humbreak::addBreaks(HumdrumFile& infile) { - markLineBreakMeasures(infile); - - HumRegex hre; - for (int i=0; iisKern()) { continue; } - barnum--; - int pageQ = infile[i].getValueInt("auto", "page"); - - if (pageQ && infile[i].isComment()) { - HTp token = infile.token(i, 0); - if (hre.search(token, "^!!LO:PB:")) { - // 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; - } - } 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; + HTp send = infile.getStrandEnd(i); + fixTiesStartEnd(sstart, send); } } -////////////////////////////// -// -// Tool_humbreak::processFile -- -// - -void Tool_humbreak::processFile(HumdrumFile& infile) { - initialize(); - if (m_removeQ) { - removeBreaks(infile); - } else if (m_page2lineQ) { - convertPageToLine(infile); - } else { - addBreaks(infile); +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::removeBreaks -- +// Tool_gasparize::fixTiesForStrand -- // -void Tool_humbreak::removeBreaks(HumdrumFile& infile) { - for (int i=0; icompare(0, 7, "!!LO:LB") == 0) { +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 (infile[i].token(0)->compare(0, 7, "!!LO:PB") == 0) { + if (current->isNull()) { + current = current->getNextToken(); continue; } - m_humdrum_text << infile[i] << endl; + 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(); } } @@ -87602,401 +88206,416 @@ void Tool_humbreak::removeBreaks(HumdrumFile& infile) { ////////////////////////////// // -// Tool_humbreak::convertPageToLine -- +// Tool_gasparize::fixTieToInvisibleRest -- // -void Tool_humbreak::convertPageToLine(HumdrumFile& infile) { +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; - 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; + 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_humdiff::Tool_humdiff -- Set the recognized options for the tool. +// Tool_gasparize::fixHangingTie -- Not dealing with chain of missing ties. // -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"); +void Tool_gasparize::fixHangingTie(HTp first, HTp second) { + string text = *second; + text += "]"; + second->setText(text); } ////////////////////////////// // -// Tool_humdiff::run -- +// Tool_gasparize::addMensurations -- Add mensurations. // -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=0; i--) { + if (!infile[i].isInterpretation()) { + 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; + for (int j=0; j> timepoints(2); - extractTimePoints(timepoints.at(0), reference); - extractTimePoints(timepoints.at(1), alternate); - - if (getBoolean("time-points")) { - printTimePoints(timepoints[0]); - printTimePoints(timepoints[1]); +void Tool_gasparize::addMensuration(int top, HumdrumFile& infile, int index) { + HTp checktoken = infile[index+1].token(0); + if (!checktoken) { + return; } - - compareTimePoints(timepoints, reference, alternate); + 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_humdiff::printTimePoints -- +// Tool_gasparize::createEditText -- Convert markers into *edit interps. // -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; +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; iisDataType("**text")) { + continue; + } + HTp send = infile.getStrandEnd(i); + bool status = addEditStylingForText(infile, sstart, send); + if (status) { + infile.analyzeBaseFromTokens(); + infile.analyzeStructureNoRhythm(); + } } } - ////////////////////////////// // -// Tool_humdiff::compareTimePoints -- +// Tool_gasparize::addEditStylingForText -- // -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; - } +bool Tool_gasparize::addEditStylingForText(HumdrumFile& infile, HTp sstart, HTp send) { + HTp current = send->getPreviousToken(); + bool output = false; + string state = ""; + string laststate = ""; + HumRegex hre; + HTp lastdata = NULL; + bool italicQ = false; + while (current && (current != sstart)) { + if (!current->isData()) { + current = current->getPreviousToken(); + continue; } - 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; + 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"; } - 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)++; + current = current->getPreviousToken(); + continue; + } else { + if (italicQ) { + state = "italic"; + } else { + state = "regular"; } } - if (!found) { - break; - } else { - compareLines(minval, indexes, timepoints, infiles); + 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); + } + } } - for (int i=0; i<(int)increment.size(); i++) { - indexes.at(i) += increment.at(i); + 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_humdiff::printNotePoints -- +// Tool_gasparize::insertEditText -- // -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; +bool Tool_gasparize::insertEditText(const string& text, HumdrumFile& infile, int line, int field) { + if (!infile[line].isInterpretation()) { + return false; } - m_free_text << "^^^^^^^^^^^^^^^^^^^^^^^^^" << endl; - m_free_text << endl; + HTp token; + for (int i=0; iisNull()) { + continue; + } + if (token->find("edit") != string::npos) { + break; + } + return false; + } + token = infile.token(line, field); + token->setText(text); + + return true; } -////////////////////////////// +///////////////////// // -// Tool_humdiff::markNote -- mark the note (since it does not have a match in other edition(s). +// Tool_gasparize::getEditLine -- // -void Tool_humdiff::markNote(NotePoint& np) { - m_marked = 1; - HTp token = np.token; - if (!token) { - return; +string Tool_gasparize::getEditLine(const string& text, int fieldindex, HLp line) { + string output; + for (int i=0; igetFieldCount()) { + output += "\t"; + } } - if (!token->isChord()) { - string contents = *token; - contents += "@"; - token->setText(contents); - return; + output += text; + if (fieldindex < line->getFieldCount()) { + output += "\t"; } - vector tokens = token->getSubtokens(); - tokens[np.subindex] += "@"; - string output = tokens[0]; - for (int i=1; i<(int)tokens.size(); i++) { - output += " "; - output += tokens[i]; + for (int i=fieldindex+1; igetFieldCount(); i++) { + output += "*"; + if (i < line->getFieldCount()) { + output += "\t"; + } } - token->setText(output); + return output; } ////////////////////////////// // -// Tool_humdiff::compareLines -- +// adjustIntrumentNames -- // -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; +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; } } } - - 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 (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 (!reportQ) { + if (abbrLine >= 0) { 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; - } + string abbr; + HumRegex hre; + for (int i=0; i& 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) { +void Tool_gasparize::removeKeyDesignations(HumdrumFile& infile) { + HumRegex hre; + for (int i=infile.getLineCount() - 1; i>=0; i--) { + if (!infile[i].isInterpretation()) { continue; } - if (nps.at(i).duration != np.duration) { - continue; + for (int j=0; jisKern()) { + continue; + } + if (hre.search(token, "^\\*[A-Ga-g][#n-]*:$")) { + // suppress the key desingation + infile.deleteLine(i); + break; + } } - return i; } - // cerr << "\tCannot find note " << np.token << " on line " << np.token->getLineIndex() << " in other work" << endl; - return -1; -} - +} ////////////////////////////// // -// Tool_humdiff::getNoteList -- +// Tool_gasparize::fixBarlines -- Add final double barline and convert +// any intermediate final barlines to double barlines. // -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()) { +void Tool_gasparize::fixBarlines(HumdrumFile& infile) { + fixFinalBarline(infile); + HumRegex hre; + + for (int i=0; iisRest()) { - continue; + if (infile[i].getDurationToEnd() == 0) { + break; } - int scount = token->getSubtokenCount(); - int track = token->getTrack(); - int layer = token->getSubtrack(); - for (int j=0; jgetSubtoken(j); - if (subtok.find("]") != string::npos) { + for (int j=0; jfind("==") == string::npos) { continue; } - if (subtok.find("_") != string::npos) { - continue; + if (hre.search(token, "^==(\\d*)")) { + string text = "="; + text += hre.getMatch(1); + text += "||"; + token->setText(text); } - // 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); } } } @@ -88005,34 +88624,23 @@ void Tool_humdiff::getNoteList(vector& notelist, HumdrumFile& infile, ////////////////////////////// // -// Tool_humdiff::extractTimePoints -- Extract a list of the timestamps in a file. +// Tool_gasparize::fixFinalBarline -- // -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=0; i--) { + if (infile[i].isData()) { + break; } - if (!infile[i].isData()) { + if (!infile[i].isBarline()) { continue; } - if (infile[i].getDuration() == 0) { - // ignore grace notes for now - continue; + for (int j=0; jsetText("=="); + } } - tp.clear(); - tp.file.push_back(&infile); - tp.index.push_back(i); - tp.timestamp = infile[i].getDurationFromStart(); - tp.measure = measure; - points.push_back(tp); } } @@ -88040,48 +88648,76 @@ void Tool_humdiff::extractTimePoints(vector& points, HumdrumFile& inf ////////////////////////////// // -// operator<< == print a TimePoint +// Tool_gasparize::createJEditorialAccidentals -- +// convert +// !LO:TX:a:t=( ) +// 4F# // -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; +void Tool_gasparize::createJEditorialAccidentals(HumdrumFile& infile) { + int strands = infile.getStrandCount(); + for (int i=0; iisKern()) { + continue; + } + HTp send = infile.getStrandEnd(i); + createJEditorialAccidentals(sstart, send); } - return out; } - - -////////////////////////////// -// -// operator<< == print a NotePoint -// - -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; +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(); } - 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; +} + +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; } - return out; + current = current->getNextToken(); } @@ -88090,28 +88726,21 @@ ostream& operator<<(ostream& out, NotePoint& np) { ///////////////////////////////// // -// Tool_humsheet::Tool_humsheet -- Set the recognized options for the tool. +// Tool_grep::Tool_grep -- Set the recognized options for 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"); +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::run -- Do the main work of the tool. +// Tool_grep::run -- Do the main work of the tool. // -bool Tool_humsheet::run(HumdrumFileSet& infiles) { +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_humsheet::printCellClasses(HTp token) { - int track = token->getTrack(); - string classlist; +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) { + int track = token->getTrack(); + string classlist; if (m_zebraQ) { if (track % 2 == 0) { @@ -95404,843 +98250,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 +99848,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 +100472,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 +100722,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); +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(); + } - if (getBoolean("replace")) { - replaceLyrics(infile, notecount); - } else if (getBoolean("words")) { - markMelismas(infile, notecount); - extractWordlist(wordinfo, wordlist, infile, notecount); - printWordlist(infile, wordinfo, wordlist); + infile.createLinesFromTokens(); +} + + + +////////////////////////////// +// +// Tool_mens2kern::processMelody -- +// + +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; + + for (int i=0; i<(int)melody.size(); i++) { + if (*melody[i] == "**mens") { + // convert spine to **kern data: + melody[i]->setText("**kern"); + } + + 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); + + 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 { - markMelismas(infile, notecount); + 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 ""; } -////////////////////////////// + +///////////////////////////////// // -// Tool_melisma::initializePartInfo -- +// Tool_meter::Tool_meter -- Set the recognized options for the tool. // -void Tool_melisma::initializePartInfo(HumdrumFile& infile) { - m_names.clear(); - m_abbreviations.clear(); - m_partnums.clear(); - - 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 (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(); - } - } else if (ktrack) { - m_names[track] = m_names[ktrack]; - m_abbreviations[track] = m_abbreviations[ktrack]; - m_partnums[track] = m_partnums[ktrack]; - } - } +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"); } -////////////////////////////// +///////////////////////////////// // -// printWordlist -- +// Tool_meter::run -- Do the main work of 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); - - // m_free_text << "===========================" << endl; - - 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; - - 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; } } + } + + // check for first-measure pickup + if (firstmeasure >= 0) { + if (bardur.at(firstmeasure) < tsigdur.at(firstmeasure)) { + pickup.at(firstmeasure) = true; + } + } - 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(); + 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 +101470,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; + return index + counter - 1; } - - -///////////////////////////////// +////////////////////////////// // -// Tool_mens2kern::Tool_mens2kern -- Set the recognized options for the tool. +// Tool_meter::getMeterData -- // -Tool_mens2kern::Tool_mens2kern(void) { - define("debug=b", "print debugging statements"); +void Tool_meter::getMeterData(HumdrumFile& infile) { + + int maxtrack = infile.getMaxTrack(); + vector 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(); -bool Tool_mens2kern::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (!line.hasSpines()) { + return; } - return status; -} - -bool Tool_mens2kern::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + 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; } - return status; -} - -bool Tool_mens2kern::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; -} + 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; + } + 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; + } + } -////////////////////////////// -// -// Tool_mens2kern::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. -// + value.str(""); + value << qq; + token->setValue("auto", "zeroBeat", value.str()); + token->setValue("auto", "hasData", 1); -void Tool_mens2kern::initialize(void) { - m_debugQ = getBoolean("debug"); + } + return; + } } -////////////////////////////// + +///////////////////////////////// // -// Tool_mens2kern::processFile -- +// Tool_gridtest::Tool_metlev -- Set the recognized options for the tool. // -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_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::processMelody -- +// Tool_metlev::run -- Primary interfaces to the tool. // -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; +bool Tool_metlev::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; isetText("**kern"); - } - if (melody[i]->isMensuration()) { - getMensuralInfo(melody[i], maximodus, modus, tempus, prolatio); +bool Tool_metlev::run(const string& indata, ostream& out) { + HumdrumFile infile(indata); + return run(infile, out); +} - // 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; - } +bool Tool_metlev::run(HumdrumFile& infile, ostream& out) { + bool status = run(infile); + out << infile; + return status; +} - 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); +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_mens2kern::getMensuralInfo -- -// + m_kernspines = infile.getKernSpineStartList(); -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; - } + vector beatlev(lineCount, NAN); + int track = 0; + if (m_kernspines.size() > 0) { + track = m_kernspines[0]->getTrack(); } else { - string levels = hre.getMatch(1); - if (levels.size() >= 1) { - maximodus = levels[0] - '0'; + m_error_text << "No **kern spines in input file" << endl; + return false; + } + infile.getMetricLevels(beatlev, track, NAN); + + for (int i=0; i= 2) { - modus = levels[1] - '0'; + if (getBoolean("no-grace-notes") && (infile[i].getDuration() == 0)) { + beatlev[i] = NAN; + continue; } - if (levels.size() >= 3) { - tempus = levels[2] - '0'; + if (getBoolean("attacks-only")) { + if (!infile[i].getKernNoteAttacks()) { + beatlev[i] = NAN; + continue; + } } - if (levels.size() >= 4) { - prolatio = levels[3] - '0'; + if (beatlev[i] - (int)beatlev[i] != 0.0) { + if (getBoolean("integer")) { + beatlev[i] = floor(beatlev[i]); + } else { + beatlev[i] = Convert::significantDigits(beatlev[i], 2); + } } } - 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; } + 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); + } + 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 +102138,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; 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); + + } 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); + + } 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); + } + } } - if (m_digits > 15) { - m_digits = 15; + + if (exinterpLine >= 0) { + processExclusiveInterpretationLine(infile, exinterpLine); } - if (m_joinQ && !(m_tsigQ || m_numeratorQ || m_denominatorQ)) { - m_tsigQ = true; + storeModOriReferenceRecords(infile); + + if (m_infoQ) { + if (m_modernQ || m_originalQ) { + m_humdrum_text << infile; + } + printInfo(); } - if (m_joinQ) { - m_nobeatQ = false; + + if (!(m_modernQ || m_originalQ)) { + // nothing to do + return; } - if (m_joinQ && m_numeratorQ && m_denominatorQ) { - m_tsigQ = true; + + switchModernOriginal(infile); + printModoriOutput(infile); +} + + +////////////////////////////// +// +// Tool_modori::processExclusiveInterpretationLine -- +// + +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; } - if (m_tsigQ) { - m_numeratorQ = true; - m_denominatorQ = true; + 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); + } } - // 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; + for (int i=0; i<(int)staff.size(); i++) { + changed |= processStaffCompanionSpines(nonstaff[i]); + } + + changed |= processStaffSpines(staffish); + + if (changed) { + infile[line].createLineFromTokens(); + } +} + + + +////////////////////////////// +// +// Tool_modori::processStaffSpines -- +// + +bool Tool_modori::processStaffSpines(vector& tokens) { + + 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; + } + } + + return changed; +} + + + +////////////////////////////// +// +// Tool_modori::processStaffCompanionSpines -- +// + +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]); + } + } + + 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; + } + + + 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) + + 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; + } + } } + return gchanged; } ////////////////////////////// // -// Tool_meter::processFile -- +// Tool_modori::storeModOriReferenceRecors -- // -void Tool_meter::processFile(HumdrumFile& infile) { - analyzePickupMeasures(infile); - getMeterData(infile); - printMeterData(infile); -} - - -////////////////////////////// -// -// Tool_meter::analyzePickupMeasures -- -// +void Tool_modori::storeModOriReferenceRecords(HumdrumFile& infile) { + m_references.clear(); -void Tool_meter::analyzePickupMeasures(HumdrumFile& infile) { - vector sstarts; - infile.getKernSpineStartList(sstarts); - for (int i=0; i<(int)sstarts.size(); i++) { - analyzePickupMeasures(sstarts[i]); + 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; -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; + 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); } - 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(); - } - - // 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; - } - - // 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; + } else if (m_originalQ || m_infoQ) { + if (hre.search(keys[i], "-ori$")) { + originalIndex.push_back(i); } } - if (barandtime[i].size() < 2) { - tsigdur.at(i) = active; - continue; - } - active = getTimeSigDuration(barandtime.at(i).at(1)); - tsigdur.at(i) = active; } - 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 (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 (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))); } } } - // check for first-measure pickup - if (firstmeasure >= 0) { - if (bardur.at(firstmeasure) < tsigdur.at(firstmeasure)) { - pickup.at(firstmeasure) = true; - } - } - - 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"; + 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))); } - cerr << endl; - } - cerr << endl; - } - - // Markup pickup measure notes/rests - for (int i=0; i<(int)pickup.size() - 1; i++) { - if (!pickup[i]) { - continue; } - markPickupContent(barandtime.at(i).at(0), barandtime.at(i+1).at(0)); } - - // Pickup/incomplete measures covering three or more barlines are not considered - // (these could be used with dashed barlines or similar). - } ////////////////////////////// // -// Tool_meter::markPickupContent -- +// Tool_modori::getPairedReference -- // -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(); +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; } } - if (current == etok) { - break; + ii = index - i; + if (ii >= 0) { + if (tkey == keys.at(ii)) { + return ii; + } } - current = current->getNextToken(); } + return -1; } ////////////////////////////// // -// Tool_meter::getTimeSigDuration -- +// Tool_modori::switchModernOriginal -- // -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; - } - return output; -} - +void Tool_modori::switchModernOriginal(HumdrumFile& infile) { + set changed; -////////////////////////////// -// -// Tool_meter::printMeterData -- -// + 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); + } + } + } + } -void Tool_meter::printMeterData(HumdrumFile& infile) { - bool foundLabel = false; - bool foundData = false; + 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); + } + } + } + } - for (int i=0; isecond.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); + } + } } + } - if ((!foundData) && (!foundLabel) && (!m_nolabelQ) && infile[i].isData()) { - printLabelLine(infile[i]); - foundData = true; + 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(); } + } - bool hasLabel = false; - if ((!m_nolabelQ) && (!foundLabel) && (!foundData)) { - if (searchForLabels(infile[i])) { - hasLabel = true; - foundLabel = true; + 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); } } - printHumdrumLine(infile[i], hasLabel); } -} + 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(); -////////////////////////////// -// -// printLabelLine -- -// + 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(); + + text = *second; + hre.replaceDestructive(text, "-mod:", ":"); + second->setText(text); + infile[second->getLineIndex()].createLineFromTokens(); + } + } -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"; } } - m_humdrum_text << "\n"; -} - - - -////////////////////////////// -// -// Tool_meter::printHumdrumLine -- -// -void Tool_meter::printHumdrumLine(HumdrumLine& line, bool printLabels) { + // 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. - for (int i=0; iisKern()) { - i = printKernAndAnalysisSpine(line, i, printLabels); - } else { - m_humdrum_text << token; + 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); + } + } + } } - if (i < line.getFieldCount() - 1) { - m_humdrum_text << "\t"; + } + + 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); + } + } } } - m_humdrum_text << "\n"; -} + for (auto it = changed.begin(); it != changed.end(); ++it) { + int line = *it; + infile[line].createLineFromTokens(); + } + + updateLoMo(infile); +} ////////////////////////////// // -// Tool_meter::searchForLabels -- +// Tool_modori::printModoriOutput -- // -bool Tool_meter::searchForLabels(HumdrumLine& line) { - if (!line.isInterpretation()) { - return false; - } - HumRegex hre; - for (int i=0; igetValue("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_meter::getHumNumString -- +// Tool_modori::updateLoMo -- // -string Tool_meter::getHumNumString(HumNum input) { - stringstream tem; - input.printTwoPart(tem); - return tem.str(); +void Tool_modori::updateLoMo(HumdrumFile& infile) { + for (int i=0; i<(int)m_lomo.size(); i++) { + processLoMo(m_lomo[i]); + } } ////////////////////////////// // -// Tool_meter::printKernAndAnalysisSpine -- +// Tool_modori::processLoMo -- // -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 (counter > 0) { - m_humdrum_text << "\t"; - } - counter++; - if (forceInterpretation) { - m_humdrum_text << "*"; - } else { - m_humdrum_text << token; - } +void Tool_modori::processLoMo(HTp lomo) { + HumRegex hre; - 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, ",", "\\."); + 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; } - } else { - analysis = getHumNumString(value); + break; } } - } 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 = ""; - } - } + 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 (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_denominatorQ) { - m_humdrum_text << "\t" << denominator; + } 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 (!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"; - } - } - } else { - if (!m_nobeatQ) { - m_humdrum_text << "\t" << analysis; - if (line.isData() && hasRest && !hasNote) { - m_humdrum_text << "r"; - } - } - 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) { + 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 index + counter - 1; } ////////////////////////////// // -// Tool_meter::getMeterData -- +// Tool_modori::flipMensurationStyle -- Returns true if swapped. // -void Tool_meter::getMeterData(HumdrumFile& infile) { - - int maxtrack = infile.getMaxTrack(); - vector curNum(maxtrack + 1, 0); - vector curDen(maxtrack + 1, 0); - vector curBeat(maxtrack + 1, 0); - vector curBarTime(maxtrack + 1, 0); - - 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 output; } ////////////////////////////// // -// Tool_meter::processLine -- +// Tool_modori::swapKeyStyle -- Returns true if swapped. // -void Tool_meter::processLine(HumdrumLine& line, vector& curNum, - vector& curDen, vector& curBeat, - vector& curBarTime) { - - int fieldCount = line.getFieldCount(); +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 (!line.hasSpines()) { - return; + if (one->isKeySignature()) { + ktype1 = true; + } else if (one->isModernKeySignature()) { + mtype1 = true; + } else if (one->isOriginalKeySignature()) { + otype1 = true; } - 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 (two->isKeySignature()) { + ktype2 = true; + } else if (two->isModernKeySignature()) { + mtype2 = true; + } else if (two->isOriginalKeySignature()) { + otype2 = true; } - 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); - } + 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; } - return; } + return output; +} - 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; - } - } - value.str(""); - value << qq; - token->setValue("auto", "zeroBeat", value.str()); - token->setValue("auto", "hasData", 1); +////////////////////////////// +// +// Tool_modori::swapInstrumentNameStyle -- Returns true if swapped. +// + +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; + + if (one->isInstrumentName()) { + ktype1 = true; + } else if (one->isModernInstrumentName()) { + mtype1 = true; + } else if (one->isOriginalInstrumentName()) { + otype1 = true; + } + + if (two->isInstrumentName()) { + ktype2 = true; + } else if (two->isModernInstrumentName()) { + mtype2 = true; + } else if (two->isOriginalInstrumentName()) { + otype2 = true; + } + 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; + } + } 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; } + return output; } - -///////////////////////////////// +////////////////////////////// // -// Tool_gridtest::Tool_metlev -- Set the recognized options for the tool. +// Tool_modori::swapInstrumentAbbreviationStyle -- Returns true if swapped. // -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. -// +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_metlev::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_metlev::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - return run(infile, out); -} + if (two->isInstrumentAbbreviation()) { + ktype2 = true; + } else if (two->isModernInstrumentAbbreviation()) { + mtype2 = true; + } else if (two->isOriginalInstrumentAbbreviation()) { + otype2 = true; + } -bool Tool_metlev::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - out << infile; - return status; + 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 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::swapMensurationStyle -- Returns true if swapped. +// - m_kernspines = infile.getKernSpineStartList(); +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; - 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->isMensuration()) { + ktype1 = true; + } else if (one->isModernMensuration()) { + mtype1 = true; + } else if (one->isOriginalMensuration()) { + otype1 = true; } - infile.getMetricLevels(beatlev, track, NAN); - for (int i=0; iisMensuration()) { + ktype2 = true; + } else if (two->isModernMensuration()) { + mtype2 = true; + } else if (two->isOriginalMensuration()) { + 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; + if (m_modernQ) { + if (ktype1 && mtype2) { + convertMensurationToOriginal(one); + convertMensurationToRegular(two); + output = true; + } else if (mtype1 && ktype2) { + convertMensurationToRegular(one); + convertMensurationToOriginal(two); + output = 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); + } else if (m_originalQ) { + if (ktype1 && otype2) { + convertMensurationToModern(one); + convertMensurationToRegular(two); + output = true; + } else if (otype1 && ktype2) { + convertMensurationToRegular(one); + convertMensurationToModern(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::swapClefStyle -- Returns true if swapped. // -void Tool_metlev::fillVoiceResults(vector >& results, - HumdrumFile& infile, vector& beatlev) { +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; - 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->isClef()) { + ktype1 = true; + } else if (one->isModernClef()) { + mtype1 = true; + } else if (one->isOriginalClef()) { + 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->isClef()) { + ktype2 = true; + } else if (two->isModernClef()) { + mtype2 = true; + } else if (two->isOriginalClef()) { + 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 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; } - 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) { + convertClefToModern(one); + convertClefToRegular(two); + output = true; + } else if (otype1 && ktype2) { + convertClefToRegular(one); + convertClefToModern(two); + output = true; } } + return output; } - -///////////////////////////////// +////////////////////////////// // -// Tool_modori::Tool_modori -- Set the recognized options for the tool. +// Tool_modori::convertKeySignatureToModern -- // -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"); +void Tool_modori::convertKeySignatureToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?k(.*)")) { + string text = "*mk"; + text += hre.getMatch(1); + token->setText(text); + } } -///////////////////////////////// +////////////////////////////// // -// Tool_modori::run -- Do the main work of the tool. +// Tool_modori::convertInstrumentNameToModern -- // -bool Tool_modori::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; isetText(text); } - 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; - } - return status; -} +////////////////////////////// +// +// Tool_modori::convertInstrumentAbbreviationToModern -- +// -bool Tool_modori::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; +void Tool_modori::convertInstrumentAbbreviationToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I'(.*)")) { + string text = "*mI'"; + text += hre.getMatch(1); + token->setText(text); } - return status; } -bool Tool_modori::run(HumdrumFile& infile) { - initialize(); - processFile(infile); - return true; + +////////////////////////////// +// +// 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); + } } ////////////////////////////// // -// Tool_modori::initialize -- Initializations that only have to be done once -// for all HumdrumFile segments. +// Tool_modori::convertInstrumentNameToOriginal -- // -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; +void Tool_modori::convertInstrumentNameToOriginal(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I\"(.*)")) { + string text = "*oI\""; + text += hre.getMatch(1); + token->setText(text); } - 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_modori::processFile -- +// Tool_modori::convertInstrumentAbbreviationToOriginal -- // -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(); - - 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); - +void Tool_modori::convertInstrumentAbbreviationToOriginal(HTp token) { HumRegex hre; - int exinterpLine = -1; - - 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); + if (hre.search(token, "^\\*[mo]?I'(.*)")) { + string text = "*oI'"; + text += hre.getMatch(1); + token->setText(text); + } +} - } 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::convertKeySignatureToRegular -- +// - } 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::convertKeySignatureToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?k(.*)")) { + string text = "*k"; + 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::convertInstrumentNameToRegular -- +// - if (!(m_modernQ || m_originalQ)) { - // nothing to do - return; +void Tool_modori::convertInstrumentNameToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I\"(.*)")) { + string text = "*I\""; + text += hre.getMatch(1); + token->setText(text); } - - switchModernOriginal(infile); - printModoriOutput(infile); } + ////////////////////////////// // -// Tool_modori::processExclusiveInterpretationLine -- +// Tool_modori::convertInstrumentAbbreviationToRegular -- // -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::convertInstrumentAbbreviationToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?I'(.*)")) { + string text = "*I'"; + 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]); - } - changed |= processStaffSpines(staffish); +////////////////////////////// +// +// Tool_modori::convertClefToModern -- +// - if (changed) { - infile[line].createLineFromTokens(); +void Tool_modori::convertClefToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?clef(.*)")) { + string text = "*mclef"; + text += hre.getMatch(1); + token->setText(text); } } @@ -99563,510 +103492,367 @@ void Tool_modori::processExclusiveInterpretationLine(HumdrumFile& infile, int li ////////////////////////////// // -// Tool_modori::processStaffSpines -- +// Tool_modori::convertClefToOriginal -- // -bool Tool_modori::processStaffSpines(vector& tokens) { - +void Tool_modori::convertClefToOriginal(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]?clef(.*)")) { + string text = "*oclef"; + text += hre.getMatch(1); + token->setText(text); } - - return changed; } ////////////////////////////// // -// Tool_modori::processStaffCompanionSpines -- +// Tool_modori::convertClefToRegular -- // -bool Tool_modori::processStaffCompanionSpines(vector tokens) { +void Tool_modori::convertClefToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?clef(.*)")) { + string text = "*clef"; + text += hre.getMatch(1); + token->setText(text); + } +} - 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]); - } - } - bool gchanged = false; +////////////////////////////// +// +// Tool_modori::convertMensurationToModern -- +// - if (mods.empty() && oris.empty()) { - // Nothing to do. - return false; +void Tool_modori::convertMensurationToModern(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?met\\((.*)")) { + string text = "*mmet("; + text += hre.getMatch(1); + token->setText(text); } +} - // mods and oris should not be mixed, so if there are no - // other spines, then also give up: - if (other.empty()) { - return false; - } - if (m_modernQ) { - bool changed = false; - // Swap (**mod-XXX and **XXX) to (**XXX and **ori-XXX) +////////////////////////////// +// +// Tool_modori::convertMensurationToOriginal -- +// - 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; - } - } +void Tool_modori::convertMensurationToOriginal(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?met\\((.*)")) { + string text = "*omet("; + text += hre.getMatch(1); + token->setText(text); + } +} - } 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; - } - } - } - return gchanged; +////////////////////////////// +// +// Tool_modori::convertMensurationToRegular -- +// + +void Tool_modori::convertMensurationToRegular(HTp token) { + HumRegex hre; + if (hre.search(token, "^\\*[mo]?met\\((.*)")) { + string text = "*met("; + text += hre.getMatch(1); + token->setText(text); + } } -////////////////////////////// +//////////////////// // -// Tool_modori::storeModOriReferenceRecors -- +// Tool_modori::printInfo -- // -void Tool_modori::storeModOriReferenceRecords(HumdrumFile& infile) { - m_references.clear(); +void Tool_modori::printInfo(void) { + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! KEYS:" << endl; - 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; + 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; + } } - vector modernIndex; - vector originalIndex; + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! CLEFS:" << endl; - 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); + 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_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))); + 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_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))); - } - } + 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; + } + + 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; } + + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; + m_humdrum_text << "!! REFERENCES:" << endl; + + 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"; + } + + m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; } + ////////////////////////////// // -// Tool_modori::getPairedReference -- +// SonorityDatabase::buildDatabase -- // -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 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; - 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; - } + for (int i=0; igetFieldCount(); i++) { + HTp token = m_line->token(i); + if (!token->isKern()) { + continue; } - ii = index - i; - if (ii >= 0) { - if (tkey == keys.at(ii)) { - return ii; + 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 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); - } - } - } - } - 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); - } - } - } - } +////////////////////////////// +// +// MSearchQueryToken::parseHarmonicQuery -- +// - 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); - } - } - } +void MSearchQueryToken::parseHarmonicQuery(void) { + if (!hpieces.empty()) { + // do not reparse + return; } - - 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(); + for (int i=0; i<(int)harmonic.size(); i++) { + char ch = tolower(harmonic[i]); + if (ch >= '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; } } - 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); - } - } + hquery.resize(hpieces.size()); + for (int i=0; i<(int)hpieces.size(); i++) { + hquery[i].setString(hpieces[i]); } +} - 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 (m_originalQ) { - if (hre.search(first, "^!!![^:]*?-ori:")) { - string text = *first; - hre.replaceDestructive(text, ":", "-...:"); - first->setText(text); - infile[first->getLineIndex()].createLineFromTokens(); +///////////////////////////////// +// +// Tool_msearch::Tool_msearch -- Set the recognized options for the tool. +// - text = *second; - hre.replaceDestructive(text, "-mod:", ":"); - second->setText(text); - infile[second->getLineIndex()].createLineFromTokens(); - } - } +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"); +} - } - } - // 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); - } - } - } - } - } +///////////////////////////////// +// +// Tool_msearch::run -- Do the main work of the tool. +// - 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); - } - } - } +bool Tool_msearch::run(HumdrumFileSet& infiles) { + bool status = true; + for (int i=0; i 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::updateLoMo -- +// Tool_msearch::initialize -- // -void Tool_modori::updateLoMo(HumdrumFile& infile) { - for (int i=0; i<(int)m_lomo.size(); i++) { - processLoMo(m_lomo[i]); +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]; } } @@ -100074,87 +103860,17 @@ void Tool_modori::updateLoMo(HumdrumFile& infile) { ////////////////////////////// // -// Tool_modori::processLoMo -- +// Tool_msearch::fillWords -- // -void Tool_modori::processLoMo(HTp lomo) { - HumRegex hre; - - 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 (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(); - } - } +void Tool_msearch::fillWords(HumdrumFile& infile, vector& words) { + vector textspines; + infile.getSpineStartList(textspines, "**silbe"); + if (textspines.empty()) { + infile.getSpineStartList(textspines, "**text"); + } + for (int i=0; i<(int)textspines.size(); i++) { + fillWordsForTrack(words, textspines[i]); } } @@ -100162,339 +103878,324 @@ void Tool_modori::processLoMo(HTp lomo) { ////////////////////////////// // -// Tool_modori::flipMensurationStyle -- Returns true if swapped. +// Tool_msearch::fillWordsForTrack -- // -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::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; + } + 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::swapKeyStyle -- Returns true if swapped. +// Tool_msearch::doTextSearch -- do a basic text search of all parts. // -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; +void Tool_msearch::doTextSearch(HumdrumFile& infile, NoteGrid& grid, + vector& query) { - if (one->isKeySignature()) { - ktype1 = true; - } else if (one->isModernKeySignature()) { - mtype1 = true; - } else if (one->isOriginalKeySignature()) { - otype1 = true; - } + vector words; + words.reserve(10000); + fillWords(infile, words); + int tcount = 0; - if (two->isKeySignature()) { - ktype2 = true; - } else if (two->isModernKeySignature()) { - mtype2 = true; - } else if (two->isOriginalKeySignature()) { - 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 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; + 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) { - convertKeySignatureToModern(one); - convertKeySignatureToRegular(two); - output = true; - } else if (otype1 && ktype2) { - convertKeySignatureToRegular(one); - convertKeySignatureToModern(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::swapInstrumentNameStyle -- Returns true if swapped. +// Tool_msearch::printQuery -- // -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; - - if (one->isInstrumentName()) { - ktype1 = true; - } else if (one->isModernInstrumentName()) { - mtype1 = true; - } else if (one->isOriginalInstrumentName()) { - otype1 = true; - } - - if (two->isInstrumentName()) { - ktype2 = true; - } else if (two->isModernInstrumentName()) { - mtype2 = true; - } else if (two->isOriginalInstrumentName()) { - otype2 = true; - } - - 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; - } - } 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; - } +void Tool_msearch::printQuery(vector& query) { + for (int i=0; i<(int)query.size(); i++) { + cout << query[i]; } - return output; } ////////////////////////////// // -// Tool_modori::swapInstrumentAbbreviationStyle -- Returns true if swapped. +// Tool_msearch::doMusicSearch -- do a basic melodic search of all parts. // -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::doMusicSearch(HumdrumFile& infile, NoteGrid& grid, + vector& query) { - if (one->isInstrumentAbbreviation()) { - ktype1 = true; - } else if (one->isModernInstrumentAbbreviation()) { - mtype1 = true; - } else if (one->isOriginalInstrumentAbbreviation()) { - otype1 = true; + 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 +104203,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 +104278,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 +104325,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,790 +104383,1192 @@ 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; + } -////////////////////////////// -// -// Tool_modori::convertKeySignatureToRegular -- -// + if (currindex < 0) { + cerr << "STRANGE NEGATIVE INDEX " << currindex << endl; + break; + } -void Tool_modori::convertKeySignatureToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?k(.*)")) { - string text = "*k"; - text += hre.getMatch(1); - token->setText(text); - } -} + // 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; + } + } -////////////////////////////// -// -// Tool_modori::convertInstrumentNameToRegular -- -// + ////////////////////////////// + // + // INTERVALS + // -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 (query[i].dinterval > -1000) { + // match to a specific diatonic interval to the next note + double currpitch; + double nextpitch; + currpitch = notes[currindex]->getAbsDiatonicPitch(); -////////////////////////////// -// -// Tool_modori::convertInstrumentAbbreviationToRegular -- -// + if (nextindex >= 0) { + nextpitch = notes[nextindex]->getAbsDiatonicPitch(); + } 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].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; -////////////////////////////// -// -// Tool_modori::convertClefToModern -- -// + currpitch = notes[currindex]->getAbsBase40Pitch(); -void Tool_modori::convertClefToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?clef(.*)")) { - string text = "*mclef"; - text += hre.getMatch(1); - token->setText(text); - } -} + 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); + if (interval != query[i].cinterval) { + match.clear(); + return false; + } -////////////////////////////// -// -// Tool_modori::convertClefToOriginal -- -// + } else if (!query[i].anyinterval) { -void Tool_modori::convertClefToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?clef(.*)")) { - string text = "*oclef"; - text += hre.getMatch(1); - token->setText(text); - } -} + double currpitch; + double nextpitch; + double lastpitch; + currpitch = notes[currindex]->getAbsDiatonicPitchClass(); + if (nextindex >= 0) { + nextpitch = notes[nextindex]->getAbsDiatonicPitchClass(); + } else { + nextpitch = -123456789.0; + } -////////////////////////////// -// -// Tool_modori::convertClefToRegular -- -// + if (lastindex >= 0) { + lastpitch = notes[nextindex]->getAbsDiatonicPitchClass(); + } else { + lastpitch = -987654321.0; + } -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].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; + } + 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; + } + } + } + } + } -////////////////////////////// -// -// Tool_modori::convertMensurationToModern -- -// + ////////////////////////////// + // + // PITCH + // -void Tool_modori::convertMensurationToModern(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?met\\((.*)")) { - string text = "*mmet("; - text += hre.getMatch(1); - token->setText(text); - } -} + 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; + } + 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; + } + } -////////////////////////////// -// -// Tool_modori::convertMensurationToOriginal -- -// + if (!query[i].harmonic.empty()) { + query[i].parseHarmonicQuery(); + bool status = doHarmonicPitchSearch(query[i], notes[currindex]->getToken()); + if (!status) { + return false; + } + } -void Tool_modori::convertMensurationToOriginal(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?met\\((.*)")) { - string text = "*omet("; - text += hre.getMatch(1); - token->setText(text); + // 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); } + + return true; } ////////////////////////////// // -// Tool_modori::convertMensurationToRegular -- +// Tool_msearch::doHarmonicPitchSearch -- // -void Tool_modori::convertMensurationToRegular(HTp token) { - HumRegex hre; - if (hre.search(token, "^\\*[mo]?met\\((.*)")) { - string text = "*met("; - 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()); + } + bool exactQ = false; + bool onlyQ = false; -//////////////////// -// -// Tool_modori::printInfo -- -// + if (query.harmonic.find("==") != string::npos) { + exactQ = true; + } else if (query.harmonic.find("=") != string::npos) { + onlyQ = true; + } -void Tool_modori::printInfo(void) { - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! KEYS:" << 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); - 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; - } + for (int i=0; ifirst; - for (int j=0; j<(int)it->second.size(); ++j) { - m_humdrum_text << '\t' << it->second.at(j); + int sum = 0; + for(int i=0; i<(int)query.hquery.size(); i++) { + if (query.hquery[i].hasAccidental()) { + diatonicCountsQuery.at(query.hquery[i].getBase7Pc())++; + if (query.hquery[i].hasUpperCase()) { + if (query.hquery[i].getBase7Pc() != sonorities.getLowest().getBase7Pc()) { + return false; + } } - m_humdrum_text << endl; - } - } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! MENSURATIONS:" << endl; + // Don't check for same pitch-class twice: + if (chromaticCountsMatch.at(query.hquery[i].getBase40Pc())) { + continue; + } + } else { + diatonicCountsQuery.at(query.hquery[i].getBase7Pc())++; + if (query.hquery[i].hasUpperCase()) { + if (query.hquery[i].getBase7Pc() != sonorities.getLowest().getBase7Pc()) { + return false; + } + } - 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); + // Don't check for same pitch-class twice: + if (diatonicCountsMatch.at(query.hquery[i].getBase7Pc())) { + continue; } - m_humdrum_text << endl; } - } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! LYRICS:" << endl; + int status = checkHarmonicPitchMatch(query.hquery[i], sonorities, false); - 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 (!status) { + return false; + } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! TEXT:" << endl; + if (query.hquery[i].hasAccidental()) { + chromaticCountsMatch.at(query.hquery[i].getBase40Pc()) += status; + } else { + diatonicCountsMatch.at(query.hquery[i].getBase7Pc()) += status; + } + sum += status; - for (int i=0; i<(int)m_lotext.size(); i++) { - m_humdrum_text << "!!\t" << m_lotext[i] << endl; } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; - m_humdrum_text << "!! REFERENCES:" << endl; - - 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"; + if ((!exactQ) && (!onlyQ)) { + return true; } - m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; -} - - - -////////////////////////////// -// -// SonorityDatabase::buildDatabase -- -// - -void SonorityDatabase::buildDatabase(HLp line) { - clear(); - if (line == NULL) { - return; - } - m_line = line; - bool nullQ = false; - if (!line->isData()) { - return; + if (exactQ && (sum != sonorities.getNoteCount())) { + return false; } - int lowesti = 0; - int lowest12 = 1000; - 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 (exactQ) { + for (int i=0; i<(int)diatonicCountsMatch.size(); i++) { + if (diatonicCountsMatch[i] != diatonicCountsQuery[i]) { + return false; + } } - if (token->isNull()) { - nullQ = true; - token = token->resolveNull(); + for (int i=0; i<(int)chromaticCountsMatch.size(); i++) { + if (chromaticCountsMatch[i] != chromaticCountsQuery[i]) { + return false; + } } - if (token->isNull()) { - continue; + } else if (onlyQ) { + SonorityDatabase son2; + for (int i=0; i<(int)query.hpieces.size(); i++) { + son2.addNote(query.hpieces[i]); } - 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; +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; + } } } - hquery.resize(hpieces.size()); - for (int i=0; i<(int)hpieces.size(); i++) { - hquery[i].setString(hpieces[i]); + 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& query, + const string& input) { + query.clear(); + bool inquote = false; + + 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::run -- Do the main work of the tool. +// Tool_msearch::fillMusicQuery -- // -bool Tool_msearch::run(HumdrumFileSet& infiles) { - bool status = true; - for (int i=0; i& query) { + query.clear(); + + string qinput; + string pinput; + string iinput; + string rinput; + + if (getBoolean("query")) { + qinput = getString("query"); } - return status; -} + if (getBoolean("pitch")) { + pinput = getString("pitch"); + m_verticalOnlyQ = checkVerticalOnly(pinput); + } -bool Tool_msearch::run(const string& indata, ostream& out) { - HumdrumFile infile(indata); - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (getBoolean("interval")) { + iinput = getString("interval"); } - return status; -} + if (getBoolean("rhythm")) { + rinput = getString("rhythm"); + } -bool Tool_msearch::run(HumdrumFile& infile, ostream& out) { - bool status = run(infile); - if (hasAnyText()) { - getAllText(out); - } else { - out << infile; + if (!rinput.empty()) { + fillMusicQueryRhythm(query, rinput); } - return status; -} + if (!qinput.empty()) { + fillMusicQueryInterleaved(query, qinput); + } -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; + if (!pinput.empty()) { + fillMusicQueryPitch(query, pinput); } - initialize(); - if (getBoolean("text")) { - m_text = getString("text"); + if (!iinput.empty()) { + fillMusicQueryInterval(query, iinput); } - if (m_text.empty()) { - vector 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, "^"); + } + if (input[i-1] == 'v') { + pitches.back().insert(0, "v"); } } - tok = tok->getNextToken(); continue; + } + if (!pitches.empty()) { + if (ch == 'n') { + pitches.back() += 'n'; + } else if (ch == '-') { + pitches.back() += '-'; + } else if (ch == '#') { + pitches.back() += '#'; + } + } + } + + if (pitches.size() <= 1) { + return ""; + } + + 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 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 { - // start a new word - TextInfo* temp = new TextInfo(); - temp->nexttoken = NULL; - if (!words.empty()) { - words.back()->nexttoken = tok; + p1 = Convert::kernToBase7(pitches[i]); + p2 = Convert::kernToBase7(pitches[i+1]); + base7 = p2 - p1; + sign = base7 < 0 ? -1 : +1; + if (sign < 0) { + base7 = -base7; } - temp->fullword = *tok; - if (!temp->fullword.empty()) { - if (temp->fullword.back() == '-') { - temp->fullword.pop_back(); - } + string value = ""; + if (sign < 0) { + value += "-"; } - temp->starttoken = tok; - words.push_back(temp); - tok = tok->getNextToken(); - continue; + value += to_string(base7 + 1); + output += value; + output += " "; + } + } + + if (output.size() > 0) { + if (output.back() == ' ') { + output.resize((int)output.size() - 1); } } + + return output; } ////////////////////////////// // -// Tool_msearch::doTextSearch -- do a basic text search of all parts. +// Tool_msearch::fillMusicQueryInterval -- // -void Tool_msearch::doTextSearch(HumdrumFile& infile, NoteGrid& grid, - vector& query) { +void Tool_msearch::fillMusicQueryInterval(vector& query, + const string& input) { - vector words; - words.reserve(10000); - fillWords(infile, words); - int tcount = 0; + string newinput = convertPitchesToIntervals(input); - 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]); - } - } + char ch; + int counter = 0; + MSearchQueryToken temp; + MSearchQueryToken *active = &temp; + + if (query.size() > 0) { + active = &query.at(counter); + } else { + // what is this for? } - 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++; + int sign = 1; + string alteration; + for (int i=0; i<(int)newinput.size(); i++) { + ch = newinput[i]; + if (ch == ' ') { + // skip over spaces + continue; } - } - if (silbecount > 0) { - // giving priority to **silbe content - textinterp = "**silbe"; - } + 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"; + } + continue; + } + if ((ch == 'M') || (ch == 'm')) { + alteration = ch; + continue; + } + if (ch == '-') { + sign = -1; + continue; + } + if (ch == '+') { + sign = +1; + continue; + } + ch = tolower(ch); - if (tcount && m_markQ) { - string content = "!!!RDF"; - content += textinterp; - content += ": "; - content += m_marker; - content += " = marked text"; - if (getBoolean("color")) { - content += ", color=\"" + getString("color") + "\""; + if (!isdigit(ch)) { + // skip over non-digits (sign of interval + // will be read retroactively). + continue; } - infile.appendLine(content); - infile.createLinesFromTokens(); - } - for (int i=0; i<(int)words.size(); i++) { - delete words[i]; - words[i] = NULL; - } + // check for intervals. Intervals will trigger a + // new element in the query list - if (!m_quietQ) { - addTextSearchSummary(infile, tcount, m_marker); + 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; + } } -} - - - -////////////////////////////// -// -// Tool_msearch::printQuery -- -// -void Tool_msearch::printQuery(vector& query) { - for (int i=0; i<(int)query.size(); i++) { - cout << query[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(); } + } ////////////////////////////// // -// Tool_msearch::doMusicSearch -- do a basic melodic search of all parts. +// Tool_msearch::makeBase40Interval -- // -void Tool_msearch::doMusicSearch(HumdrumFile& infile, NoteGrid& grid, - vector& query) { - - m_matches.clear(); - - if (m_debugQ) { - printQuery(query); +int Tool_msearch::makeBase40Interval(int diatonic, const string& alteration) { + int sign = 1; + if (diatonic < 0) { + sign = -1; + diatonic = -diatonic; } - - 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 (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; } - 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 ((!alteration.empty()) && (alteration[0] == 'A')) { + if (alteration.size() <= 2) { + base40 += (int)alteration.size(); + } else { + cerr << "TOO MUCH AUGMENTED, IGNORING" << endl; } } - } - - if (mcount && m_markQ) { - string content = "!!!RDF**kern: " + m_marker + " = marked note"; - if (getBoolean("color")) { - content += ", color=\"" + getString("color") + "\""; + } 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(content); - infile.createLinesFromTokens(); - } - if (!m_quietQ) { - addMusicSearchSummary(infile, mcount, m_marker); } + base40 *= sign; + return base40; } ////////////////////////////// // -// Tool_msearch::addMusicSearchSummary -- +// Tool_msearch::fillMusicQueryInterleaved -- // -void Tool_msearch::addMusicSearchSummary(HumdrumFile& infile, int mcount, const string& marker) { - - m_barnums = infile.getMeasureNumbers(); +void Tool_msearch::fillMusicQueryInterleaved(vector& query, + const string& input, bool rhythmQ) { - infile.appendLine("!!@@BEGIN: MUSIC_SEARCH_RESULT"); - string line; + string newinput = input; + char ch; + int counter = 0; + MSearchQueryToken temp; + MSearchQueryToken *active = &temp; + string paren; - line = "!!@QUERY:\t"; + if (query.size() > 0) { + active = &query.at(counter); + } else { + // what is this for? + } - 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; + 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; } - } - 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 (ch == '=') { + continue; + } + if (ch == ' ') { + // skip over multiple spaces + if (i > 0) { + if (newinput[i-1] == ' ') { + continue; + } + } } - } - 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; + 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("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; + // 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 music match location here. - for (int i=0; i<(int)m_matches.size(); i++) { - addMatch(infile, m_matches[i]); + // Convert rhythms to durations + for (int i=0; i<(int)query.size(); i++) { + if (query[i].anyrhythm) { + continue; + } + if (query[i].rhythm.empty()) { + continue; + } + query[i].duration = Convert::recipToDuration(query[i].rhythm); } - infile.appendLine("!!@@END: MUSIC_SEARCH_RESULT"); + // 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::addMatch -- -// -// Todo: -// * add duration of match +// checkVerticalOnly -- // -void Tool_msearch::addMatch(HumdrumFile& infile, vector& match) { - if (match.empty()) { - return; +bool Tool_msearch::checkVerticalOnly(const string& input) { + if (input.empty()) { + return false; } - if (match.back() == NULL) { - // strange problem - return; + if (input.size() < 2) { + return false; } - 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 (input[0] != '(') { + return false; } - infile.appendLine(measure); - - infile.appendLine("!!@@END:\tMATCH"); + 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; + } + if (input[i] == ')') { + return false; + } + } + return true; } ////////////////////////////// // -// Tool_msearch::makeLowerCase -- +// Tool_msearch::storeMatch -- Store a search result for later printing +// in the input file footer. // -void Tool_msearch::makeLowerCase(string& inout) { - for (int i=0; i<(int)inout.size(); i++) { - inout[i] = tolower(inout[i]); +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); } } @@ -101357,2787 +105576,2932 @@ void Tool_msearch::makeLowerCase(string& inout) { ////////////////////////////// // -// Tool_msearch::addTextSearchSummary -- +// operator<< -- print MSearchQueryToken item. // -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); +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; } - - // Print match location here. - infile.appendLine("!!@@END: TEXT_SEARCH_RESULT"); + return out; } ////////////////////////////// // -// Tool_msearch::markNote -- +// Tool_musedata2hum::Tool_musedata2hum -- // -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); - } +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"); } ////////////////////////////// // -// Tool_msearch::markMatch -- assumes monophonic music. +// initialize -- // -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; - } - } +void Tool_musedata2hum::initialize(void) { + m_stemsQ = getBoolean("stems"); + m_recipQ = getBoolean("recip"); + m_group = getString("group"); + m_noOmvQ = getBoolean("no-omv"); } ////////////////////////////// // -// Tool_msearch::markTextMatch -- assumes monophonic voices. +// Tool_musedata2hum::setOptions -- // -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_musedata2hum::setOptions(int argc, char** argv) { + m_options.process(argc, argv); +} - //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(); - } +void Tool_musedata2hum::setOptions(const vector& argvlist) { + m_options.process(argvlist); } ////////////////////////////// // -// Tool_msearch::checkForMusicMatch -- See if the given position -// in the music matches the query. +// Tool_musedata2hum::getOptionDefinitions -- Used to avoid +// duplicating the definitions in the test main() function. // -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; - } +Options Tool_musedata2hum::getOptionDefinitions(void) { + return m_options; +} - 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; - } +////////////////////////////// +// +// Tool_musedata2hum::convert -- Convert a MusicXML file into +// Humdrum content. +// - // If the query item can be anything, it automatically matches: - if (query[i].anything) { - match.push_back(notes[currindex]); - continue; - } +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); +} - ////////////////////////////// - // - // RHYTHM - // - if (!query[i].anyrhythm) { - if (notes[currindex]->getDuration() != query[i].duration) { - match.clear(); - return false; - } - } +bool Tool_musedata2hum::convert(ostream& out, istream& input) { + MuseDataSet mds; + mds.read(input); + return convert(out, mds); +} - ////////////////////////////// - // - // INTERVALS - // - if (query[i].dinterval > -1000) { - // match to a specific diatonic interval to the next note +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); +} - double currpitch; - double nextpitch; - currpitch = notes[currindex]->getAbsDiatonicPitch(); +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 (nextindex >= 0) { - nextpitch = notes[nextindex]->getAbsDiatonicPitch(); - } else { - nextpitch = -123456789.0; - } + m_tempo = mds.getMidiTempo(); - // maybe be careful of rests getting into this calculation: - int interval = (int)(nextpitch - currpitch); + vector groupMemberIndex = mds.getGroupIndexList(m_group); + if (groupMemberIndex.empty()) { + cerr << "Error: no files in the " << m_group << " membership." << endl; + return false; + } - 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 + HumGrid outdata; + bool status = true; + for (int i=0; i<(int)groupMemberIndex.size(); i++) { + status &= convertPart(outdata, mds, groupMemberIndex[i], i, (int)groupMemberIndex.size()); + } - double currpitch; - double nextpitch; + HumdrumFile outfile; + outdata.transferTokens(outfile); + outfile.generateLinesFromTokens(); + stringstream sss; + sss << outfile; + outfile.readString(sss.str()); - currpitch = notes[currindex]->getAbsBase40Pitch(); + if (needsAboveBelowKernRdf()) { + outfile.appendLine("!!!RDF**kern: > = above"); + outfile.appendLine("!!!RDF**kern: < = above"); + } - if (nextindex >= 0) { - nextpitch = notes[nextindex]->getAbsBase40Pitch(); - } else { - nextpitch = -123456789.0; - } + outfile.createLinesFromTokens(); - // maybe be careful of rests getting into this calculation: - int interval = (int)(nextpitch - currpitch); + Tool_trillspell trillspell; + trillspell.run(outfile); - if (interval != query[i].cinterval) { - match.clear(); - return false; + // 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 { + break; + } + } + if (hre.search(output, "!!!\\s*([^!:]+)\\s*:")) { + string key = hre.getMatch(1); + m_usedReferences[key] = true; + } + if (ending) { + m_postReferences.push_back(output); + } else { + out << output << endl; } + } + } - } else if (!query[i].anyinterval) { + if (!m_usedReferences["COM"]) { + string composer = mds[ii].getComposer(); + if (!composer.empty()) { + out << "!!!COM: " << composer << endl; + } + } - double currpitch; - double nextpitch; - double lastpitch; + if (!m_usedReferences["CDT"]) { + string cdate = mds[ii].getComposerDate(); + if (!cdate.empty()) { + out << "!!!CDT: " << cdate << endl; + } + } - currpitch = notes[currindex]->getAbsDiatonicPitchClass(); + if (!m_usedReferences["OTL"]) { + string worktitle = mds[ii].getWorkTitle(); + if (!worktitle.empty()) { + out << "!!!OTL: " << worktitle << endl; + } + } - if (nextindex >= 0) { - nextpitch = notes[nextindex]->getAbsDiatonicPitchClass(); - } else { - nextpitch = -123456789.0; + if (!m_noOmvQ) { + if (!m_usedReferences["OMV"]) { + string movementtitle = mds[ii].getMovementTitle(); + if (!movementtitle.empty()) { + out << "!!!OMV: " << movementtitle << endl; } + } + } - if (lastindex >= 0) { - lastpitch = notes[nextindex]->getAbsDiatonicPitchClass(); - } else { - lastpitch = -987654321.0; - } + if (!m_usedReferences["OPS"]) { + string opus = mds[ii].getOpus(); + if (!opus.empty()) { + out << "!!!OPS: " << opus << endl; + } + } - 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; - } + if (!m_usedReferences["ONM"]) { + string number = mds[ii].getNumber(); + if (!number.empty()) { + out << "!!!ONM: " << number << endl; + } + } - 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; - } + if (!m_usedReferences["OMD"]) { + if (!m_omd.empty()) { + out << "!!!OMD: " << m_omd << endl; + } + } + + bool foundDataQ = false; + for (int i=0; iisRest()) { - 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; - } + if (!m_usedReferences["ENC"]) { + string encoder = mds[ii].getEncoderName(); + if (!encoder.empty()) { + out << "!!!ENC: " << encoder << endl; + } + } + + if (!m_usedReferences["END"]) { + string edate = mds[ii].getEncoderDate(); + if (!edate.empty()) { + out << "!!!END: " << edate << endl; + } + } + + for (int i=0; i<(int)m_postReferences.size(); i++) { + out << m_postReferences[i] << endl; + } + m_postReferences.clear(); + + stringstream ss; + auto nowtime = std::chrono::system_clock::now(); + time_t currenttime = std::chrono::system_clock::to_time_t(nowtime); + ss << std::ctime(¤ttime); + out << "!!!ONB: Converted from MuseData with musedata2hum on " << ss.str(); - 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(); - } + string copyright = mds[ii].getCopyright(); + if (!copyright.empty()) { + out << "!!!YEM: " << copyright << endl; + } - if (qpitch != npitch) { - match.clear(); - return false; - } + // Convert comments in footer of last part: + int lastone = groupMemberIndex.back(); + vector outputs; + for (int i=mds[lastone].getLineCount() - 1; i>=0; i--) { + if (mds[lastone][i].isAnyNote()) { + break; } - - if (!query[i].harmonic.empty()) { - query[i].parseHarmonicQuery(); - bool status = doHarmonicPitchSearch(query[i], notes[currindex]->getToken()); - if (!status) { - 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); } - - // 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); + for (int i=(int)outputs.size() - 1; i>=0; i--) { + out << outputs[i] << endl; } - return true; + return status; } ////////////////////////////// // -// Tool_msearch::doHarmonicPitchSearch -- +// 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. // -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; +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++; + } } - m_sonoritiesChecked[lindex] = true; - SonorityDatabase& sonorities = m_sonorities[lindex]; - if (sonorities.isEmpty()) { - sonorities.buildDatabase(token->getLine()); + 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; +} - bool exactQ = false; - bool onlyQ = false; - 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); +////////////////////////////// +// +// Tool_musedata2hum::convertPart -- +// - for (int i=0; i= part.getLineCount()) { + endtime = part[i-1].getAbsBeat(); + } else { + endtime = part[i].getAbsBeat(); + } - for (int k=0; ksetDuration(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 true; + return i; } ////////////////////////////// // -// Tool_msearch::checkHarmonicPitchMatch -- Returns the number of matched notes. +// Tool_musedata2hum::setMeasureNumber -- // -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::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; } - } - - 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; isetMeasureNumber(-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::fillTextQuery -- +// Tool_musedata2hum::setMeasureStyle -- // -void Tool_msearch::fillTextQuery(vector& query, - const string& input) { - query.clear(); - bool inquote = false; - - query.resize(1); - - for (int i=0; i<(int)input.size(); i++) { - if (input[i] == '"') { - inquote = !inquote; - query.resize(query.size() + 1); - continue; +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 (isspace(input[i])) { - query.resize(query.size() + 1); + } else if (line.compare(0, 7, "mheavy3") == 0) { + if (barstyle.find("|:") != string::npos) { + gm->setStyle(MeasureStyle::RepeatForward); } - query.back().word.push_back(input[i]); - if (inquote) { - query.back().link = true; + } 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); } } - ////////////////////////////// // -// Tool_msearch::fillMusicQuery -- +// Tool_musedata2hum::convertLine -- // -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::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; } - if (getBoolean("pitch")) { - pinput = getString("pitch"); - m_verticalOnlyQ = checkVerticalOnly(pinput); + if (mr.isAnyNoteOrRest()) { + m_figureOffset = 0; } - if (getBoolean("interval")) { - iinput = getString("interval"); + if (mr.isDirection()) { + return; } - if (getBoolean("rhythm")) { - rinput = getString("rhythm"); - } + HumNum timestamp = mr.getAbsBeat(); + // cerr << "CONVERTING LINE " << timestamp << "\t" << mr << endl; + string tok; + GridSlice* slice = NULL; - if (!rinput.empty()) { - fillMusicQueryRhythm(query, rinput); + 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 (!attributes["Q"].empty()) { + m_quarterDivisions = std::stoi(attributes["Q"]); + } + + 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); + } + + 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); + } + } + } 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) { + // 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); + } 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; + } + } + } else if (mr.isDirection()) { + if (mr.isTextDirection()) { + addTextDirection(gm, part, staff, mr, timestamp); + } } +} - if (!qinput.empty()) { - fillMusicQueryInterleaved(query, qinput); - } - if (!pinput.empty()) { - fillMusicQueryPitch(query, pinput); - } +////////////////////////////// +// +// 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 +// - if (!iinput.empty()) { - fillMusicQueryInterval(query, iinput); +void Tool_musedata2hum::addDirectionDynamics(GridSlice* slice, int part, MuseRecord& mr) { + MuseRecord* direction = mr.getMusicalDirection(); + if (direction == NULL) { + return; } - if (query.size() == 1) { - if (query[0].anything) { - query.clear(); + 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_msearch::fillMusicQueryPitch -- +// Tool_musedata2hum::needsAboveBelowKernRdf -- Function name says it all. // -void Tool_msearch::fillMusicQueryPitch(vector& query, - const string& input) { - fillMusicQueryInterleaved(query, input); +bool Tool_musedata2hum::needsAboveBelowKernRdf(void) { + return m_aboveBelowKernRdf; } ////////////////////////////// // -// Tool_msearch::fillMusicQueryRhythm -- +// Tool_musedata2hum::addTextDirection -- // -void Tool_msearch::fillMusicQueryRhythm(vector& query, - const string& input) { - string output; - output.reserve(input.size() * 4); +void Tool_musedata2hum::addTextDirection(GridMeasure* gm, int part, int staff, + MuseRecord& mr, HumNum timestamp) { - for (int i=0; i<(int)input.size(); i++) { - output += input[i]; - output += ' '; + if (!mr.isTextDirection()) { + return; } - - // 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--; - } + 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; - fillMusicQueryInterleaved(query, output, true); + // add staff index later + gm->addLayoutParameter(NULL, part, output); -} +} ////////////////////////////// // -// Tool_msearch::convertPitchesToIntervals -- +// Tool_musedata2hum::addFiguredHarmony -- // -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 (tolower(input[i] == 'r')) { - // not allowing rests for now - return input; +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; } } - 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 (fh.find(":") == string::npos) { + HTp fhtok = new HumdrumToken(fh); + m_lastfigure = fhtok; + gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; + return; } - if (pitches.size() <= 1) { - return ""; + if (!m_lastfigure) { + HTp fhtok = new HumdrumToken(fh); + m_lastfigure = fhtok; + gm->addFiguredBass(fhtok, timestamp, part, maxstaff); + m_figureOffset += figureDuration; + return; } - 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; + // 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++; + } + if (fh[i] == ':') { + colpos = i; break; } } } - 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 += "-"; + 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]; } - 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 += "-"; + if (isspace(lastfh[i])) { + // do nothing + } else { + pieces.resize(pieces.size()+1); + pieces.back() += lastfh[i]; + state = 1; } - value += to_string(base7 + 1); - output += value; - output += " "; } } - if (output.size() > 0) { - if (output.back() == ' ') { - output.resize((int)output.size() - 1); + 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 += ' '; } } - return output; + 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; } ////////////////////////////// // -// Tool_msearch::fillMusicQueryInterval -- +// Tool_musedata2hum::addLyrics -- // -void Tool_msearch::fillMusicQueryInterval(vector& query, - const string& input) { +void Tool_musedata2hum::addLyrics(GridSlice* slice, int part, int staff, MuseRecord& mr) { + int versecount = mr.getVerseCount(); + if (versecount == 0) { + return; + } + for (int i=0; iat(part)->at(staff)->setVerse(i, verse); + } + slice->reportVerseCount(part, staff, versecount); +} - 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? - } +////////////////////////////// +// +// Tool_musedata2hum::addNoteDynamics -- only one contiguous dynamic allowed +// - 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"; +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); } - continue; - } - if ((ch == 'A') || (ch == 'a')) { - if ((!alteration.empty()) && (alteration[0] == 'A')) { - alteration += "A"; - } else { - alteration = "A"; + } else { + switch (notations[i]) { + case 'p': + case 'm': + case 'f': + state = 1; + dynamics.back() = notations[i]; + column.back() = i; + break; } - 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). + bool setdynamics = false; + vector ps; + HumRegex hre; + for (int i=0; i<(int)dynamics.size(); i++) { + if (dynamics[i].empty()) { 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; + 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; + } } - 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; - } + slice->at(part)->setDynamics(dynamics[i]); + setdynamics = true; + break; // only one dynamic allowed (at least for now) } - // 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(); + if (setdynamics) { + HumGrid* grid = slice->getOwner(); + if (grid) { + grid->setDynamicsPresent(part); + } } - } ////////////////////////////// // -// Tool_msearch::makeBase40Interval -- -// - -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; - } +// 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; + } +} + + + +////////////////////////////// +// +// Tool_musedata2hum::getMeasure -- Could be imporoved by NlogN search. +// + +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]; } } - base40 *= sign; - return base40; + // 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_msearch::fillMusicQueryInterleaved -- +// Tool_musedata2hum::setInitialOmd -- // -void Tool_msearch::fillMusicQueryInterleaved(vector& query, - const string& input, bool rhythmQ) { +void Tool_musedata2hum::setInitialOmd(const string& omd) { + m_omd = omd; +} - 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? - } - 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; - } +////////////////////////////// +// +// Tool_musedata2hum::cleanString -- +// - if (ch == '=') { - continue; - } - if (ch == ' ') { - // skip over multiple spaces - if (i > 0) { - if (newinput[i-1] == ' ') { - continue; - } - } - } +string Tool_musedata2hum::cleanString(const string& input) { + return MuseData::cleanString(input); +} - 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; - } - } - } - // 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; - } - // 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; - } +////////////////////////////// +// +// Tool_musicxml2hum::Tool_musicxml2hum -- +// - // 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 - } +Tool_musicxml2hum::Tool_musicxml2hum(void) { + // Options& options = m_options; + // options.define("k|kern=b","display corresponding **kern data"); - // Convert rhythms to durations - for (int i=0; i<(int)query.size(); i++) { - if (query[i].anyrhythm) { - continue; - } - if (query[i].rhythm.empty()) { - continue; - } - query[i].duration = Convert::recipToDuration(query[i].rhythm); - } + define("r|recip=b", "output **recip spine"); + define("s|stems=b", "include stems in output"); - // what is this for (end condition)? - //if ((!query.empty()) && (query[0].base <= 0)) { - // temp.clear(); - // temp.anything = true; - // query.insert(query.begin(), temp); - //} + VoiceDebugQ = false; + DebugQ = false; } ////////////////////////////// // -// checkVerticalOnly -- +// Tool_musicxml2hum::convert -- Convert a MusicXML file into +// Humdrum content. // -bool Tool_msearch::checkVerticalOnly(const string& input) { - if (input.empty()) { +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 "; + cerr << "Error description:\t" << result.description() << endl; + cerr << "Error offset:\t" << result.offset << "\n"; return false; } - if (input.size() < 2) { + + 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"; + cout << " Error description:\t" << result.description() << "\n"; + cout << "Error offset:\t" << result.offset << "\n\n"; return false; } - if (input[0] != '(') { - return false; + + 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(); + } } - if (input.back() != ')') { - return false; + + // 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=1; i<(int)input.size()-1; i++) { - // Maybe allow internal () if there is nothing outside of them. - if (input[i] == '(') { - return false; + + 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& 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); + // set the duration of the last slice + + HumdrumFile outfile; + outdata.transferTokens(outfile); + + addHeaderRecords(outfile, doc); + addFooterRecords(outfile, doc); + + Tool_ruthfix ruthfix; + ruthfix.run(outfile); + + addMeasureOneNumber(outfile); + + // Maybe implement barnum tool and apply here based on options. + + Tool_chord chord; + chord.run(outfile); + + if (m_hasOrnamentsQ) { + Tool_trillspell trillspell; + trillspell.run(outfile); } -} + if (m_hasTremoloQ) { + Tool_tremolo tremolo; + tremolo.run(outfile); + } + 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; + } -////////////////////////////// -// -// operator<< -- print MSearchQueryToken item. -// + 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; } - return out; + if (m_slurbelow || m_staffbelow) { + out << "!!!RDF**kern: < = below" << endl; + } + + 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); } - - return status; } ////////////////////////////// // -// 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. +// initialize -- // -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"; - } - } - out << endl; - } - out << line << endl; +void Tool_musicxml2hum::initialize(void) { + m_recipQ = getBoolean("recip"); + m_stemsQ = getBoolean("stems"); + m_hasOrnamentsQ = false; } ////////////////////////////// // -// Tool_musedata2hum::convertPart -- +// Tool_musicxml2hum::reindexVoices -- // -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::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); + } } - - storePartName(outdata, part, partindex); - - return status; } -/////////////////////////////// +////////////////////////////// // -// Tool_musedata2hum::storePartName -- +// Tool_musicxml2hum::prepareRdfs -- // -void Tool_musedata2hum::storePartName(HumGrid& outdata, MuseData& part, int index) { - string name = part.getPartName(); - if (!name.empty()) { - outdata.setPartName(index, name); +void Tool_musicxml2hum::prepareRdfs(vector& partdata) { + string caesura; + for (int i=0; i<(int)partdata.size(); i++) { + caesura = partdata[i].getCaesura(); + if (!caesura.empty()) { + } + } + + if (!caesura.empty()) { + m_caesura_rdf = "!!!RDF**kern: " + caesura + " = caesura"; } + } ////////////////////////////// // -// Tool_musedata2hum::convertMeasure -- +// Tool_musicxml2hum::reindexMeasure -- // -int Tool_musedata2hum::convertMeasure(HumGrid& outdata, MuseData& part, int partindex, int startindex) { - if (part.getLineCount() == 0) { - return 1; +void Tool_musicxml2hum::reindexMeasure(MxmlMeasure* measure) { + if (!measure) { + return; } - 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;; + + 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= 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; - } +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_musicxml2hum::getOptionDefinitions -- Used to avoid +// duplicating the definitions in the test main() function. +// + +Options Tool_musicxml2hum::getOptionDefinitions(void) { + return m_options; +} + + +/////////////////////////////////////////////////////////////////////////// + + +////////////////////////////// +// +// Tool_musicxml2hum::fillPartData -- +// + +bool Tool_musicxml2hum::fillPartData(vector& partdata, + const vector& partids, map& partinfo, + map& partcontent) { + + 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 (pos < 0) { - gm->setMeasureNumber(-1); - return; + return output; +} + + +bool Tool_musicxml2hum::fillPartData(MxmlPart& partdata, + const string& id, xml_node partdeclaration, xml_node partcontent) { + if (m_stemsQ) { + partdata.enableStems(); } - int num = stoi(line.substr(pos)); - if (m_lastbarnum >= 0) { - int temp = num; - num = m_lastbarnum; - m_lastbarnum = temp; + + 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); + + 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); + } + } + } + } - gm->setMeasureNumber(num); + return true; } ////////////////////////////// // -// Tool_musedata2hum::setMeasureStyle -- +// Tool_musicxml2hum::printPartInfo -- Debug information. // -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); +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(); } - } 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); + cout << "\t\tTotal duration:\t" << partdata[i].getDuration() << endl; + } + + MxmlMeasure* measure; + for (int i=0; igetDuration(); + } + if (j < (int)partdata.size() - 1) { + cout << "\t"; + } } - } else if (line.compare(0, 7, "mdouble") == 0) { - gm->setStyle(MeasureStyle::Double); + cout << endl; } } + ////////////////////////////// // -// Tool_musedata2hum::convertLine -- +// Tool_musicxml2hum::insertPartNames -- // -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; +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 (mr.isAnyNoteOrRest()) { - m_figureOffset = 0; + for (int i=0; i<(int)partdata.size(); i++) { + string value; + value = partdata[i].getPartAbbr(); + if (!value.empty()) { + hasabbr = true; + break; + } } - if (mr.isDirection()) { + if (!(hasabbr || hasname)) { 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); - } - } + GridMeasure* gm; + if (outdata.empty()) { + gm = new GridMeasure(&outdata); + outdata.push_back(gm); + } else { + gm = outdata[0]; + } - if (!attributes["Q"].empty()) { - m_quarterDivisions = std::stoi(attributes["Q"]); - } + int maxstaff; - string mclef = attributes["C"]; - if (!mclef.empty()) { - string kclef = Convert::museClefToKernClef(mclef); - if (!kclef.empty()) { - gm->addClefToken(kclef, timestamp, part, staff, layer, 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); } + } - 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); + if (hasname) { + for (int i=0; i<(int)partdata.size(); i++) { + string partname = partdata[i].getPartName(); + if (partname.empty()) { + continue; } - } - } 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 (partname.find("MusicXML") != string::npos) { + // ignore Finale dummy part names + continue; } - } - 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); + if (partname.find("Part_") != string::npos) { + // ignore SharpEye dummy part names + continue; } - } - 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 (partname.find("Unnamed") != string::npos) { + // ignore Sibelius dummy part names + continue; } - } - } else if (mr.isDirection()) { - if (mr.isTextDirection()) { - addTextDirection(gm, part, staff, mr, timestamp); + 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 "; + cerr << "counts yet. Compare MM" << measurecount << " to MM"; + cerr << partdata[i].getMeasureCount() << endl; + return false; } } + + 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 +108509,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 +108859,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 +109112,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 +109317,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 +110506,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 +110603,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 +//