diff --git a/include/vrv/note.h b/include/vrv/note.h index aaf6a461b5..28bc00c41b 100644 --- a/include/vrv/note.h +++ b/include/vrv/note.h @@ -13,6 +13,7 @@ //---------------------------------------------------------------------------- #include "accid.h" +#include "altsyminterface.h" #include "atts_analytical.h" #include "atts_externalsymbols.h" #include "atts_frettab.h" @@ -45,6 +46,7 @@ class Verse; */ class Note : public LayerElement, public StemmedDrawingInterface, + public AltSymInterface, public DurationInterface, public PitchInterface, public PositionInterface, @@ -80,6 +82,8 @@ class Note : public LayerElement, * @name Getter to interfaces */ ///@{ + AltSymInterface *GetAltSymInterface() override { return vrv_cast(this); } + const AltSymInterface *GetAltSymInterface() const override { return vrv_cast(this); } DurationInterface *GetDurationInterface() override { return vrv_cast(this); } const DurationInterface *GetDurationInterface() const override { return vrv_cast(this); } PitchInterface *GetPitchInterface() override { return vrv_cast(this); } @@ -145,7 +149,7 @@ class Note : public LayerElement, * @name Return the smufl string to use for a note give the notation type */ ///@{ - std::u32string GetTabFretString(data_NOTATIONTYPE notationType) const; + std::u32string GetTabFretString(data_NOTATIONTYPE notationType, int &overline, int &strike, int &underline) const; ///@} /** diff --git a/include/vrv/rest.h b/include/vrv/rest.h index 506df84a89..9a833f49cd 100644 --- a/include/vrv/rest.h +++ b/include/vrv/rest.h @@ -8,6 +8,7 @@ #ifndef __VRV_REST_H__ #define __VRV_REST_H__ +#include "altsyminterface.h" #include "atts_externalsymbols.h" #include "atts_mensural.h" #include "durationinterface.h" @@ -34,6 +35,7 @@ enum RestNotePlace { RNP_UNSET = -1, RNP_noteInSpace, RNP_noteOnLine }; * This class models the MEI element. */ class Rest : public LayerElement, + public AltSymInterface, public DurationInterface, public PositionInterface, public AttColor, @@ -69,6 +71,7 @@ class Rest : public LayerElement, * @name Getter to interfaces */ ///@{ + AltSymInterface *GetAltSymInterface() override { return vrv_cast(this); } PositionInterface *GetPositionInterface() override { return vrv_cast(this); } const PositionInterface *GetPositionInterface() const override { return vrv_cast(this); } DurationInterface *GetDurationInterface() override { return vrv_cast(this); } diff --git a/include/vrv/staffdef.h b/include/vrv/staffdef.h index 7e779f3df0..b59d303483 100644 --- a/include/vrv/staffdef.h +++ b/include/vrv/staffdef.h @@ -32,7 +32,8 @@ class StaffDef : public ScoreDefElement, public AttStaffDefLog, public AttStaffDefVis, public AttTimeBase, - public AttTransposition { + public AttTransposition, + public AttVerticalAlign { public: /** * @name Constructors, destructors, and other standard methods diff --git a/include/vrv/tabdursym.h b/include/vrv/tabdursym.h index 87e39ab218..e83d6b7819 100644 --- a/include/vrv/tabdursym.h +++ b/include/vrv/tabdursym.h @@ -21,7 +21,7 @@ namespace vrv { /** * This class models the MEI element. */ -class TabDurSym : public LayerElement, public StemmedDrawingInterface, public AttNNumberLike { +class TabDurSym : public LayerElement, public StemmedDrawingInterface, public AttNNumberLike, public AttStaffLoc { public: /** * @name Constructors, destructors, and other standard methods @@ -30,6 +30,7 @@ class TabDurSym : public LayerElement, public StemmedDrawingInterface, public At ///@{ TabDurSym(); virtual ~TabDurSym(); + Object *Clone() const override { return new TabDurSym(*this); } void Reset() override; std::string GetClassName() const override { return "TabDurSym"; } ///@} diff --git a/include/vrv/tabgrp.h b/include/vrv/tabgrp.h index 14e9f8ba4d..cf9dd6713b 100644 --- a/include/vrv/tabgrp.h +++ b/include/vrv/tabgrp.h @@ -29,6 +29,7 @@ class TabGrp : public LayerElement, public ObjectListInterface, public DurationI ///@{ TabGrp(); virtual ~TabGrp(); + Object *Clone() const override { return new TabGrp(*this); } void Reset() override; std::string GetClassName() const override { return "TabGrp"; } ///@} diff --git a/include/vrv/tuning.h b/include/vrv/tuning.h index bdb78867b9..d30b842856 100644 --- a/include/vrv/tuning.h +++ b/include/vrv/tuning.h @@ -40,9 +40,22 @@ class Tuning : public Object, public AttCourseLog { bool IsSupportedChild(Object *object) override; /** - * Return the line for a the tuning and a given course and a notation type + * Return the line for a note according to tablature type. + * Guitar, french and italian tablature: the line is based on the course. + * German tablature: the line is based on the note's index in the note list + * or by explicit @loc. + * + * @param[in] course + * @param[in] notationType + * @param[in] lines + * @param[in] listSize + * @param[in] index - 0 based from the bottom of the chord + * @param[in] loc - German tablature: note@loc if specified, 0 at the bottom + * @param[in] topAlign - German tablature: true => align at the top, false => align at the bottom + * @return position in staff half lines */ - int CalcPitchPos(int course, data_NOTATIONTYPE notationType, int lines) const; + int CalcPitchPos( + int course, data_NOTATIONTYPE notationType, int lines, int listSize, int index, int loc, bool topAlign) const; /** * Calculate the MIDI note number for course/fret diff --git a/include/vrv/view.h b/include/vrv/view.h index 63f55a2769..56cdedebee 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -232,7 +232,7 @@ class View { void DrawMeterSigGrp(DeviceContext *dc, Layer *layer, Staff *staff); void DrawMNum(DeviceContext *dc, MNum *mnum, Measure *measure, System *system, int yOffset); void DrawStaff(DeviceContext *dc, Staff *staff, Measure *measure, System *system); - void DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, System *system); + void DrawStaffLines(DeviceContext *dc, Staff *staff, StaffDef *staffDef, Measure *measure, System *system); void DrawLayer(DeviceContext *dc, Layer *layer, Staff *staff, Measure *measure); void DrawLayerList(DeviceContext *dc, Layer *layer, Staff *staff, Measure *measure, const ClassId classId); void DrawLayerDefLabels( diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h index b1fd69af2d..750326523f 100644 --- a/include/vrv/vrvdef.h +++ b/include/vrv/vrvdef.h @@ -695,6 +695,7 @@ enum MeasureType { MEASURED = 0, UNMEASURED, NEUMELINE }; //---------------------------------------------------------------------------- #define TABLATURE_STAFF_RATIO 1.75 +#define GERMAN_TAB_STAFF_RATIO 2.2 #define SUPER_SCRIPT_FACTOR 0.58 #define SUPER_SCRIPT_POSITION -0.20 // lowered down from the midline diff --git a/src/beam.cpp b/src/beam.cpp index 2cefcb2e3b..5a51746a94 100644 --- a/src/beam.cpp +++ b/src/beam.cpp @@ -602,8 +602,8 @@ void BeamSegment::CalcBeamInit( beamInterface->m_beamWidthBlack /= 2; beamInterface->m_beamWidthWhite /= 2; - // Adjust it further for tab.lute.french and tab.lute.italian - if (staff->IsTabLuteFrench() || staff->IsTabLuteItalian()) { + // Adjust it further for tab.lute.french, tab.lute.german and tab.lute.italian + if (staff->IsTabLuteFrench() || staff->IsTabLuteGerman() || staff->IsTabLuteItalian()) { beamInterface->m_beamWidthBlack = beamInterface->m_beamWidthBlack * 2 / 5; beamInterface->m_beamWidthWhite = beamInterface->m_beamWidthWhite * 3 / 5; } diff --git a/src/calcalignmentpitchposfunctor.cpp b/src/calcalignmentpitchposfunctor.cpp index 982d8078d8..dabdc80d3e 100644 --- a/src/calcalignmentpitchposfunctor.cpp +++ b/src/calcalignmentpitchposfunctor.cpp @@ -17,6 +17,7 @@ #include "rest.h" #include "score.h" #include "staff.h" +#include "tabgrp.h" #include "tuning.h" //---------------------------------------------------------------------------- @@ -105,8 +106,10 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE TabGrp *tabGrp = note->IsTabGrpNote(); if (tabGrp) { assert(staffY->m_drawingTuning); - loc = staffY->m_drawingTuning->CalcPitchPos( - note->GetTabCourse(), staffY->m_drawingNotationType, staffY->m_drawingLines); + assert(staffY->m_drawingStaffDef); + loc = staffY->m_drawingTuning->CalcPitchPos(note->GetTabCourse(), staffY->m_drawingNotationType, + staffY->m_drawingLines, tabGrp->GetListSize(), tabGrp->GetListIndex(note), note->GetLoc(), + staffY->m_drawingStaffDef->GetValign() != VERTICALALIGNMENT_bottom); } else if ((note->HasPname() && (note->HasOct() || note->HasOctDefault())) || note->HasLoc()) { loc = PitchInterface::CalcLoc(note, layerY, layerElementY); @@ -307,7 +310,7 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE else if (layerElement->Is(TABDURSYM)) { int yRel = 0; if (staffY->IsTabWithStemsOutside()) { - double spacingRatio = (staffY->IsTabLuteFrench()) ? 2.0 : 1.0; + double spacingRatio = (staffY->IsTabLuteFrench() || staffY->IsTabLuteGerman()) ? 2.0 : 1.0; yRel += m_doc->GetDrawingUnit(staffY->m_drawingStaffSize) * spacingRatio; } layerElement->SetDrawingYRel(yRel); diff --git a/src/iomei.cpp b/src/iomei.cpp index 5968378c37..e71c091392 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -1840,6 +1840,7 @@ void MEIOutput::WriteStaffDef(pugi::xml_node currentNode, StaffDef *staffDef) staffDef->WriteStaffDefVis(currentNode); staffDef->WriteTimeBase(currentNode); staffDef->WriteTransposition(currentNode); + staffDef->WriteVerticalAlign(currentNode); } void MEIOutput::WriteInstrDef(pugi::xml_node currentNode, InstrDef *instrDef) @@ -2763,6 +2764,7 @@ void MEIOutput::WriteNote(pugi::xml_node currentNode, Note *note) assert(note); this->WriteLayerElement(currentNode, note); + this->WriteAltSymInterface(currentNode, note); this->WriteDurationInterface(currentNode, note); this->WritePitchInterface(currentNode, note); this->WritePositionInterface(currentNode, note); @@ -2823,6 +2825,7 @@ void MEIOutput::WriteRest(pugi::xml_node currentNode, Rest *rest) assert(rest); this->WriteLayerElement(currentNode, rest); + this->WriteAltSymInterface(currentNode, rest); this->WriteDurationInterface(currentNode, rest); this->WritePositionInterface(currentNode, rest); rest->WriteColor(currentNode); @@ -2856,6 +2859,7 @@ void MEIOutput::WriteTabDurSym(pugi::xml_node currentNode, TabDurSym *tabDurSym) this->WriteLayerElement(currentNode, tabDurSym); tabDurSym->WriteNNumberLike(currentNode); + tabDurSym->WriteStaffLoc(currentNode); } void MEIOutput::WriteTabGrp(pugi::xml_node currentNode, TabGrp *tabGrp) @@ -3842,6 +3846,9 @@ bool MEIInput::IsAllowed(std::string element, Object *filterParent) if (element == "note") { return true; } + if (element == "rest") { + return true; + } else { return false; } @@ -5207,6 +5214,7 @@ bool MEIInput::ReadStaffDef(Object *parent, pugi::xml_node staffDef) vrvStaffDef->ReadStaffDefVis(staffDef); vrvStaffDef->ReadTimeBase(staffDef); vrvStaffDef->ReadTransposition(staffDef); + vrvStaffDef->ReadVerticalAlign(staffDef); if (!vrvStaffDef->HasN()) { LogWarning("No @n on might yield unpredictable results"); @@ -6967,6 +6975,7 @@ bool MEIInput::ReadNote(Object *parent, pugi::xml_node note) } } + this->ReadAltSymInterface(note, vrvNote); this->ReadDurationInterface(note, vrvNote); this->ReadPitchInterface(note, vrvNote); this->ReadPositionInterface(note, vrvNote); @@ -7021,6 +7030,7 @@ bool MEIInput::ReadRest(Object *parent, pugi::xml_node rest) } } + this->ReadAltSymInterface(rest, vrvRest); this->ReadDurationInterface(rest, vrvRest); this->ReadPositionInterface(rest, vrvRest); vrvRest->ReadColor(rest); @@ -7150,6 +7160,7 @@ bool MEIInput::ReadTabDurSym(Object *parent, pugi::xml_node tabRhyhtm) this->ReadLayerElement(tabRhyhtm, vrvTabDurSym); vrvTabDurSym->ReadNNumberLike(tabRhyhtm); + vrvTabDurSym->ReadStaffLoc(tabRhyhtm); parent->AddChild(vrvTabDurSym); this->ReadUnsupportedAttr(tabRhyhtm, vrvTabDurSym); @@ -7287,6 +7298,9 @@ bool MEIInput::ReadSymbolDefChildren(Object *parent, pugi::xml_node parentNode, else if (elementName == "svg") { success = this->ReadSvg(parent, xmlElement); } + else if (elementName == "symbol") { + success = this->ReadSymbol(parent, xmlElement); + } // xml comment else if (elementName == "") { success = this->ReadXMLComment(parent, xmlElement); diff --git a/src/note.cpp b/src/note.cpp index f0bd536eca..3af77d8737 100644 --- a/src/note.cpp +++ b/src/note.cpp @@ -31,6 +31,7 @@ #include "staff.h" #include "stem.h" #include "syl.h" +#include "symboldef.h" #include "tabgrp.h" #include "tie.h" #include "tuning.h" @@ -52,6 +53,7 @@ static const ClassRegistrar s_factory("note", NOTE); Note::Note() : LayerElement(NOTE, "note-") , StemmedDrawingInterface() + , AltSymInterface() , DurationInterface() , PitchInterface() , PositionInterface() @@ -71,6 +73,7 @@ Note::Note() , AttTiePresent() , AttVisibility() { + this->RegisterInterface(AltSymInterface::GetAttClasses(), AltSymInterface::IsInterface()); this->RegisterInterface(DurationInterface::GetAttClasses(), DurationInterface::IsInterface()); this->RegisterInterface(PitchInterface::GetAttClasses(), PitchInterface::IsInterface()); this->RegisterInterface(PositionInterface::GetAttClasses(), PositionInterface::IsInterface()); @@ -99,6 +102,7 @@ void Note::Reset() { LayerElement::Reset(); StemmedDrawingInterface::Reset(); + AltSymInterface::Reset(); DurationInterface::Reset(); PitchInterface::Reset(); PositionInterface::Reset(); @@ -251,25 +255,78 @@ const TabGrp *Note::IsTabGrpNote() const return vrv_cast(this->GetFirstAncestor(TABGRP, MAX_TABGRP_DEPTH)); } -std::u32string Note::GetTabFretString(data_NOTATIONTYPE notationType) const +std::u32string Note::GetTabFretString(data_NOTATIONTYPE notationType, int &overline, int &strike, int &underline) const { + overline = 0; + strike = 0; + underline = 0; + + // @glyph.num, @glyph.name or @altsym + const Resources *resources = this->GetDocResources(); + if (resources != NULL) { + std::u32string fretStr; + // If there is @glyph.num, return glyph based on it (first priority) + if (this->HasGlyphNum()) { + const char32_t code = this->GetGlyphNum(); + if (NULL != resources->GetGlyph(code)) fretStr.push_back(code); + } + + // If there is @glyph.name (second priority) + else if (this->HasGlyphName()) { + const char32_t code = resources->GetGlyphCode(this->GetGlyphName()); + if (NULL != resources->GetGlyph(code)) fretStr.push_back(code); + } + + // If there is @altsym (third priority) + else if (this->HasAltsym() && this->HasAltSymbolDef()) { + const SymbolDef *symbolDef = this->GetAltSymbolDef(); + + // there may be more than one + for (const Object *child : symbolDef->GetChildren()) { + if (child->Is(SYMBOL)) { + const Symbol *symbol = vrv_cast(child); + + // If there is @glyph.num, return glyph based on it (fourth priority) + if (symbol->HasGlyphNum()) { + const char32_t code = symbol->GetGlyphNum(); + if (NULL != resources->GetGlyph(code)) fretStr.push_back(code); + } + + // If there is @glyph.name (fifth priority) + else if (symbol->HasGlyphName()) { + const char32_t code = resources->GetGlyphCode(symbol->GetGlyphName()); + if (NULL != resources->GetGlyph(code)) fretStr.push_back(code); + } + } + } + } + + if (!fretStr.empty()) return fretStr; + } + if (notationType == NOTATIONTYPE_tab_lute_italian) { std::u32string fretStr; - int fret = this->GetTabFret(); - // Maximum allowed would be 19 (always bindly addind 1 as first figure) - if (fret > 9) fretStr.push_back(SMUFL_EBE1_luteItalianFret1); - switch (fret % 10) { - case 0: fretStr.push_back(SMUFL_EBE0_luteItalianFret0); break; - case 1: fretStr.push_back(SMUFL_EBE1_luteItalianFret1); break; - case 2: fretStr.push_back(SMUFL_EBE2_luteItalianFret2); break; - case 3: fretStr.push_back(SMUFL_EBE3_luteItalianFret3); break; - case 4: fretStr.push_back(SMUFL_EBE4_luteItalianFret4); break; - case 5: fretStr.push_back(SMUFL_EBE5_luteItalianFret5); break; - case 6: fretStr.push_back(SMUFL_EBE6_luteItalianFret6); break; - case 7: fretStr.push_back(SMUFL_EBE7_luteItalianFret7); break; - case 8: fretStr.push_back(SMUFL_EBE8_luteItalianFret8); break; - case 9: fretStr.push_back(SMUFL_EBE9_luteItalianFret9); break; - default: break; + const int fret = this->GetTabFret(); + const int course = this->GetTabCourse(); + + // Italian tablature glyphs are contiguous + static_assert(SMUFL_EBE1_luteItalianFret1 == SMUFL_EBE0_luteItalianFret0 + 1); + static_assert(SMUFL_EBE2_luteItalianFret2 == SMUFL_EBE0_luteItalianFret0 + 2); + // ... + static_assert(SMUFL_EBE9_luteItalianFret9 == SMUFL_EBE0_luteItalianFret0 + 9); + + if (course <= 7 || fret != 0) { + const auto decimal = std::div(fret, 10); + if (decimal.quot > 0) fretStr.push_back(SMUFL_EBE0_luteItalianFret0 + decimal.quot); + if (decimal.rem <= 9) fretStr.push_back(SMUFL_EBE0_luteItalianFret0 + decimal.rem); + if (course >= 7) strike = 1; // course 7 and fretted courses >= 8 use a ledger line + underline = std::max(0, course - 7); // compressed ledger lines for fretted courses >= 8 + } + else { + // open courses >= 8 just use the number of the course on stave line 7 + const auto decimal = std::div(course, 10); + if (decimal.quot > 0) fretStr.push_back(SMUFL_EBE0_luteItalianFret0 + decimal.quot); + if (decimal.rem <= 9) fretStr.push_back(SMUFL_EBE0_luteItalianFret0 + decimal.rem); } return fretStr; } @@ -320,6 +377,75 @@ std::u32string Note::GetTabFretString(data_NOTATIONTYPE notationType) const } return fretStr; } + else if (notationType == NOTATIONTYPE_tab_lute_german) { + std::u32string fretStr; + const int fret = this->GetTabFret(); + const int course = this->GetTabCourse(); + + // SMuFL has glyphs for German lute tablature following Hans and Melchior Newsidler's notation + // for the >= 6th courses. + // "German Renaissance lute tablature (U+EC00–U+EC2F)" + // https://w3c.github.io/smufl/latest/tables/german-renaissance-lute-tablature.html + // + // However, some glyphs are missing: + // + // Digit 1 with a strike through for the open 6th course. + // Digit 1 with two strike throughs for the open 7th course. + // Digit 1 with three strike throughs for the open 8th course. + // "et" for 2nd course 5th fret. + // "con" for 1st course 5th fret. + // Gothic font digits 1-5 for the open courses <= 5. + // Second lowercase alphabet with an overline used for courses <= 5 frets 6 to 10. + // + // To overcome these omissions I've substituted missing glyphs from other + // parts of the SMuFL collection. Overlines and strike throughs are drawn separately. + + if (course >= 6 && fret >= 0 && fret <= 13) { + // + A B C D ... + if (fret == 0) { + fretStr = SMUFL_EA51_figbass1; // substitute for 1 with oblique stroke + strike = course - 5; // 6 course 1 strike, 7 course 2 strikes, ... + } + else { + // The German tablature uppercase letters A-N are contiguous, correctly omitting J + static_assert(SMUFL_EC23_luteGermanNUpper == SMUFL_EC17_luteGermanAUpper + 13 - 1); + fretStr = SMUFL_EC17_luteGermanAUpper + fret - 1; + overline = course - 6; // 6 course 0 overline, 7 course 1 overline, ... + } + } + else if (course >= 1 && course <= 5 && fret == 0) { + // Substitute for gothic digits + static const char32_t digit[] = { SMUFL_EA51_figbass1, SMUFL_EA52_figbass2, SMUFL_EA54_figbass3, + SMUFL_EA55_figbass4, SMUFL_EA57_figbass5 }; + fretStr = digit[5 - course]; + } + else if (course >= 1 && course <= 5 && fret >= 0 && fret <= 10) { + const int firstAlphabetFret = fret <= 5 ? fret : fret - 5; // map second alphabet to first + + if (course == 2 && firstAlphabetFret == 5) { + // TODO replace with U+EC24, luteGermanEt when available + // https://github.com/w3c/smufl/issues/274 + fretStr = SMUFL_EA5F_figbass7Raised2; // substitute for "et" + } + else if (course == 1 && firstAlphabetFret == 5) { + // TODO replace with U+EC25, luteGermanCon when available + // https://github.com/w3c/smufl/issues/274 + fretStr = SMUFL_EA61_figbass9; // substitute for "con" + } + else { + // The German tablature lowercase letters a-z are contiguous, correctly omitting j u w + static_assert(SMUFL_EC16_luteGermanZLower == SMUFL_EC00_luteGermanALower + 23 - 1); + + // lowercase letters run 5th to 1st course, frets 1 to 5 + // and frets 6 to 10 with an overline + fretStr = SMUFL_EC00_luteGermanALower + (5 - course) + (firstAlphabetFret - 1) * 5; + } + + // second alphabet needs an overline + overline = (fret >= 6) ? 1 : 0; + } + return fretStr; + } else { std::string str = StringFormat("%d", this->GetTabFret()); return UTF8to32(str); diff --git a/src/positioninterface.cpp b/src/positioninterface.cpp index d3bf0abed6..0a20079ba1 100644 --- a/src/positioninterface.cpp +++ b/src/positioninterface.cpp @@ -76,6 +76,17 @@ bool PositionInterface::HasLedgerLines(int &linesAbove, int &linesBelow, const S { if (!staff) return false; + if (staff->IsTabLuteFrench() || staff->IsTabLuteGerman() || staff->IsTabLuteItalian()) { + // French and German tablature do not use ledger lines. + // Italian tablature does use a single ledger line for 7th course, and compressed + // ledger lines for fretted 8th and above, but not for open 8th and above. So + // rather than use the CMN ledger line handling we draw our own. + // Guitar tablature has been left as originally implemented. + linesAbove = 0; + linesBelow = 0; + return false; + } + linesAbove = (this->GetDrawingLoc() - staff->m_drawingLines * 2 + 2) / 2; linesBelow = -(this->GetDrawingLoc()) / 2; diff --git a/src/rest.cpp b/src/rest.cpp index 7e622d604d..477f707d08 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -22,6 +22,7 @@ #include "layer.h" #include "smufl.h" #include "staff.h" +#include "symboldef.h" #include "system.h" #include "transposition.h" #include "vrv.h" @@ -171,6 +172,7 @@ static const ClassRegistrar s_factory("rest", REST); Rest::Rest() : LayerElement(REST, "rest-") + , AltSymInterface() , DurationInterface() , PositionInterface() , AttColor() @@ -179,6 +181,7 @@ Rest::Rest() , AttExtSymNames() , AttRestVisMensural() { + this->RegisterInterface(AltSymInterface::GetAttClasses(), AltSymInterface::IsInterface()); this->RegisterInterface(DurationInterface::GetAttClasses(), DurationInterface::IsInterface()); this->RegisterInterface(PositionInterface::GetAttClasses(), PositionInterface::IsInterface()); this->RegisterAttClass(ATT_COLOR); @@ -194,6 +197,7 @@ Rest::~Rest() {} void Rest::Reset() { LayerElement::Reset(); + AltSymInterface::Reset(); DurationInterface::Reset(); PositionInterface::Reset(); this->ResetColor(); @@ -259,6 +263,23 @@ char32_t Rest::GetRestGlyph(const data_DURATION duration) const char32_t code = resources->GetGlyphCode(this->GetGlyphName()); if (NULL != resources->GetGlyph(code)) return code; } + // If there is @altsym (third priority) + else if (this->HasAltsym() && this->HasAltSymbolDef()) { + const SymbolDef *symbolDef = this->GetAltSymbolDef(); + const Symbol *symbol = vrv_cast(symbolDef->GetFirst(SYMBOL)); + if (symbol != NULL) { + // If there is @glyph.num, return glyph based on it (fourth priority) + if (symbol->HasGlyphNum()) { + const char32_t code = symbol->GetGlyphNum(); + if (NULL != resources->GetGlyph(code)) return code; + } + // If there is @glyph.name (fifth priority) + else if (symbol->HasGlyphName()) { + const char32_t code = resources->GetGlyphCode(symbol->GetGlyphName()); + if (NULL != resources->GetGlyph(code)) return code; + } + } + } if (this->IsMensuralDur()) { switch (duration) { diff --git a/src/staff.cpp b/src/staff.cpp index 26de629c67..4d1188afb3 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -186,6 +186,8 @@ void Staff::AdjustDrawingStaffSize() int Staff::GetDrawingStaffNotationSize() const { + if (this->IsTabLuteGerman()) return m_drawingStaffSize / GERMAN_TAB_STAFF_RATIO; + return (this->IsTablature()) ? m_drawingStaffSize / TABLATURE_STAFF_RATIO : m_drawingStaffSize; } diff --git a/src/staffdef.cpp b/src/staffdef.cpp index 5a86e2a6c4..5d55f3c8f8 100644 --- a/src/staffdef.cpp +++ b/src/staffdef.cpp @@ -42,6 +42,7 @@ StaffDef::StaffDef() , AttStaffDefVis() , AttTimeBase() , AttTransposition() + , AttVerticalAlign() { this->RegisterAttClass(ATT_DISTANCES); this->RegisterAttClass(ATT_LABELLED); @@ -52,6 +53,7 @@ StaffDef::StaffDef() this->RegisterAttClass(ATT_STAFFDEFVIS); this->RegisterAttClass(ATT_TIMEBASE); this->RegisterAttClass(ATT_TRANSPOSITION); + this->RegisterAttClass(ATT_VERTICALALIGN); this->Reset(); } @@ -71,6 +73,7 @@ void StaffDef::Reset() this->ResetStaffDefVis(); this->ResetTimeBase(); this->ResetTransposition(); + this->ResetVerticalAlign(); m_drawingVisibility = OPTIMIZATION_NONE; } diff --git a/src/symboldef.cpp b/src/symboldef.cpp index a0fe1d66c7..eb58a10fcd 100644 --- a/src/symboldef.cpp +++ b/src/symboldef.cpp @@ -16,6 +16,7 @@ #include "doc.h" #include "graphic.h" #include "svg.h" +#include "symbol.h" #include "vrv.h" namespace vrv { @@ -48,6 +49,9 @@ bool SymbolDef::IsSupportedChild(Object *child) else if (child->Is(SVG)) { assert(dynamic_cast(child)); } + else if (child->Is(SYMBOL)) { + assert(dynamic_cast(child)); + } else { return false; } diff --git a/src/tabdursym.cpp b/src/tabdursym.cpp index 21d03c05f6..2bb64949d1 100644 --- a/src/tabdursym.cpp +++ b/src/tabdursym.cpp @@ -31,9 +31,11 @@ namespace vrv { static const ClassRegistrar s_factory("tabDurSym", TABDURSYM); -TabDurSym::TabDurSym() : LayerElement(TABDURSYM, "tabdursym-"), StemmedDrawingInterface(), AttNNumberLike() +TabDurSym::TabDurSym() + : LayerElement(TABDURSYM, "tabdursym-"), StemmedDrawingInterface(), AttNNumberLike(), AttStaffLoc() { this->RegisterAttClass(ATT_NNUMBERLIKE); + this->RegisterAttClass(ATT_STAFFLOC); this->Reset(); } @@ -45,6 +47,7 @@ void TabDurSym::Reset() LayerElement::Reset(); StemmedDrawingInterface::Reset(); this->ResetNNumberLike(); + this->ResetStaffLoc(); } bool TabDurSym::IsSupportedChild(Object *child) @@ -89,7 +92,7 @@ void TabDurSym::AdjustDrawingYRel(const Staff *staff, const Doc *doc) // For stems outside add a margin to the tabDurSym - otherwise attached to the staff line if (staff->IsTabWithStemsOutside()) { - double spacingRatio = (staff->IsTabLuteFrench()) ? 2.0 : 1.0; + double spacingRatio = (staff->IsTabLuteFrench() || staff->IsTabLuteGerman()) ? 2.0 : 1.0; yRel += doc->GetDrawingUnit(staff->m_drawingStaffSize) * spacingRatio; } @@ -116,8 +119,11 @@ int TabDurSym::CalcStemLenInThirdUnits(const Staff *staff, data_STEMDIRECTION st int baseStem = STANDARD_STEMLENGTH_TAB * 3; + // Shorter for german lute tablature to match ryhthm glyphs + if (staff->IsTabLuteGerman()) baseStem -= 3; // One unit longer for guitar tablature - if (staff->IsTabGuitar()) baseStem += 3; + else if (staff->IsTabGuitar()) + baseStem += 3; // One unit longer for stems inside the staff if (!staff->IsTabWithStemsOutside()) baseStem += 3; diff --git a/src/tabgrp.cpp b/src/tabgrp.cpp index 72436718d3..d9b668b073 100644 --- a/src/tabgrp.cpp +++ b/src/tabgrp.cpp @@ -16,6 +16,7 @@ #include "editorial.h" #include "functor.h" #include "note.h" +#include "rest.h" #include "tabdursym.h" namespace vrv { @@ -46,6 +47,9 @@ bool TabGrp::IsSupportedChild(Object *child) if (child->Is(NOTE)) { assert(dynamic_cast(child)); } + else if (child->Is(REST)) { + assert(dynamic_cast(child)); + } else if (child->Is(TABDURSYM)) { assert(dynamic_cast(child)); } diff --git a/src/tuning.cpp b/src/tuning.cpp index 7ebbdc1e4b..1dde76662f 100644 --- a/src/tuning.cpp +++ b/src/tuning.cpp @@ -56,13 +56,27 @@ bool Tuning::IsSupportedChild(Object *child) return true; } -int Tuning::CalcPitchPos(int course, data_NOTATIONTYPE notationType, int lines) const +int Tuning::CalcPitchPos( + int course, data_NOTATIONTYPE notationType, int lines, int listSize, int index, int loc, bool topAlign) const { switch (notationType) { case NOTATIONTYPE_tab_lute_french: // all courses >= 7 are positioned above line 0 return (lines - std::min(course, 7)) * 2 + 1; // above the line - case NOTATIONTYPE_tab_lute_italian: return (course - 1) * 2; + case NOTATIONTYPE_tab_lute_italian: + // all courses >= 7 are positioned on line 7 + return (std::min(course, 7) - 1) * 2; + case NOTATIONTYPE_tab_lute_german: + if (loc != MEI_UNSET) { + return loc; + } + else if (topAlign) { + return (lines - (listSize - index)) * 2; + } + else { + // bottom align + return index * 2; + } case NOTATIONTYPE_tab_guitar: [[fallthrough]]; default: return abs(course - lines) * 2; } diff --git a/src/view_beam.cpp b/src/view_beam.cpp index 046e24e190..1673c5d82a 100644 --- a/src/view_beam.cpp +++ b/src/view_beam.cpp @@ -292,7 +292,7 @@ void View::DrawBeamSegment( data_DURATION durRef = DURATION_8; data_DURATION durRef2 = DURATION_16; - if (staff->IsTabLuteFrench() || staff->IsTabLuteItalian()) { + if (staff->IsTabLuteFrench() || staff->IsTabLuteGerman() || staff->IsTabLuteItalian()) { durRef = DURATION_4; durRef2 = DURATION_8; } diff --git a/src/view_element.cpp b/src/view_element.cpp index c4166e20b9..cad7563ea2 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -53,6 +53,7 @@ #include "stem.h" #include "syl.h" #include "system.h" +#include "tabgrp.h" #include "tie.h" #include "tuplet.h" #include "verse.h" @@ -1538,12 +1539,17 @@ void View::DrawRest(DeviceContext *dc, LayerElement *element, Layer *layer, Staf const bool drawingCueSize = rest->GetDrawingCueSize(); const int staffSize = staff->GetDrawingStaffNotationSize(); data_DURATION drawingDur = rest->GetActualDur(); - if (drawingDur == DURATION_NONE) { - if (!dc->Is(BBOX_DEVICE_CONTEXT)) { - LogWarning("Missing duration for rest '%s'", rest->GetID().c_str()); - } + // in tablature the @dur is in the parent TabGrp - try to get if from there + if ((drawingDur == DURATION_NONE) && staff->IsTablature()) { + TabGrp *tabGrp = vrv_cast(rest->GetFirstAncestor(TABGRP)); + if (tabGrp != NULL) drawingDur = tabGrp->GetActualDur(); + } + // Make sure we have something to draw + if ((drawingDur == DURATION_NONE) && !dc->Is(BBOX_DEVICE_CONTEXT)) { + LogWarning("Missing duration for rest '%s'", rest->GetID().c_str()); drawingDur = DURATION_4; } + const char32_t drawingGlyph = rest->GetRestGlyph(drawingDur); const int x = element->GetDrawingX(); diff --git a/src/view_page.cpp b/src/view_page.cpp index ccabdb3237..304095385a 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1253,9 +1253,7 @@ void View::DrawStaff(DeviceContext *dc, Staff *staff, Measure *measure, System * staff->SetFromFacsimile(m_doc); } - if (staffDef && (staffDef->GetLinesVisible() != BOOLEAN_false)) { - this->DrawStaffLines(dc, staff, measure, system); - } + this->DrawStaffLines(dc, staff, staffDef, measure, system); if (staffDef && (m_doc->GetType() != Facs)) { this->DrawStaffDef(dc, staff, measure); @@ -1285,13 +1283,22 @@ void View::DrawStaff(DeviceContext *dc, Staff *staff, Measure *measure, System * dc->EndGraphic(staff, this); } -void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, System *system) +void View::DrawStaffLines(DeviceContext *dc, Staff *staff, StaffDef *staffDef, Measure *measure, System *system) { assert(dc); assert(staff); assert(measure); assert(system); + // If German lute tablature the default is @lines.visible="false", but setting @lines.visible="true" + // will draw the staff lines. + bool gltLines = (staff->IsTabLuteGerman() && staffDef->GetLinesVisible() != BOOLEAN_true); + // For anything other than German lute tablature the default is @lines.visible="true" + bool visibleLines = (staffDef->GetLinesVisible() != BOOLEAN_false); + + // Nothing to do if both are false + if (!gltLines && !visibleLines) return; + int j, x1, x2, y1, y2; x1 = measure->GetDrawingX(); @@ -1308,35 +1315,51 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, Measure *measure, Sys dc->SetPen(m_currentColor, ToDeviceContextX(lineWidth), AxSOLID); dc->SetBrush(m_currentColor, AxSOLID); - for (j = 0; j < staff->m_drawingLines; ++j) { - // Skewed lines - with Facs (neumes) only for now - if (y1 != y2) { - dc->DrawLine(ToDeviceContextX(x1), ToDeviceContextY(y1), ToDeviceContextX(x2), ToDeviceContextY(y2)); - // For drawing rectangles instead of lines - y1 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - y2 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - } - else { - const bool isFrenchOrItalianTablature = (staff->IsTabLuteFrench() || staff->IsTabLuteItalian()); - SegmentedLine line(x1, x2); - // We do not need to do this during layout calculation - and only with tablature but not for French or - // Italian tablature - if (!dc->Is(BBOX_DEVICE_CONTEXT) && staff->IsTablature() && !isFrenchOrItalianTablature) { - Object fullLine; - fullLine.SetParent(system); - fullLine.UpdateContentBBoxY(y1 + (lineWidth / 2), y1 - (lineWidth / 2)); - fullLine.UpdateContentBBoxX(x1, x2); - int margin = m_doc->GetDrawingUnit(100) / 2; - ListOfObjects notes = staff->FindAllDescendantsByType(NOTE, false); - for (Object *note : notes) { - if (note->VerticalContentOverlap(&fullLine, margin / 2)) { - line.AddGap(note->GetContentLeft() - margin, note->GetContentRight() + margin); + // If German lute tablature the default is @lines.visible="false", but setting @lines.visible="true" + // will draw the staff lines. + // For anything other than German lute tablature the default is @lines.visible="true" + if (gltLines) { + // German tablature has no staff, just a single base line + // But internally we maintain the fiction of an invisible staff as a coordinate system + SegmentedLine line(x1, x2); + // Issue #3589 move base line slightly further down and reduce thickness + y1 -= (m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * staff->m_drawingLines) * 11 / 10; + this->DrawHorizontalSegmentedLine(dc, y1, line, lineWidth / 2); + } + // Normal staff lines + else { + // draw staff lines + for (j = 0; j < staff->m_drawingLines; ++j) { + // Skewed lines - with Facs (neumes) only for now + if (y1 != y2) { + dc->DrawLine(ToDeviceContextX(x1), ToDeviceContextY(y1), ToDeviceContextX(x2), ToDeviceContextY(y2)); + // For drawing rectangles instead of lines + y1 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + y2 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + } + else { + const bool isFrenchOrGermanOrItalianTablature + = (staff->IsTabLuteFrench() || staff->IsTabLuteGerman() || staff->IsTabLuteItalian()); + SegmentedLine line(x1, x2); + // We do not need to do this during layout calculation - and only with guitar tablature but not for + // French, German or Italian lute tablature + if (!dc->Is(BBOX_DEVICE_CONTEXT) && staff->IsTablature() && !isFrenchOrGermanOrItalianTablature) { + Object fullLine; + fullLine.SetParent(system); + fullLine.UpdateContentBBoxY(y1 + (lineWidth / 2), y1 - (lineWidth / 2)); + fullLine.UpdateContentBBoxX(x1, x2); + int margin = m_doc->GetDrawingUnit(100) / 2; + ListOfObjects notes = staff->FindAllDescendantsByType(NOTE, false); + for (Object *note : notes) { + if (note->VerticalContentOverlap(&fullLine, margin / 2)) { + line.AddGap(note->GetContentLeft() - margin, note->GetContentRight() + margin); + } } } + this->DrawHorizontalSegmentedLine(dc, y1, line, lineWidth); + y1 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + y2 = y1; } - this->DrawHorizontalSegmentedLine(dc, y1, line, lineWidth); - y1 -= m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); - y2 = y1; } } diff --git a/src/view_tab.cpp b/src/view_tab.cpp index 6fa926578e..44a247351b 100644 --- a/src/view_tab.cpp +++ b/src/view_tab.cpp @@ -22,6 +22,7 @@ #include "rend.h" #include "smufl.h" #include "staff.h" +#include "staffdef.h" #include "stem.h" #include "system.h" #include "tabdursym.h" @@ -103,10 +104,13 @@ void View::DrawTabNote(DeviceContext *dc, LayerElement *element, Layer *layer, S int glyphSize = staff->GetDrawingStaffNotationSize(); bool drawingCueSize = false; + int overline = 0; + int strike = 0; + int underline = 0; if (staff->m_drawingNotationType == NOTATIONTYPE_tab_guitar) { - std::u32string fret = note->GetTabFretString(staff->m_drawingNotationType); + std::u32string fret = note->GetTabFretString(staff->m_drawingNotationType, overline, strike, underline); FontInfo fretTxt; if (!dc->UseGlobalStyling()) { @@ -132,7 +136,7 @@ void View::DrawTabNote(DeviceContext *dc, LayerElement *element, Layer *layer, S } else { - std::u32string fret = note->GetTabFretString(staff->m_drawingNotationType); + std::u32string fret = note->GetTabFretString(staff->m_drawingNotationType, overline, strike, underline); // Center for italian tablature if (staff->IsTabLuteItalian()) { y -= (m_doc->GetGlyphHeight(SMUFL_EBE0_luteItalianFret0, glyphSize, drawingCueSize) / 2); @@ -142,9 +146,59 @@ void View::DrawTabNote(DeviceContext *dc, LayerElement *element, Layer *layer, S y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize) - m_doc->GetDrawingStaffLineWidth(staff->m_drawingStaffSize); } + // Center for German tablature + else if (staff->IsTabLuteGerman()) { + y -= m_doc->GetGlyphHeight(SMUFL_EC17_luteGermanAUpper, glyphSize, drawingCueSize) / 2; + } dc->SetFont(m_doc->GetDrawingSmuflFont(glyphSize, false)); this->DrawSmuflString(dc, x, y, fret, HORIZONTALALIGNMENT_center, glyphSize); + + // Add overlines, strikethoughs and underlines if required + if ((overline > 0 || strike > 0 || underline > 0) && !fret.empty()) { + const int lineThickness + = m_options->m_lyricLineThickness.GetValue() * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + const int widthFront = m_doc->GetGlyphWidth(fret.front(), glyphSize, drawingCueSize); + const int widthBack = m_doc->GetGlyphWidth(fret.back(), glyphSize, drawingCueSize); + TextExtend extend; + dc->GetSmuflTextExtent(fret, &extend); + + // TODO These fiddle factors seem necessary to get the lines balanced on either side + // of the fret string. Can we do better? + const int x1 + = x - (fret.size() == 1 ? widthFront * 7 / 10 : widthFront * 12 / 10); // extend on the left hand side + const int x2 = x + extend.m_width - widthBack * 1 / 10; // trim right hand overhang on last character + + dc->SetPen(m_currentColor, lineThickness, AxSOLID); + dc->SetBrush(m_currentColor, AxSOLID); + + // overlines + int y1 = y + extend.m_ascent + lineThickness; + + for (int i = 0; i < overline; ++i) { + dc->DrawLine(ToDeviceContextX(x1), ToDeviceContextY(y1), ToDeviceContextX(x2), ToDeviceContextY(y1)); + y1 += 2 * lineThickness; + } + + // strikethroughs + y1 = y + extend.m_ascent / 2 - (strike - 1) * lineThickness; + + for (int i = 0; i < strike; ++i) { + dc->DrawLine(ToDeviceContextX(x1), ToDeviceContextY(y1), ToDeviceContextX(x2), ToDeviceContextY(y1)); + y1 += 2 * lineThickness; + } + + // underlines + y1 = y - extend.m_descent - lineThickness; + + for (int i = 0; i < underline; ++i) { + dc->DrawLine(ToDeviceContextX(x1), ToDeviceContextY(y1), ToDeviceContextX(x2), ToDeviceContextY(y1)); + y1 -= 2 * lineThickness; + } + + dc->ResetPen(); + dc->ResetBrush(); + } dc->ResetFont(); } @@ -167,10 +221,23 @@ void View::DrawTabDurSym(DeviceContext *dc, LayerElement *element, Layer *layer, dc->StartGraphic(tabDurSym, "", tabDurSym->GetID()); + if (tabDurSym->HasLoc()) { + const int yRel = ((staff->m_drawingLines - 1) * 2 - tabDurSym->GetLoc()) + * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + tabDurSym->SetDrawingYRel(-yRel); + } + else if (staff->IsTabLuteItalian()) { + // make space for 7th course + const int yRel + = ((staff->m_drawingLines - 1) * 2 - 7 * 2 + 1) * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + tabDurSym->SetDrawingYRel(-yRel); + } + int x = element->GetDrawingX(); int y = element->GetDrawingY(); const int glyphSize = staff->GetDrawingStaffNotationSize(); + const int drawingDur = (tabGrp->GetDurGes() != DURATION_NONE) ? tabGrp->GetActualDurGes() : tabGrp->GetActualDur(); // For beam and guitar notation, stem are drawn through the child Stem