Skip to content

Commit

Permalink
TAB - Mixing mensural value symbols and beaming in historic tablatures
Browse files Browse the repository at this point in the history
__References__: Technology Preview forum post with discussion, screen-shots and links to additional threads https://musescore.org/en/node/81051

Implements the possibility to mix, in TAB's with note symbols at staff side, mensural value symbols (discreet glyphs) with the 'grid'-shaped beaming found in historical sources and commonly used in lute literature.

The beaming is implemented by special drawing of the `TabDurationSymbol` element holding the note value symbol, using drawing primitives instead of the relevant font glyph.

The user may choose between the two renderings (discreet glyph or beaming grid), on a chord-by-chord basis, by setting the chord `BeamMode` (via for instance the relevant palette): `AUTO` selects the glyph rendering, `beam start` the start of the grid and `beam middle` the continuation of the grid beamed to the previous element.

Typographic features of the grid (stem width, stem height and beam thickness) depend on the glyph style and are hard-coded in the style definition. As well as the number of beams for note value, which also depends on the note value style.

Also:
- Implements to possibility to force the display of a note value which would not be rendered by the current note value repetition setting, by setting the chord beam mode to any other value.
- Implements the 'no stem' chord setting for this TAB style, allowing to remove a note value symbol otherwise generated by the current repetition setting.
- Improves the detection of note value font metrics (still not perfect, though, as Qt `QFontMetricsF::tightboundingRect()` returns very approximated results)
- Fixes note value glyph scaling, when the staff scale is modified.
  • Loading branch information
mgavioli committed Sep 27, 2015
1 parent 5fc6556 commit 6cab2d0
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 37 deletions.
12 changes: 12 additions & 0 deletions fonts/fonts_tablature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@
<displayName>MuseScore Tab Modern</displayName>
<defaultPitch>15</defaultPitch>
<defaultYOffset>0</defaultYOffset>
<beamWidth>0.5</beamWidth>
<stemHeight>3</stemHeight>
<stemWidth>0.1</stemWidth>
<zeroBeamValue>semiminima</zeroBeamValue>
<duration value="longa">Ƞ</duration>
<duration value="brevis">ȡ</duration>
<duration value="semibrevis">Ȣ</duration>
Expand All @@ -245,6 +249,10 @@
<displayName>MuseScore Tab Italian</displayName>
<defaultPitch>15</defaultPitch>
<defaultYOffset>0</defaultYOffset>
<beamWidth>0.15</beamWidth>
<stemHeight>1.75</stemHeight>
<stemWidth>0.15</stemWidth>
<zeroBeamValue>semibrevis</zeroBeamValue>
<duration value="longa">Ȁ</duration>
<duration value="brevis">ȁ</duration>
<duration value="semibrevis">Ȃ</duration>
Expand All @@ -263,6 +271,10 @@
<displayName>MuseScore Tab French</displayName>
<defaultPitch>15</defaultPitch>
<defaultYOffset>0</defaultYOffset>
<beamWidth>0.30</beamWidth>
<stemHeight>3.125</stemHeight>
<stemWidth>0.21</stemWidth>
<zeroBeamValue>semiminima</zeroBeamValue>
<duration value="longa">Ȑ</duration>
<duration value="brevis">ȑ</duration>
<duration value="semibrevis">Ȓ</duration>
Expand Down
29 changes: 21 additions & 8 deletions libmscore/chord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1432,10 +1432,17 @@ void Chord::layoutStem()
StaffType* tab = 0;
if (staff() && staff()->isTabStaff()) {
tab = staff()->staffType();
// require stems only if TAB is not stemless and this chord has a stem
if (!tab->slashStyle() && _stem) { // (duplicate code with defaultStemLength())
// if stems are beside staff, apply special formatting
if (!tab->stemThrough()) {
// if stemless TAB
if (tab->slashStyle()) {
// if 'grid' duration symbol of MEDIALFINAL type, it is time to compute its width
if (_tabDur != nullptr && _tabDur->beamGrid() == TabBeamGrid::MEDIALFINAL)
_tabDur->layout2();
// in all other stemless cases, do nothing
return;
}
// not a stemless TAB; if stems are beside staff, apply special formatting
if (!tab->stemThrough()) {
if (_stem) { // (duplicate code with defaultStemLength())
// process stem:
_stem->setLen(tab->chordStemLength(this) * _spatium);
// process hook
Expand All @@ -1457,10 +1464,10 @@ void Chord::layoutStem()
_hook->setPos(x, y);
_hook->adjustReadPos();
}
return;
}
// if stems are through staff, use standard formatting
return;
}
// if stems are through staff, use standard formatting
}

//
Expand Down Expand Up @@ -2269,18 +2276,24 @@ void Chord::layoutTablature()
// check duration of prev. CR segm
ChordRest * prevCR = prevChordRest(this);
// if no previous CR
// OR symbol repeat set to ALWAYS
// OR symbol repeat condition is triggered
// OR duration type and/or number of dots is different from current CR
// OR chord beam mode not AUTO
// OR previous CR is a rest
// AND no not-stem
// set a duration symbol (trying to re-use existing symbols where existing to minimize
// symbol creation and deletion)
TablatureSymbolRepeat symRepeat = tab->symRepeat();
if (prevCR == 0
if ( (prevCR == 0
|| symRepeat == TablatureSymbolRepeat::ALWAYS
|| (symRepeat == TablatureSymbolRepeat::MEASURE && measure() != prevCR->measure())
|| (symRepeat == TablatureSymbolRepeat::SYSTEM && measure()->system() != prevCR->measure()->system())
|| beamMode() != Beam::Mode::AUTO
|| prevCR->durationType().type() != durationType().type()
|| prevCR->dots() != dots()
|| prevCR->type() == Element::Type::REST) {
|| prevCR->type() == Element::Type::REST)
&& !noStem() ) {
// symbol needed; if not exist, create; if exists, update duration
if (!_tabDur)
_tabDur = new TabDurationSymbol(score(), tab, durationType().type(), dots());
Expand Down
197 changes: 171 additions & 26 deletions libmscore/stafftype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
//=============================================================================

#include "stafftype.h"

#include "chord.h"
#include "mscore.h"
#include "navigate.h"
#include "staff.h"
#include "xml.h"
#include "mscore.h"
#include "chord.h"

#define TAB_DEFAULT_LINE_SP (1.5)
#define TAB_RESTSYMBDISPL 2.0
Expand Down Expand Up @@ -404,18 +406,27 @@ void StaffType::setDurationMetrics()
if (_durationMetricsValid && _refDPI == MScore::DPI) // metrics are still valid
return;

QFontMetricsF fm(durationFont());
// QFontMetrics[F]() returns results unreliably rounded to integral pixels;
// use a scaled up font and then scale computed values down
// QFontMetricsF fm(durationFont());
QFont font(durationFont());
qreal pixelSize = _durationFontSize * MScore::DPI / PPI;
font.setPixelSize(lrint(pixelSize) * 100.0);
QFontMetricsF fm(font);
QString txt(_durationFonts[_durationFontIdx].displayValue, int(TabVal::NUM_OF));
QRectF bb( fm.tightBoundingRect(txt) );
// raise symbols by a default margin and, if marks are above lines, by half the line distance
// (converted from spatium units to raster units)
_durationGridYOffset = ( TAB_DEFAULT_DUR_YOFFS - (_onLines ? 0.0 : lineDistance().val()/2.0) )
* MScore::DPI*SPATIUM20;
// this is the bottomest point of any duration sign
_durationYOffset = _durationGridYOffset;
// move symbols so that the lowest margin 'sits' on the base line:
// move down by the whole part above (negative) the base line
// ( -bb.y() ) then up by the whole height ( -bb.height()/2 )
_durationYOffset = -bb.y() - bb.height()
// then move up by a default margin and, if marks are above lines, by half the line distance
// (converted from spatium units to raster units)
+ ( TAB_DEFAULT_DUR_YOFFS - (_onLines ? 0.0 : lineDistance().val()/2.0) ) * MScore::DPI*SPATIUM20;
_durationBoxH = bb.height();
_durationBoxY = bb.y() + _durationYOffset;
// ( -bb.y() ) then up by the whole height ( -bb.height() )
_durationYOffset -= (bb.height() + bb.y()) / 100.0;
_durationBoxH = bb.height() / 100.0;
_durationBoxY = _durationGridYOffset - bb.height() / 100.0;
// keep track of the conditions under which metrics have been computed
_refDPI = MScore::DPI;
_durationMetricsValid = true;
Expand Down Expand Up @@ -834,15 +845,19 @@ TabDurationSymbol::TabDurationSymbol(Score* s)
{
setFlags(flags() & ~(ElementFlag::MOVABLE | ElementFlag::SELECTABLE) );
setGenerated(true);
_tab = 0;
_text = QString();
_beamGrid = TabBeamGrid::NONE;
_beamLength = 0.0;
_tab = 0;
_text = QString();
}

TabDurationSymbol::TabDurationSymbol(Score* s, StaffType* tab, TDuration::DurationType type, int dots)
: Element(s)
{
setFlags(flags() & ~(ElementFlag::MOVABLE | ElementFlag::SELECTABLE) );
setGenerated(true);
_beamGrid = TabBeamGrid::NONE;
_beamLength = 0.0;
setDuration(type, dots, tab);
}

Expand All @@ -863,18 +878,85 @@ void TabDurationSymbol::layout()
setbbox(QRectF());
return;
}
QFontMetricsF fm(_tab->durationFont());
qreal mags = magS();
qreal wbb = fm.width(_text);
qreal ybb = _tab->durationBoxY();
qreal ypos = _tab->durationFontYOffset();
// with rests, move symbol down by half its displacement from staff
if(parent() && parent()->type() == Element::Type::REST) {
ybb += TAB_RESTSYMBDISPL * spatium();
ypos += TAB_RESTSYMBDISPL * spatium();
qreal _spatium = spatium();
qreal hbb, wbb, xbb, ybb; // bbox sizes
qreal xpos, ypos; // position coords

_beamGrid = TabBeamGrid::NONE;
Chord* chord = static_cast<Chord*>(parent());
// if no chord (shouldn't happens...) or not a special beam mode, layout regular symbol
if (!chord || chord->type() != Element::Type::CHORD ||
(chord->beamMode() != Beam::Mode::BEGIN && chord->beamMode() != Beam::Mode::MID &&
chord->beamMode() != Beam::Mode::END) ) {
QFontMetricsF fm(_tab->durationFont());
hbb = _tab->durationBoxH();
wbb = fm.width(_text);
xbb = 0.0;
xpos = 0.0;
ypos = _tab->durationFontYOffset();
ybb = _tab->durationBoxY() - ypos;
// with rests, move symbol down by half its displacement from staff
if(parent() && parent()->type() == Element::Type::REST) {
ybb += TAB_RESTSYMBDISPL * _spatium;
ypos += TAB_RESTSYMBDISPL * _spatium;
}
}
// if on a chord with special beam mode, layout an 'English'-style duration grid
else {
TablatureDurationFont font = _tab->_durationFonts[_tab->_durationFontIdx];
hbb = font.gridStemHeight * _spatium; // bbox height is stem height
wbb = font.gridStemWidth * _spatium; // bbox width is stem width
xbb = -wbb * 0.5; // bbox is half at left and half at right of stem centre
ybb = -hbb; // bbox top is at top of stem height
xpos = 0.75 * _spatium; // conventional centring of stem on fret marks
ypos = _tab->durationGridYOffset(); // stem start is at bottom
if (chord->beamMode() == Beam::Mode::BEGIN) {
_beamGrid = TabBeamGrid::INITIAL;
_beamLength = 0.0;
}
else if (chord->beamMode() == Beam::Mode::MID || chord->beamMode() == Beam::Mode::END) {
_beamLevel = (int)(chord->durationType().type()) - (int)(font.zeroBeamLevel);
_beamGrid = (_beamLevel < 1 ? TabBeamGrid::INITIAL : TabBeamGrid::MEDIALFINAL);
// _beamLength and bbox x and width will be set in layout2(),
// once horiz. positions of chords are known
}
}
bbox().setRect(0.0, ybb * mags, wbb * mags, _tab->durationBoxH() * mags);
setPos(0.0, ypos*mags);
// set this' mag from parent chord mag (include staff mag)
qreal mag = chord != nullptr ? chord->mag() : 1.0;
setMag(mag);
mag = magS(); // local mag * score mag
// set magnified bbox and position
bbox().setRect(xbb * mag, ybb * mag, wbb * mag, hbb * mag);
setPos(xpos*mag, ypos*mag);
}

//---------------------------------------------------------
// layout2
//
// Second step: after horizontal positions of elements involved are defined,
// compute width of 'grid beams'
//---------------------------------------------------------

void TabDurationSymbol::layout2()
{
// if not within a TAB or not a MEDIALFINAL grid element, do nothing
if(!_tab || _beamGrid != TabBeamGrid::MEDIALFINAL)
return;

// get 'grid' beam length from page positions of this' chord and previous chord
Chord* chord = static_cast<Chord*>(parent());
ChordRest* prevChord = prevChordRest(chord, true);
if (chord == nullptr || prevChord == nullptr)
return;
qreal mags = magS();
qreal beamLen = prevChord->pagePos().x() - chord->pagePos().x(); // negative
// page pos. difference already includes any magnification in effect:
// scale it down, as it will be magnified again during drawing
_beamLength = beamLen / mags;
// update bbox x and w, but keep current y and h
bbox().setX(beamLen);
// set bbox width to half a stem width (magnified) plus beam length (already magnified)
bbox().setWidth(_tab->_durationFonts[_tab->_durationFontIdx].gridStemWidth * spatium() * 0.5 * mags - beamLen);
}

//---------------------------------------------------------
Expand All @@ -888,10 +970,40 @@ void TabDurationSymbol::draw(QPainter* painter) const
qreal mag = magS();
qreal imag = 1.0 / mag;

painter->setPen(curColor());
QPen pen(curColor());
painter->setPen(pen);
painter->scale(mag, mag);
painter->setFont(_tab->durationFont());
painter->drawText(QPointF(0.0, 0.0), _text);
if (_beamGrid == TabBeamGrid::NONE) {
// if no beam grid, draw symbol
painter->setFont(_tab->durationFont());
painter->drawText(QPointF(0.0, 0.0), _text);
}
else {
// if beam grid, draw stem line
TablatureDurationFont& font = _tab->_durationFonts[_tab->_durationFontIdx];
qreal _spatium = spatium();
pen.setCapStyle(Qt::FlatCap);
pen.setWidthF(font.gridStemWidth * _spatium);
painter->setPen(pen);
// take stem height from bbox, but de-magnify it, as drawing is already magnified
qreal h = bbox().y() / mag;
painter->drawLine(QPointF(0.0, h), QPointF(0.0, 0.0) );
// if beam grid is medial/final, draw beam lines too: lines go from mid of
// previous stem (delta x stored in _beamLength) to mid of this' stem (0.0)
if (_beamGrid == TabBeamGrid::MEDIALFINAL) {
pen.setWidthF(font.gridBeamWidth * _spatium);
painter->setPen(pen);
// lower heigth available to beams by half a beam width,
// so that top beam upper border aligns with stem top
h += (font.gridBeamWidth * _spatium) * 0.5;
// draw beams equally spaced within the stem height (this is
// different from modern engraving, but common in historic prints)
qreal step = -h / _beamLevel;
qreal y = h;
for (int i = 0; i < _beamLevel; i++, y += step)
painter->drawLine(QPointF(_beamLength, y), QPointF(0.0, y) );
}
}
painter->scale(imag, imag);
}

Expand Down Expand Up @@ -968,6 +1080,39 @@ bool TablatureDurationFont::read(XmlReader& e)
defPitch = e.readDouble();
else if (tag == "defaultYOffset")
defYOffset = e.readDouble();
else if (tag == "beamWidth")
gridBeamWidth = e.readDouble();
else if (tag == "stemHeight")
gridStemHeight = e.readDouble();
else if (tag == "stemWidth")
gridStemWidth = e.readDouble();
else if (tag == "zeroBeamValue") {
QString val(e.readElementText());
if (val == "longa")
zeroBeamLevel = TDuration::DurationType::V_LONG;
else if (val == "brevis")
zeroBeamLevel = TDuration::DurationType::V_BREVE;
else if (val == "semibrevis")
zeroBeamLevel = TDuration::DurationType::V_WHOLE;
else if (val == "minima")
zeroBeamLevel = TDuration::DurationType::V_HALF;
else if (val == "semiminima")
zeroBeamLevel = TDuration::DurationType::V_QUARTER;
else if (val == "fusa")
zeroBeamLevel = TDuration::DurationType::V_EIGHTH;
else if (val == "semifusa")
zeroBeamLevel = TDuration::DurationType::V_16TH;
else if (val == "32")
zeroBeamLevel = TDuration::DurationType::V_32ND;
else if (val == "64")
zeroBeamLevel = TDuration::DurationType::V_64TH;
else if (val == "128")
zeroBeamLevel = TDuration::DurationType::V_128TH;
else if (val == "256")
zeroBeamLevel = TDuration::DurationType::V_256TH;
else
e.unknown();
}
else if (tag == "duration") {
QString val = e.attribute("value");
QString txt(e.readElementText());
Expand Down
Loading

0 comments on commit 6cab2d0

Please sign in to comment.