diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e5409eb..6e3a7137 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ set(SRCS src/tool-dissonant.cpp src/tool-esac2hum.cpp src/tool-extract.cpp + src/tool-fb.cpp src/tool-filter.cpp src/tool-hproof.cpp src/tool-imitation.cpp @@ -148,6 +149,7 @@ set(HDRS include/tool-dissonant.h include/tool-esac2hum.h include/tool-extract.h + include/tool-fb.h include/tool-hproof.h include/tool-imitation.h include/tool-mei2hum.h diff --git a/Makefile b/Makefile index 08eed9b3..6028d96a 100644 --- a/Makefile +++ b/Makefile @@ -621,6 +621,16 @@ tool-extract.o: tool-extract.cpp tool-extract.h \ HumHash.h HumParamSet.h HumdrumFileStream.h \ HumRegex.h +tool-fb.o: tool-fb.cpp tool-fb.h \ + HumTool.h Options.h HumdrumFileSet.h \ + HumdrumFile.h HumdrumFileContent.h \ + HumdrumFileStructure.h HumdrumFileBase.h \ + HumSignifiers.h HumSignifier.h HumdrumLine.h \ + HumdrumToken.h HumNum.h HumAddress.h \ + HumHash.h HumParamSet.h HumdrumFileStream.h \ + NoteGrid.h NoteCell.h Convert.h \ + HumRegex.h + tool-filter.o: tool-filter.cpp tool-filter.h \ HumTool.h Options.h HumdrumFileSet.h \ HumdrumFile.h HumdrumFileContent.h \ @@ -632,7 +642,7 @@ tool-filter.o: tool-filter.cpp tool-filter.h \ tool-chooser.h tool-chord.h tool-cint.h \ NoteGrid.h NoteCell.h HumRegex.h \ tool-composite.h tool-dissonant.h \ - tool-extract.h tool-homorhythm.h \ + tool-extract.h tool-fb.h tool-homorhythm.h \ tool-homorhythm2.h tool-hproof.h \ tool-humdiff.h tool-shed.h tool-imitation.h \ tool-kern2mens.h tool-melisma.h tool-metlev.h \ diff --git a/include/humlib.h b/include/humlib.h index a2722aed..e87ba87f 100644 --- a/include/humlib.h +++ b/include/humlib.h @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Jan 19 21:53:43 PST 2023 +// Last Modified: Sa 21 Jan 2023 09:48:51 CET // Filename: humlib.h // URL: https://github.com/craigsapp/humlib/blob/master/include/humlib.h // Syntax: C++11 @@ -7231,37 +7231,88 @@ class Tool_extract : public HumTool { +class FiguredBassNumber { + public: + FiguredBassNumber(int num, string accid, bool showAccid, int voiceIndex, int lineIndex, bool isAttack, bool intervallsatz); + std::string toString(bool nonCompoundIntervalsQ, bool noAccidentalsQ, bool hideThreeQ); + int getNumberWithinOctave(void); + + int m_voiceIndex; + int m_lineIndex; + int m_number; + std::string m_accidentals; + bool m_showAccidentals; // Force shoing figured base numbers when they need an accidental + bool m_baseOfSustainedNoteDidChange; + bool m_isAttack; + bool m_convert2To9 = false; + bool m_intervallsatz = false; + +}; + +class FiguredBassAbbreviationMapping { + public: + FiguredBassAbbreviationMapping(string s, vector n); + + static vector s_mappings; + + // String to compare the numbers with + // e.g. "6 4 3" + // Sorted by size, larger numbers first + string m_str; + + // Figured bass number as int + vector m_numbers; + +}; + class Tool_fb : public HumTool { + public: - Tool_fb (void); - ~Tool_fb () {}; + Tool_fb (void); + ~Tool_fb() {}; - bool run (HumdrumFileSet& infiles); - bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, ostream& out); + bool run (HumdrumFile& infile, ostream& out); protected: - void processFile (HumdrumFile& infile); - void initialize (void); - void processLine (HumdrumFile& infile, int index); - void setupScoreData (HumdrumFile& infile); - void getAnalyses (HumdrumFile& infile); - void getHarmonicIntervals(HumdrumFile& infile); - void calculateIntervals(vector& intervals, vector& tokens, int bassIndex); - void printOutput (HumdrumFile& infile); - void printLineStyle3 (HumdrumFile& infile, int line); - std::string getAnalysisTokenStyle3(HumdrumFile& infile, int line, int field); + void initialize (void); + void processFile (HumdrumFile& infile); + bool hideNumbersForTokenLine (HTp token, pair timeSig); + vector getTrackData (const vector& numbers, int lineCount); + vector getTrackDataForVoice (int voiceIndex, const vector& numbers, int lineCount); + FiguredBassNumber* createFiguredBassNumber (int basePitchBase40, int targetPitchBase40, int voiceIndex, int lineIndex, bool isAttack, string keySignature); + vector filterNegativeNumbers (vector numbers); + vector filterFiguredBassNumbersForLine (vector numbers, int lineIndex); + vector filterFiguredBassNumbersForLineAndVoice(vector numbers, int lineIndex, int voiceIndex); + string formatFiguredBassNumbers (const vector& numbers); + vector analyzeChordNumbers (const vector& numbers); + vector getAbbreviatedNumbers (const vector& numbers); + string getNumberString (vector numbers); + string getKeySignature (HumdrumFile& infile, int lineIndex); + int getLowestBase40Pitch (vector base40Pitches); + private: - std::vector m_kernspines; - std::vector m_kerntracks; - std::vector m_track2index; - std::vector> m_keyaccid; - std::vector> m_intervals; - const int m_rest = -1000; - int m_reference = 0; // currently fixed to bass - int m_debugQ = false; + bool m_compoundQ = false; + bool m_accidentalsQ = false; + int m_baseTrackQ = 1; + bool m_intervallsatzQ = false; + bool m_sortQ = false; + bool m_lowestQ = false; + bool m_normalizeQ = false; + bool m_abbrQ = false; + bool m_attackQ = false; + bool m_figuredbassQ = false; + bool m_hideThreeQ = false; + bool m_showNegativeQ = false; + bool m_aboveQ = false; + string m_recipQ = ""; + + string m_spineTracks = ""; // used with -s option + string m_kernTracks = ""; // used with -k option + vector m_selectedKernSpines; // used with -k and -s option }; diff --git a/include/tool-fb.h b/include/tool-fb.h index ea2cd47c..ac10913f 100644 --- a/include/tool-fb.h +++ b/include/tool-fb.h @@ -1,14 +1,12 @@ // -// Programmer: Craig Stuart Sapp -// Creation Date: Wed Mar 9 21:50:25 PST 2022 -// Last Modified: Wed Mar 9 21:50:28 PST 2022 +// Programmer: Wolfgang Drescher +// Creation Date: Sun Nov 27 2022 00:25:34 CET // Filename: tool-fb.h // URL: https://github.com/craigsapp/humlib/blob/master/include/tool-fb.h // Syntax: C++11; humlib -// vim: ts=3 noexpandtab +// vim: syntax=cpp ts=3 noexpandtab nowrap // -// Description: Extract figured bass numbers from musical content. -// Reference: https://github.com/WolfgangDrescher/humdrum-figured-bass-filter-demo +// Description: Interface for fb tool, which automatically adds figured bass numbers. // #ifndef _TOOL_FB_H @@ -16,42 +14,94 @@ #include "HumTool.h" #include "HumdrumFile.h" +#include "NoteGrid.h" namespace hum { // START_MERGE +class FiguredBassNumber { + public: + FiguredBassNumber(int num, string accid, bool showAccid, int voiceIndex, int lineIndex, bool isAttack, bool intervallsatz); + std::string toString(bool nonCompoundIntervalsQ, bool noAccidentalsQ, bool hideThreeQ); + int getNumberWithinOctave(void); + + int m_voiceIndex; + int m_lineIndex; + int m_number; + std::string m_accidentals; + bool m_showAccidentals; // Force shoing figured base numbers when they need an accidental + bool m_baseOfSustainedNoteDidChange; + bool m_isAttack; + bool m_convert2To9 = false; + bool m_intervallsatz = false; + +}; + +class FiguredBassAbbreviationMapping { + public: + FiguredBassAbbreviationMapping(string s, vector n); + + static vector s_mappings; + + // String to compare the numbers with + // e.g. "6 4 3" + // Sorted by size, larger numbers first + string m_str; + + // Figured bass number as int + vector m_numbers; + +}; + class Tool_fb : public HumTool { + public: - Tool_fb (void); - ~Tool_fb () {}; + Tool_fb (void); + ~Tool_fb() {}; - bool run (HumdrumFileSet& infiles); - bool run (HumdrumFile& infile); - bool run (const string& indata, ostream& out); - bool run (HumdrumFile& infile, ostream& out); + bool run (HumdrumFileSet& infiles); + bool run (HumdrumFile& infile); + bool run (const string& indata, ostream& out); + bool run (HumdrumFile& infile, ostream& out); protected: - void processFile (HumdrumFile& infile); - void initialize (void); - void processLine (HumdrumFile& infile, int index); - void setupScoreData (HumdrumFile& infile); - void getAnalyses (HumdrumFile& infile); - void getHarmonicIntervals(HumdrumFile& infile); - void calculateIntervals(vector& intervals, vector& tokens, int bassIndex); - void printOutput (HumdrumFile& infile); - void printLineStyle3 (HumdrumFile& infile, int line); - std::string getAnalysisTokenStyle3(HumdrumFile& infile, int line, int field); + void initialize (void); + void processFile (HumdrumFile& infile); + bool hideNumbersForTokenLine (HTp token, pair timeSig); + vector getTrackData (const vector& numbers, int lineCount); + vector getTrackDataForVoice (int voiceIndex, const vector& numbers, int lineCount); + FiguredBassNumber* createFiguredBassNumber (int basePitchBase40, int targetPitchBase40, int voiceIndex, int lineIndex, bool isAttack, string keySignature); + vector filterNegativeNumbers (vector numbers); + vector filterFiguredBassNumbersForLine (vector numbers, int lineIndex); + vector filterFiguredBassNumbersForLineAndVoice(vector numbers, int lineIndex, int voiceIndex); + string formatFiguredBassNumbers (const vector& numbers); + vector analyzeChordNumbers (const vector& numbers); + vector getAbbreviatedNumbers (const vector& numbers); + string getNumberString (vector numbers); + string getKeySignature (HumdrumFile& infile, int lineIndex); + int getLowestBase40Pitch (vector base40Pitches); + private: - std::vector m_kernspines; - std::vector m_kerntracks; - std::vector m_track2index; - std::vector> m_keyaccid; - std::vector> m_intervals; - const int m_rest = -1000; - int m_reference = 0; // currently fixed to bass - int m_debugQ = false; + bool m_compoundQ = false; + bool m_accidentalsQ = false; + int m_baseTrackQ = 1; + bool m_intervallsatzQ = false; + bool m_sortQ = false; + bool m_lowestQ = false; + bool m_normalizeQ = false; + bool m_abbrQ = false; + bool m_attackQ = false; + bool m_figuredbassQ = false; + bool m_hideThreeQ = false; + bool m_showNegativeQ = false; + bool m_aboveQ = false; + string m_recipQ = ""; + + string m_spineTracks = ""; // used with -s option + string m_kernTracks = ""; // used with -k option + vector m_selectedKernSpines; // used with -k and -s option }; @@ -60,6 +110,3 @@ class Tool_fb : public HumTool { } // end namespace hum #endif /* _TOOL_FB_H */ - - - diff --git a/src/humlib.cpp b/src/humlib.cpp index c41f4d9d..1972b55c 100644 --- a/src/humlib.cpp +++ b/src/humlib.cpp @@ -1,7 +1,7 @@ // // Programmer: Craig Stuart Sapp // Creation Date: Sat Aug 8 12:24:49 PDT 2015 -// Last Modified: Thu Jan 19 21:53:43 PST 2023 +// Last Modified: Sa 21 Jan 2023 09:48:51 CET // Filename: /include/humlib.cpp // URL: https://github.com/craigsapp/humlib/blob/master/src/humlib.cpp // Syntax: C++11 @@ -78025,34 +78025,46 @@ void Tool_extract::initialize(HumdrumFile& infile) { - -///////////////////////////////// +////////////////////////////// // // Tool_fb::Tool_fb -- Set the recognized options for the tool. // Tool_fb::Tool_fb(void) { - define("d|debug=b", "Print debug information"); - define("r|reference=i:0", "Reference kern spine (1 indexed)"); + define("c|compound=b", "output reasonable figured bass numbers within octave"); + define("a|accidentals=b", "display accidentals in figured bass output"); + define("b|base|base-track=i:1", "number of the base kern track (compare with -k)"); + define("i|intervallsatz=b", "display intervals under their voice and not under the lowest staff"); + define("o|sort|order=b", "sort figured bass numbers by interval size and not by voice index"); + define("l|lowest=b", "use lowest note as base note; -b flag will be ignored"); + define("n|normalize=b", "remove octave and doubled intervals; adds: --compound --sort"); + define("r|abbr=b", "use abbreviated figures; adds: --normalize --compound --sort"); + define("t|ties=b", "hide repeated numbers for sustained notes when base does not change"); + define("f|figuredbass=b", "shortcut for -c -a -o -n -r -3"); + define("3|hide-three=b", "hide number 3 if it has an accidental (e.g.: #3 => #)"); + define("m|negative=b", "show negative numbers"); + define("above=b", "place figured bass numbers above staff (**fba)"); + define("frequency|recip=s:", "frequency to display the numbers (set a **recip value, e.g. 2, 4, 8, 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"); } -///////////////////////////////// +////////////////////////////// // // Tool_fb::run -- Do the main work of the tool. // -bool Tool_fb::run(HumdrumFileSet& infiles) { +bool Tool_fb::run(HumdrumFileSet &infiles) { bool status = true; - for (int i=0; i numbers; -////////////////////////////// -// -// Tool_fb::getHarmonicIntervals -- Fill in -// + vector kernspines = infile.getKernSpineStartList(); -void Tool_fb::getHarmonicIntervals(HumdrumFile& infile) { - m_intervals.resize(infile.getLineCount()); + int maxTrack = infile.getMaxTrack(); - vector tokens(m_kernspines.size(), NULL); - for (int i=0; i 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())) { + 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 = {}; + + // 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(); + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + + if (abs(lowest) < lowestNotePitch) { + lowestNotePitch = abs(lowest); + usedBaseKernTrack = k + 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; + } + } while (currentToken); + } + } + + NoteCell* baseCell = grid.cell(usedBaseKernTrack - 1, i); + + // Ignore grace notes + if (baseCell->getToken()->getOwner()->getDuration() == 0) { continue; } - fill(tokens.begin(), tokens.end(), (HTp)NULL); - for (int j=0; jisKern()) { + + string keySignature = getKeySignature(infile, baseCell->getLineIndex()); + + // Hide numbers if they do not match rhythmic position of --recip + if (!m_recipQ.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; } - int track = token->getTrack(); - int index = m_track2index.at(track); - tokens[index] = token; - // cerr << token << "\t"; - } - m_intervals[i].resize(m_kernspines.size()); - calculateIntervals(m_intervals[i], tokens, m_reference); - // cerr << endl; - - if (m_debugQ) { - for (int j=0; j<(int)m_intervals[i].size(); j++) { - m_free_text << tokens[j] << "\t("; - if (m_intervals[i][j] == m_rest) { - m_free_text << "R"; + } + + + HTp currentToken = baseCell->getToken(); + int initialTokenTrack = baseCell->getToken()->getTrack(); + int lowestBaseNoteBase40Pitch = 9999; + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + + // Ignore if base is a rest or silent note + if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) { + if(abs(lowest) < lowestBaseNoteBase40Pitch) { + lowestBaseNoteBase40Pitch = abs(lowest); + } + } + + 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); + + // 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 = {}; + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + + for (int subtokenBase40: resolvedToken->getBase40Pitches()) { + + // Ignore if target is a rest or silent note + if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) { + continue; + } + + // Ignore if same pitch as base voice + if ((abs(lowestBaseNoteBase40Pitch) == abs(subtokenBase40)) && (baseCell->getToken()->getTrack() == initialTokenTrack)) { + continue; + } + + // Create FiguredBassNumber + FiguredBassNumber* number = createFiguredBassNumber(abs(lowestBaseNoteBase40Pitch), abs(subtokenBase40), targetCell->getVoiceIndex(), targetCell->getLineIndex(), targetCell->isAttack(), keySignature); + + currentNumbers[j].push_back(number->m_number); + chordNumbers.push_back(number); + } + + HTp nextToken = currentToken->getNextField(); + if (nextToken && (initialTokenTrack == nextToken->getTrack())) { + currentToken = nextToken; } else { - m_free_text << m_intervals[i][j]; + // Break loop if nextToken is not the same track as initialTokenTrack + break; } - m_free_text << ")"; - if (j < (int)m_intervals[i].size() - 1) { - m_free_text << "\t"; + } 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); } - m_free_text << endl; } + + // Set current numbers as the new last numbers + lastNumbers = currentNumbers; } + + string exinterp = m_aboveQ ? "**fba" : "**fb"; + + 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_fb::calculateIntervals -- +// Tool_fb::hideNumbersForTokenLine -- Checks if rhythmic position of line should display numbers // -void Tool_fb::calculateIntervals(vector& intervals, - vector& tokens, int bassIndex) { - if (intervals.size() != tokens.size()) { - cerr << "ERROR: Size if vectors do not match" << endl; - return; +bool Tool_fb::hideNumbersForTokenLine(HTp token, pair timeSig) { + // Get note duration from --recip option + HumNum recip = Convert::recipToDuration(m_recipQ); + if (recip.toFloat() != 0) { + float timeSigBarDuration = timeSig.first * Convert::recipToDuration(to_string(timeSig.second.getInteger())).toFloat(); + float 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 recip duration + return fmod(durationFromBarline, recip.toFloat()) != 0; } + return false; +} - HTp reftok = tokens[m_reference]; - if (reftok->isNull()) { - reftok = reftok->resolveNull(); - } - if (!reftok || reftok->isRest()) { - for (int i=0; i<(int)tokens.size(); i++) { - intervals[i] = m_rest; + +////////////////////////////// +// +// Tool_fb::getTrackData -- Create **fb spine data with formatted numbers for all voices +// + +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; } - int base40ref = Convert::kernToBase40(reftok); + return trackData; +} - for (int i=0; i<(int)tokens.size(); i++) { - if (i == m_reference) { - intervals[i] = m_rest; - continue; - } - if (tokens[i]->isRest()) { - intervals[i] = m_rest; - continue; - } - if (tokens[m_reference]->isRest()) { - intervals[i] = m_rest; - continue; - } - if (tokens[i]->isNull()) { - continue; + + +////////////////////////////// +// +// 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 base40 = Convert::kernToBase40(tokens[i]); - int interval = base40 - base40ref; - intervals[i] = interval; } + + return trackData; } ////////////////////////////// // -// Tool_fb::setupScoreData -- +// 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_fb::setupScoreData(HumdrumFile& infile) { - infile.getKernSpineStartList(m_kernspines); - m_kerntracks.resize(m_kernspines.size()); - for (int i=0; i<(int)m_kernspines.size(); i++) { - m_kerntracks[i] = m_kernspines[i]->getTrack(); +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; + + 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 maxtrack = infile.getMaxTrack(); - m_track2index.resize(maxtrack + 1); - fill(m_track2index.begin(), m_track2index.end(), -1); - for (int i=0; i<(int)m_kerntracks.size(); i++) { - m_track2index.at(m_kerntracks[i]) = i; + // Transform key signature to lower case + transform(keySignature.begin(), keySignature.end(), keySignature.begin(), [](unsigned char c) { + return tolower(c); + }); + + char targetPitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(targetPitchBase40)); + int targetAccidNr = Convert::base40ToAccidental(targetPitchBase40); + string targetAccid; + for (int i=0; i= (int)m_kernspines.size()) { - m_reference = (int)m_kernspines.size() - 1; + char basePitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(basePitchBase40)); + int baseAccidNr = Convert::base40ToAccidental(basePitchBase40); + string baseAccid; + for (int i=0; i pcs(7, 0); + // 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; + } - m_keyaccid.resize(infile.getLineCount()); - for (int i=0; iisKern()) { - continue; - } - if (token->isKeySignature()) { - fill(pcs.begin(), pcs.end(), 0); - HumRegex hre; - if (hre.search(token, "c#")) { pcs[0] = +1;} - if (hre.search(token, "d#")) { pcs[1] = +1;} - if (hre.search(token, "e#")) { pcs[2] = +1;} - if (hre.search(token, "f#")) { pcs[3] = +1;} - if (hre.search(token, "g#")) { pcs[4] = +1;} - if (hre.search(token, "a#")) { pcs[5] = +1;} - if (hre.search(token, "b#")) { pcs[6] = +1;} - if (hre.search(token, "c-")) { pcs[0] = -1;} - if (hre.search(token, "d-")) { pcs[1] = -1;} - if (hre.search(token, "e-")) { pcs[2] = -1;} - if (hre.search(token, "f-")) { pcs[3] = -1;} - if (hre.search(token, "g-")) { pcs[4] = -1;} - if (hre.search(token, "a-")) { pcs[5] = -1;} - if (hre.search(token, "b-")) { pcs[6] = -1;} - m_keyaccid[i] = pcs; - } + // 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; } } - for (int i=1; i 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_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music. +// + +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_fb::filterFiguredBassNumbersForLineAndVoice -- +// + +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) { + + 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; + } + + // 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; + } + + // Analysze before sorting + if (m_compoundQ) { + formattedNumbers = analyzeChordNumbers(formattedNumbers); + } + + // 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 (m_abbrQ) { + // Overwrite formattedNumbers with abbreviated numbers + formattedNumbers = getAbbreviatedNumbers(formattedNumbers); } - for (int i=infile.getLineCount() - 2; i>=0; i--) { - if (m_keyaccid[i].empty()) { - m_keyaccid[i] = m_keyaccid[i+1]; + + // join numbers + string str = ""; + bool first = true; + for (FiguredBassNumber* number: formattedNumbers) { + string num = number->toString(m_compoundQ, m_accidentalsQ, m_hideThreeQ); + if (num.length() > 0) { + if (!first) str += " "; + first = false; + str += num; } } + return str; } ////////////////////////////// // -// Tool_fb:printOutput -- +// 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; + + vector mappings = FiguredBassAbbreviationMapping::s_mappings; + + string numberString = getNumberString(numbers); + + // Check if an abbreviation exists for passed numbers + auto it = find_if(mappings.begin(), mappings.end(), [numberString](FiguredBassAbbreviationMapping* abbr) { + return abbr->m_str == numberString; + }); + + if (it != mappings.end()) { + int index = it - mappings.begin(); + FiguredBassAbbreviationMapping* abbr = mappings[index]; + 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) { + 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_fb::printOutput(HumdrumFile& infile) { - for (int i=0; i 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; } - printLineStyle3(infile, i); } + + return analyzedNumbers; } ////////////////////////////// // -// Tool_fb::printLineStyle3 -- +// Tool_fb::getNumberString -- Get only the numbers (without accidentals) of passed FiguredBassNumbers // -void Tool_fb::printLineStyle3(HumdrumFile& infile, int line) { - bool printed = false; - int reftrack = m_kerntracks[m_reference]; - bool tab = false; +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; +} - for (int i=0; igetTrack(); - if (printed || (track != reftrack + 1)) { - if (tab) { - m_humdrum_text << "\t" << token; - } else { - tab = true; - m_humdrum_text << token; + + +////////////////////////////// +// +// Tool_fb::getKeySignature -- Get the key signature for a line index of the input file +// + +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); + } } - continue; - } - // print analysis spine and then next spine - if (tab) { - m_humdrum_text << "\t"; - } else { - tab = true; } - m_humdrum_text << getAnalysisTokenStyle3(infile, line, i); - printed = true; - m_humdrum_text << "\t" << token; + }(); + return keySignature; +} + + + +////////////////////////////// +// +// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent +// TODO: Handle negative values and sustained notes +// + +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); + }); + + if (filteredBase40Pitches.size() == 0) { + return -2000; } - m_humdrum_text << "\n"; + + return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches)); +} + + +////////////////////////////// +// +// FiguredBassNumber::FiguredBassNumber -- Constructor +// + +FiguredBassNumber::FiguredBassNumber(int num, string accid, bool showAccid, int voiceIdx, int lineIdx, bool isAtk, bool intervallsatz) { + m_number = num; + m_accidentals = accid; + m_voiceIndex = voiceIdx; + m_lineIndex = lineIdx; + m_showAccidentals = showAccid; + m_isAttack = isAtk; + m_intervallsatz = intervallsatz; } ////////////////////////////// // -// Tool_fb::getAnalysisTokenStyle3 -- +// FiguredBassNumber::toString -- Convert FiguredBassNumber to a string (accidental + number) // -string Tool_fb::getAnalysisTokenStyle3(HumdrumFile& infile, int line, int field) { - if (infile[line].isCommentLocal()) { - return "!"; +string FiguredBassNumber::toString(bool compoundQ, bool accidentalsQ, bool hideThreeQ) { + int num = (compoundQ) ? getNumberWithinOctave() : m_number; + string accid = (accidentalsQ && m_showAccidentals) ? m_accidentals : ""; + if (((num == 3) || (num == -3)) && accidentalsQ && m_showAccidentals && hideThreeQ) { + return accid; } - if (infile[line].isInterpretation()) { - HTp token = infile.token(line, 0); - if (token->compare(0, 2, "**") == 0) { - return "**fb"; - } else if (*token == "*-") { - return "*-"; - } else if (token->isLabel()) { - return *token; - } else if (token->isExpansionList()) { - return *token; - } else if (token->isKeySignature()) { - return *token; - } else if (token->isKeyDesignation()) { - return *token; - } else { - return "*"; - } + if (num > 0) { + return accid + to_string(num); } - if (infile[line].isBarline()) { - HTp token = infile.token(line, 0); - return *token; + if (num < 0) { + return accid + "~" + to_string(abs(num)); } + return ""; +} - // create data token - string output; - for (int i=(int)m_intervals[line].size()-1; i>=0; i--) { - if (i == m_reference) { - continue; + +////////////////////////////// +// +// FiguredBassNumber::getNumberWithinOctave -- Get a reasonable figured bass number +// Replace 0 with 7 and -7 +// Replace 1 with 8 and -8 +// Replace 2 with 9 if it is a suspension of the ninth +// Allow 1 (unisono) in intervallsatz + +int FiguredBassNumber::getNumberWithinOctave(void) { + int num = m_number % 7; + + // Replace 0 with 7 and -7 + if ((abs(m_number) > 0) && (m_number % 7 == 0)) { + return m_number < 0 ? -7 : 7; + } + + // Replace 1 with 8 and -8 + if (abs(num) == 1) { + // Allow unisono in intervallsatz + if (m_intervallsatz) { + if (abs(m_number) == 1) { + return 1; + } } - int base40int = m_intervals[line][i]; - string iname = Convert::base40ToIntervalAbbr(base40int); - output += iname; - output += " "; + return m_number < 0 ? -8 : 8; } - if (!output.empty()) { - output.resize((int)output.size() - 1); + + // 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 output; + return num; } +////////////////////////////// +// +// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor +// Helper class to store the mappings for abbreviate figured bass numbers +// + +FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector n) { + m_str = s; + m_numbers = n; +} + + + +////////////////////////////// +// +// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers +// + +vector FiguredBassAbbreviationMapping::s_mappings = { + new FiguredBassAbbreviationMapping("3", {}), + new FiguredBassAbbreviationMapping("5", {}), + new FiguredBassAbbreviationMapping("5 3", {}), + new FiguredBassAbbreviationMapping("6 3", {6}), + new FiguredBassAbbreviationMapping("5 4", {4}), + new FiguredBassAbbreviationMapping("7 5 3", {7}), + new FiguredBassAbbreviationMapping("7 3", {7}), + new FiguredBassAbbreviationMapping("7 5", {7}), + new FiguredBassAbbreviationMapping("6 5 3", {6, 5}), + new FiguredBassAbbreviationMapping("6 4 3", {4, 3}), + new FiguredBassAbbreviationMapping("6 4 2", {4, 2}), + new FiguredBassAbbreviationMapping("9 5 3", {9}), + new FiguredBassAbbreviationMapping("9 5", {9}), + new FiguredBassAbbreviationMapping("9 3", {9}), +}; + + #define RUNTOOL(NAME, INFILE, COMMAND, STATUS) \ Tool_##NAME *tool = new Tool_##NAME; \ @@ -78540,6 +79051,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { 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") { diff --git a/src/tool-fb.cpp b/src/tool-fb.cpp index 334826e2..3a96e5c5 100644 --- a/src/tool-fb.cpp +++ b/src/tool-fb.cpp @@ -1,19 +1,19 @@ // -// Programmer: Craig Stuart Sapp -// Creation Date: Wed Mar 9 21:55:00 PST 2022 -// Last Modified: Wed Mar 9 21:55:04 PST 2022 +// Programmer: Wolfgang Drescher +// Creation Date: Sun Nov 27 2022 00:25:34 CET // Filename: tool-fb.cpp // URL: https://github.com/craigsapp/humlib/blob/master/src/tool-fb.cpp // Syntax: C++11; humlib -// vim: ts=3 noexpandtab +// vim: syntax=cpp ts=3 noexpandtab nowrap // -// Description: Extract figured bass from melodic content. -// Reference: https://github.com/WolfgangDrescher/humdrum-figured-bass-filter-demo +// Description: Add figured bass numbers from **kern spines. // #include "tool-fb.h" -#include "HumRegex.h" #include "Convert.h" +#include "HumRegex.h" +#include +#include using namespace std; @@ -21,34 +21,46 @@ namespace hum { // START_MERGE - -///////////////////////////////// +////////////////////////////// // // Tool_fb::Tool_fb -- Set the recognized options for the tool. // Tool_fb::Tool_fb(void) { - define("d|debug=b", "Print debug information"); - define("r|reference=i:0", "Reference kern spine (1 indexed)"); + define("c|compound=b", "output reasonable figured bass numbers within octave"); + define("a|accidentals=b", "display accidentals in figured bass output"); + define("b|base|base-track=i:1", "number of the base kern track (compare with -k)"); + define("i|intervallsatz=b", "display intervals under their voice and not under the lowest staff"); + define("o|sort|order=b", "sort figured bass numbers by interval size and not by voice index"); + define("l|lowest=b", "use lowest note as base note; -b flag will be ignored"); + define("n|normalize=b", "remove octave and doubled intervals; adds: --compound --sort"); + define("r|abbr=b", "use abbreviated figures; adds: --normalize --compound --sort"); + define("t|ties=b", "hide repeated numbers for sustained notes when base does not change"); + define("f|figuredbass=b", "shortcut for -c -a -o -n -r -3"); + define("3|hide-three=b", "hide number 3 if it has an accidental (e.g.: #3 => #)"); + define("m|negative=b", "show negative numbers"); + define("above=b", "place figured bass numbers above staff (**fba)"); + define("frequency|recip=s:", "frequency to display the numbers (set a **recip value, e.g. 2, 4, 8, 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"); } -///////////////////////////////// +////////////////////////////// // // Tool_fb::run -- Do the main work of the tool. // -bool Tool_fb::run(HumdrumFileSet& infiles) { +bool Tool_fb::run(HumdrumFileSet &infiles) { bool status = true; - for (int i=0; i numbers; -////////////////////////////// -// -// Tool_fb::getHarmonicIntervals -- Fill in -// + 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; + } -void Tool_fb::getHarmonicIntervals(HumdrumFile& infile) { - m_intervals.resize(infile.getLineCount()); + 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 = {}; + + // 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(); + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + + if (abs(lowest) < lowestNotePitch) { + lowestNotePitch = abs(lowest); + usedBaseKernTrack = k + 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; + } + } while (currentToken); + } + } - vector tokens(m_kernspines.size(), NULL); - for (int i=0; igetToken()->getOwner()->getDuration() == 0) { continue; } - fill(tokens.begin(), tokens.end(), (HTp)NULL); - for (int j=0; jisKern()) { + + string keySignature = getKeySignature(infile, baseCell->getLineIndex()); + + // Hide numbers if they do not match rhythmic position of --recip + if (!m_recipQ.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; } - int track = token->getTrack(); - int index = m_track2index.at(track); - tokens[index] = token; - // cerr << token << "\t"; } - m_intervals[i].resize(m_kernspines.size()); - calculateIntervals(m_intervals[i], tokens, m_reference); - // cerr << endl; - - if (m_debugQ) { - for (int j=0; j<(int)m_intervals[i].size(); j++) { - m_free_text << tokens[j] << "\t("; - if (m_intervals[i][j] == m_rest) { - m_free_text << "R"; + + + HTp currentToken = baseCell->getToken(); + int initialTokenTrack = baseCell->getToken()->getTrack(); + int lowestBaseNoteBase40Pitch = 9999; + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + + int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches()); + + // Ignore if base is a rest or silent note + if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) { + if(abs(lowest) < lowestBaseNoteBase40Pitch) { + lowestBaseNoteBase40Pitch = abs(lowest); + } + } + + 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); + + // 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 = {}; + + // Handle spine splits + do { + HTp resolvedToken = currentToken->resolveNull(); + + for (int subtokenBase40: resolvedToken->getBase40Pitches()) { + + // Ignore if target is a rest or silent note + if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) { + continue; + } + + // Ignore if same pitch as base voice + if ((abs(lowestBaseNoteBase40Pitch) == abs(subtokenBase40)) && (baseCell->getToken()->getTrack() == initialTokenTrack)) { + continue; + } + + // Create FiguredBassNumber + FiguredBassNumber* number = createFiguredBassNumber(abs(lowestBaseNoteBase40Pitch), abs(subtokenBase40), targetCell->getVoiceIndex(), targetCell->getLineIndex(), targetCell->isAttack(), keySignature); + + currentNumbers[j].push_back(number->m_number); + chordNumbers.push_back(number); + } + + HTp nextToken = currentToken->getNextField(); + if (nextToken && (initialTokenTrack == nextToken->getTrack())) { + currentToken = nextToken; } else { - m_free_text << m_intervals[i][j]; + // Break loop if nextToken is not the same track as initialTokenTrack + break; } - m_free_text << ")"; - if (j < (int)m_intervals[i].size() - 1) { - m_free_text << "\t"; + } 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); } - m_free_text << endl; } + + // Set current numbers as the new last numbers + lastNumbers = currentNumbers; } + + string exinterp = m_aboveQ ? "**fba" : "**fb"; + + 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_fb::calculateIntervals -- +// Tool_fb::hideNumbersForTokenLine -- Checks if rhythmic position of line should display numbers // -void Tool_fb::calculateIntervals(vector& intervals, - vector& tokens, int bassIndex) { - if (intervals.size() != tokens.size()) { - cerr << "ERROR: Size if vectors do not match" << endl; - return; +bool Tool_fb::hideNumbersForTokenLine(HTp token, pair timeSig) { + // Get note duration from --recip option + HumNum recip = Convert::recipToDuration(m_recipQ); + if (recip.toFloat() != 0) { + float timeSigBarDuration = timeSig.first * Convert::recipToDuration(to_string(timeSig.second.getInteger())).toFloat(); + float 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 recip duration + return fmod(durationFromBarline, recip.toFloat()) != 0; } + return false; +} + - HTp reftok = tokens[m_reference]; - if (reftok->isNull()) { - reftok = reftok->resolveNull(); - } - if (!reftok || reftok->isRest()) { - for (int i=0; i<(int)tokens.size(); i++) { - intervals[i] = m_rest; +////////////////////////////// +// +// Tool_fb::getTrackData -- Create **fb spine data with formatted numbers for all voices +// + +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; } - int base40ref = Convert::kernToBase40(reftok); + return trackData; +} - for (int i=0; i<(int)tokens.size(); i++) { - if (i == m_reference) { - intervals[i] = m_rest; - continue; - } - if (tokens[i]->isRest()) { - intervals[i] = m_rest; - continue; - } - if (tokens[m_reference]->isRest()) { - intervals[i] = m_rest; - continue; - } - if (tokens[i]->isNull()) { - continue; + + +////////////////////////////// +// +// 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 base40 = Convert::kernToBase40(tokens[i]); - int interval = base40 - base40ref; - intervals[i] = interval; } + + return trackData; } ////////////////////////////// // -// Tool_fb::setupScoreData -- +// 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_fb::setupScoreData(HumdrumFile& infile) { - infile.getKernSpineStartList(m_kernspines); - m_kerntracks.resize(m_kernspines.size()); - for (int i=0; i<(int)m_kernspines.size(); i++) { - m_kerntracks[i] = m_kernspines[i]->getTrack(); +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; + + 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 maxtrack = infile.getMaxTrack(); - m_track2index.resize(maxtrack + 1); - fill(m_track2index.begin(), m_track2index.end(), -1); - for (int i=0; i<(int)m_kerntracks.size(); i++) { - m_track2index.at(m_kerntracks[i]) = i; + // Transform key signature to lower case + transform(keySignature.begin(), keySignature.end(), keySignature.begin(), [](unsigned char c) { + return tolower(c); + }); + + char targetPitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(targetPitchBase40)); + int targetAccidNr = Convert::base40ToAccidental(targetPitchBase40); + string targetAccid; + for (int i=0; i= (int)m_kernspines.size()) { - m_reference = (int)m_kernspines.size() - 1; + char basePitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(basePitchBase40)); + int baseAccidNr = Convert::base40ToAccidental(basePitchBase40); + string baseAccid; + for (int i=0; i pcs(7, 0); + // 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; + } - m_keyaccid.resize(infile.getLineCount()); - for (int i=0; iisKern()) { - continue; - } - if (token->isKeySignature()) { - fill(pcs.begin(), pcs.end(), 0); - HumRegex hre; - if (hre.search(token, "c#")) { pcs[0] = +1;} - if (hre.search(token, "d#")) { pcs[1] = +1;} - if (hre.search(token, "e#")) { pcs[2] = +1;} - if (hre.search(token, "f#")) { pcs[3] = +1;} - if (hre.search(token, "g#")) { pcs[4] = +1;} - if (hre.search(token, "a#")) { pcs[5] = +1;} - if (hre.search(token, "b#")) { pcs[6] = +1;} - if (hre.search(token, "c-")) { pcs[0] = -1;} - if (hre.search(token, "d-")) { pcs[1] = -1;} - if (hre.search(token, "e-")) { pcs[2] = -1;} - if (hre.search(token, "f-")) { pcs[3] = -1;} - if (hre.search(token, "g-")) { pcs[4] = -1;} - if (hre.search(token, "a-")) { pcs[5] = -1;} - if (hre.search(token, "b-")) { pcs[6] = -1;} - m_keyaccid[i] = pcs; - } + // 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; } } - for (int i=1; i 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_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music. +// + +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_fb::filterFiguredBassNumbersForLineAndVoice -- +// + +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) { + + 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; } - for (int i=infile.getLineCount() - 2; i>=0; i--) { - if (m_keyaccid[i].empty()) { - m_keyaccid[i] = m_keyaccid[i+1]; + + // 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; + } + + // Analysze before sorting + if (m_compoundQ) { + formattedNumbers = analyzeChordNumbers(formattedNumbers); + } + + // 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 (m_abbrQ) { + // Overwrite formattedNumbers with abbreviated numbers + formattedNumbers = getAbbreviatedNumbers(formattedNumbers); + } + + // join numbers + string str = ""; + bool first = true; + for (FiguredBassNumber* number: formattedNumbers) { + string num = number->toString(m_compoundQ, m_accidentalsQ, m_hideThreeQ); + if (num.length() > 0) { + if (!first) str += " "; + first = false; + str += num; } } + return str; } ////////////////////////////// // -// Tool_fb:printOutput -- +// 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; + + vector mappings = FiguredBassAbbreviationMapping::s_mappings; + + string numberString = getNumberString(numbers); + + // Check if an abbreviation exists for passed numbers + auto it = find_if(mappings.begin(), mappings.end(), [numberString](FiguredBassAbbreviationMapping* abbr) { + return abbr->m_str == numberString; + }); + + if (it != mappings.end()) { + int index = it - mappings.begin(); + FiguredBassAbbreviationMapping* abbr = mappings[index]; + 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) { + 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_fb::printOutput(HumdrumFile& infile) { - for (int i=0; i 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; } - printLineStyle3(infile, i); } + + return analyzedNumbers; } ////////////////////////////// // -// Tool_fb::printLineStyle3 -- +// Tool_fb::getNumberString -- Get only the numbers (without accidentals) of passed FiguredBassNumbers // -void Tool_fb::printLineStyle3(HumdrumFile& infile, int line) { - bool printed = false; - int reftrack = m_kerntracks[m_reference]; - bool tab = false; +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; +} - for (int i=0; igetTrack(); - if (printed || (track != reftrack + 1)) { - if (tab) { - m_humdrum_text << "\t" << token; - } else { - tab = true; - m_humdrum_text << token; + + +////////////////////////////// +// +// Tool_fb::getKeySignature -- Get the key signature for a line index of the input file +// + +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); + } } - continue; - } - // print analysis spine and then next spine - if (tab) { - m_humdrum_text << "\t"; - } else { - tab = true; } - m_humdrum_text << getAnalysisTokenStyle3(infile, line, i); - printed = true; - m_humdrum_text << "\t" << token; + }(); + return keySignature; +} + + + +////////////////////////////// +// +// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent +// TODO: Handle negative values and sustained notes +// + +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); + }); + + if (filteredBase40Pitches.size() == 0) { + return -2000; } - m_humdrum_text << "\n"; + + return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches)); +} + + +////////////////////////////// +// +// FiguredBassNumber::FiguredBassNumber -- Constructor +// + +FiguredBassNumber::FiguredBassNumber(int num, string accid, bool showAccid, int voiceIdx, int lineIdx, bool isAtk, bool intervallsatz) { + m_number = num; + m_accidentals = accid; + m_voiceIndex = voiceIdx; + m_lineIndex = lineIdx; + m_showAccidentals = showAccid; + m_isAttack = isAtk; + m_intervallsatz = intervallsatz; } ////////////////////////////// // -// Tool_fb::getAnalysisTokenStyle3 -- -// - -string Tool_fb::getAnalysisTokenStyle3(HumdrumFile& infile, int line, int field) { - if (infile[line].isCommentLocal()) { - return "!"; - } - if (infile[line].isInterpretation()) { - HTp token = infile.token(line, 0); - if (token->compare(0, 2, "**") == 0) { - return "**fb"; - } else if (*token == "*-") { - return "*-"; - } else if (token->isLabel()) { - return *token; - } else if (token->isExpansionList()) { - return *token; - } else if (token->isKeySignature()) { - return *token; - } else if (token->isKeyDesignation()) { - return *token; - } else { - return "*"; - } +// FiguredBassNumber::toString -- Convert FiguredBassNumber to a string (accidental + number) +// + +string FiguredBassNumber::toString(bool compoundQ, bool accidentalsQ, bool hideThreeQ) { + int num = (compoundQ) ? getNumberWithinOctave() : m_number; + string accid = (accidentalsQ && m_showAccidentals) ? m_accidentals : ""; + if (((num == 3) || (num == -3)) && accidentalsQ && m_showAccidentals && hideThreeQ) { + return accid; } - if (infile[line].isBarline()) { - HTp token = infile.token(line, 0); - return *token; + if (num > 0) { + return accid + to_string(num); } + if (num < 0) { + return accid + "~" + to_string(abs(num)); + } + return ""; +} - // create data token - string output; - for (int i=(int)m_intervals[line].size()-1; i>=0; i--) { - if (i == m_reference) { - continue; + +////////////////////////////// +// +// FiguredBassNumber::getNumberWithinOctave -- Get a reasonable figured bass number +// Replace 0 with 7 and -7 +// Replace 1 with 8 and -8 +// Replace 2 with 9 if it is a suspension of the ninth +// Allow 1 (unisono) in intervallsatz + +int FiguredBassNumber::getNumberWithinOctave(void) { + int num = m_number % 7; + + // Replace 0 with 7 and -7 + if ((abs(m_number) > 0) && (m_number % 7 == 0)) { + return m_number < 0 ? -7 : 7; + } + + // Replace 1 with 8 and -8 + if (abs(num) == 1) { + // Allow unisono in intervallsatz + if (m_intervallsatz) { + if (abs(m_number) == 1) { + return 1; + } } - int base40int = m_intervals[line][i]; - string iname = Convert::base40ToIntervalAbbr(base40int); - output += iname; - output += " "; + return m_number < 0 ? -8 : 8; } - if (!output.empty()) { - output.resize((int)output.size() - 1); + + // 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 output; + return num; } -// END_MERGE -} // end namespace hum +////////////////////////////// +// +// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor +// Helper class to store the mappings for abbreviate figured bass numbers +// + +FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector n) { + m_str = s; + m_numbers = n; +} + +////////////////////////////// +// +// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers +// + +vector FiguredBassAbbreviationMapping::s_mappings = { + new FiguredBassAbbreviationMapping("3", {}), + new FiguredBassAbbreviationMapping("5", {}), + new FiguredBassAbbreviationMapping("5 3", {}), + new FiguredBassAbbreviationMapping("6 3", {6}), + new FiguredBassAbbreviationMapping("5 4", {4}), + new FiguredBassAbbreviationMapping("7 5 3", {7}), + new FiguredBassAbbreviationMapping("7 3", {7}), + new FiguredBassAbbreviationMapping("7 5", {7}), + new FiguredBassAbbreviationMapping("6 5 3", {6, 5}), + new FiguredBassAbbreviationMapping("6 4 3", {4, 3}), + new FiguredBassAbbreviationMapping("6 4 2", {4, 2}), + new FiguredBassAbbreviationMapping("9 5 3", {9}), + new FiguredBassAbbreviationMapping("9 5", {9}), + new FiguredBassAbbreviationMapping("9 3", {9}), +}; +// END_MERGE + +} // end namespace hum diff --git a/src/tool-filter.cpp b/src/tool-filter.cpp index 37a77d13..d43ff794 100644 --- a/src/tool-filter.cpp +++ b/src/tool-filter.cpp @@ -29,6 +29,7 @@ #include "tool-dissonant.h" #include "tool-double.h" #include "tool-extract.h" +#include "tool-fb.h" #include "tool-flipper.h" #include "tool-gasparize.h" #include "tool-half.h" @@ -235,6 +236,8 @@ bool Tool_filter::run(HumdrumFileSet& infiles) { 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") {