Merge pull request #17824 from miiizen/harp-pedal-gliss

Added option for harp pedal diagram glissando
This commit is contained in:
RomanPudashkin 2023-11-14 18:38:09 +02:00 committed by GitHub
commit 9db8ed18f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 2155 additions and 16 deletions

View File

@ -37,8 +37,10 @@ NICE-TO-HAVE TODO:
#include "types/typesconv.h"
#include "chord.h"
#include "harppedaldiagram.h"
#include "measure.h"
#include "note.h"
#include "part.h"
#include "score.h"
#include "segment.h"
#include "staff.h"
@ -57,6 +59,8 @@ static const ElementStyle glissandoElementStyle {
{ Sid::glissandoFontStyle, Pid::FONT_STYLE },
{ Sid::glissandoLineWidth, Pid::LINE_WIDTH },
{ Sid::glissandoText, Pid::GLISS_TEXT },
{ Sid::glissandoStyle, Pid::GLISS_STYLE },
{ Sid::glissandoStyleHarp, Pid::GLISS_STYLE }
};
//=========================================================
@ -129,6 +133,7 @@ Glissando::Glissando(const Glissando& g)
_showText = g._showText;
_playGlissando = g._playGlissando;
_fontStyle = g._fontStyle;
m_isHarpGliss = g.m_isHarpGliss;
}
const TranslatableString& Glissando::glissandoTypeName() const
@ -148,6 +153,15 @@ LineSegment* Glissando::createLineSegment(System* parent)
return seg;
}
Sid Glissando::getPropertyStyle(Pid id) const
{
if (id == Pid::GLISS_STYLE) {
return isHarpGliss().value_or(false) ? Sid::glissandoStyleHarp : Sid::glissandoStyle;
}
return SLine::getPropertyStyle(id);
}
void Glissando::addLineAttachPoints()
{
GlissandoSegment* frontSeg = toGlissandoSegment(frontSegment());
@ -195,6 +209,41 @@ bool Glissando::pitchSteps(const Spanner* spanner, std::vector<int>& pitchOffset
int direction = pitchEnd > pitchStart ? 1 : -1;
pitchOffsets.clear();
if (glissandoStyle == GlissandoStyle::DIATONIC) {
// Obey harp pedal diagrams if on a harp staff
if (glissando->isHarpGliss().value_or(false)) {
HarpPedalDiagram* hd = spanner->part()->currentHarpDiagram(spanner->tick());
std::set<int> playableTpcs = hd ? hd->playableTpcs() : std::set<int>({ 14, 16, 18, 13, 15, 17, 19 });
std::vector<int> playablePitches;
for (int t : playableTpcs) {
playablePitches.push_back(tpc2pitch(t) % PITCH_DELTA_OCTAVE);
}
// Push starting note, then check for enharmonic on the next string. If there is an enharmonic, 0 will be pushed back twice
pitchOffsets.push_back(0);
int en = noteStart->tpc() + TPC_DELTA_ENHARMONIC * -direction;
// Harp pedalling will only have 1 flat or sharp
if (en >= TPC_F_B && en <= TPC_B_S && playableTpcs.find(en) != playableTpcs.end()) {
pitchOffsets.push_back(0);
}
for (int p = pitchStart + direction; p != pitchEnd; p += direction) {
// Count times pitch occurs in harp pedalling - this accounts for enharmonics
int pitchOccurrences = std::count(playablePitches.begin(), playablePitches.end(), p % PITCH_DELTA_OCTAVE);
if (pitchOccurrences > 0) {
pitchOffsets.insert(pitchOffsets.end(), pitchOccurrences, p - pitchStart);
}
}
// Check for enharmonic at end, in correct direction
en = noteEnd->tpc() + TPC_DELTA_ENHARMONIC * direction;
if (en >= TPC_F_B && en <= TPC_B_S && playableTpcs.find(en) != playableTpcs.end()) {
pitchOffsets.push_back(pitchEnd - pitchStart);
}
return pitchOffsets.size() > 0;
}
// Regular diatonic mode
int lineStart = noteStart->line();
// scale obeying accidentals
for (int line = lineStart, pitch = pitchStart; (direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd); line -= direction) {
@ -448,8 +497,15 @@ bool Glissando::setProperty(Pid propertyId, const PropertyValue& v)
setShowText(v.toBool());
break;
case Pid::GLISS_STYLE:
setGlissandoStyle(v.value<GlissandoStyle>());
{
// Make sure harp glisses can only be diatonic and chromatic
GlissandoStyle glissStyle = v.value<GlissandoStyle>();
if (isHarpGliss().value_or(false) && (glissStyle != GlissandoStyle::DIATONIC && glissStyle != GlissandoStyle::CHROMATIC)) {
glissStyle = GlissandoStyle::DIATONIC;
}
setGlissandoStyle(glissStyle);
break;
}
case Pid::GLISS_SHIFT:
setGlissandoShift(v.toBool());
break;
@ -493,7 +549,7 @@ PropertyValue Glissando::propertyDefault(Pid propertyId) const
case Pid::GLISS_SHOW_TEXT:
return true;
case Pid::GLISS_STYLE:
return GlissandoStyle::CHROMATIC;
return style().styleV(getPropertyStyle(propertyId));
case Pid::GLISS_SHIFT:
return false;
case Pid::GLISS_EASEIN:

View File

@ -76,6 +76,8 @@ class Glissando final : public SLine
M_PROPERTY(int, easeIn, setEaseIn)
M_PROPERTY(int, easeOut, setEaseOut)
std::optional<bool> m_isHarpGliss = std::nullopt;
public:
static constexpr double GLISS_PALETTE_WIDTH = 4.0;
static constexpr double GLISS_PALETTE_HEIGHT = 4.0;
@ -88,12 +90,16 @@ public:
const TranslatableString& glissandoTypeName() const;
std::optional<bool> isHarpGliss() const { return m_isHarpGliss; }
void setIsHarpGliss(std::optional<bool> v) { m_isHarpGliss = v; }
// overridden inherited methods
Glissando* clone() const override { return new Glissando(*this); }
LineSegment* createLineSegment(System* parent) override;
// property/style methods
Sid getPropertyStyle(Pid id) const override;
PropertyValue getProperty(Pid propertyId) const override;
bool setProperty(Pid propertyId, const PropertyValue&) override;
PropertyValue propertyDefault(Pid) const override;

View File

@ -82,6 +82,7 @@ public:
void updateDiagramText();
bool isTpcPlayable(int tpc);
const std::set<int>& playableTpcs() const { return m_playableTpcs; }
private:

View File

@ -724,7 +724,7 @@ HarpPedalDiagram* Part::currentHarpDiagram(const Fraction& tick) const
HarpPedalDiagram* Part::nextHarpDiagram(const Fraction& tick) const
{
auto i = harpDiagrams.lower_bound(tick.ticks());
auto i = harpDiagrams.upper_bound(tick.ticks());
return (i == harpDiagrams.end()) ? nullptr : i->second;
}

View File

@ -3057,6 +3057,23 @@ void ChangeHarpPedalState::flip(EditData*)
diagram->triggerLayout();
}
std::vector<const EngravingObject*> ChangeHarpPedalState::objectItems() const
{
Part* part = diagram->part();
std::vector<const EngravingObject*> objs{ diagram };
if (!part) {
return objs;
}
HarpPedalDiagram* nextDiagram = part->nextHarpDiagram(diagram->tick());
if (nextDiagram) {
objs.push_back(nextDiagram);
} else {
objs.push_back(diagram->score()->lastElement());
}
return objs;
}
void ChangeSingleHarpPedal::flip(EditData*)
{
HarpStringType f_type = type;
@ -3068,8 +3085,26 @@ void ChangeSingleHarpPedal::flip(EditData*)
diagram->setPedal(type, pos);
type = f_type;
pos = f_pos;
diagram->triggerLayout();
}
std::vector<const EngravingObject*> ChangeSingleHarpPedal::objectItems() const
{
Part* part = diagram->part();
std::vector<const EngravingObject*> objs{ diagram };
if (!part) {
return objs;
}
HarpPedalDiagram* nextDiagram = part->nextHarpDiagram(diagram->tick());
if (nextDiagram) {
objs.push_back(nextDiagram);
} else {
objs.push_back(diagram->score()->lastElement());
}
return objs;
}
}
void ChangeStringData::flip(EditData*)

View File

@ -1592,15 +1592,19 @@ public:
class ChangeHarpPedalState : public UndoCommand
{
OBJECT_ALLOCATOR(engraving, ChangeHarpPedalState)
HarpPedalDiagram* diagram;
std::array<PedalPosition, HARP_STRING_NO> pedalState;
void flip(EditData*) override;
public:
ChangeHarpPedalState(HarpPedalDiagram* _diagram, std::array<PedalPosition, HARP_STRING_NO> _pedalState)
: diagram(_diagram), pedalState(_pedalState) {}
void flip(EditData*) override;
UNDO_NAME("ChangeHarpPedalState")
UNDO_CHANGED_OBJECTS({ diagram });
// UNDO_CHANGED_OBJECTS({ diagram })
std::vector<const EngravingObject*> objectItems() const override;
};
//---------------------------------------------------------
@ -1609,10 +1613,14 @@ public:
class ChangeSingleHarpPedal : public UndoCommand
{
OBJECT_ALLOCATOR(engraving, ChangeSingleHarpPedal)
HarpPedalDiagram* diagram;
HarpStringType type;
PedalPosition pos;
void flip(EditData*) override;
public:
ChangeSingleHarpPedal(HarpPedalDiagram* _diagram, HarpStringType _type, PedalPosition _pos)
: diagram(_diagram),
@ -1621,9 +1629,9 @@ public:
{
}
void flip(EditData*) override;
UNDO_NAME("ChangeSingleHarpPedal")
UNDO_CHANGED_OBJECTS({ diagram });
// UNDO_CHANGED_OBJECTS({ diagram });
std::vector<const EngravingObject*> objectItems() const override;
};
class ChangeStringData : public UndoCommand

View File

@ -2530,7 +2530,7 @@ void TLayout::layoutFretDiagram(const FretDiagram* item, FretDiagram::LayoutData
}
}
static void _layoutGlissando(const Glissando* item, LayoutContext& ctx, Glissando::LayoutData* ldata)
static void _layoutGlissando(Glissando* item, LayoutContext& ctx, Glissando::LayoutData* ldata)
{
double _spatium = item->spatium();
@ -2542,6 +2542,32 @@ static void _layoutGlissando(const Glissando* item, LayoutContext& ctx, Glissand
}
ldata->setPos(0.0, 0.0);
String instrId = item->staff()->part()->instrumentId(item->tick());
bool harpStaff = instrId == "harp";
if (!item->isHarpGliss().has_value()) {
item->setIsHarpGliss(harpStaff);
} else {
if (harpStaff != item->isHarpGliss().value()) {
// Preserve whether this gliss has its default playback style
bool defaultStyle = false;
if (item->isStyled(Pid::GLISS_STYLE)) {
defaultStyle = true;
}
item->setIsHarpGliss(harpStaff);
if (defaultStyle) {
item->resetProperty(Pid::GLISS_STYLE);
}
// Make sure harp glisses can only be diatonic and chromatic
GlissandoStyle glissStyle = item->glissandoStyle();
if (item->isHarpGliss().value()
&& (glissStyle != GlissandoStyle::DIATONIC
&& glissStyle != GlissandoStyle::CHROMATIC)) {
item->setGlissandoStyle(GlissandoStyle::DIATONIC);
}
}
}
Note* anchor1 = toNote(item->startElement());
Note* anchor2 = toNote(item->endElement());
Chord* cr1 = anchor1->chord();

View File

@ -2648,6 +2648,15 @@ void TRead::read(Glissando* g, XmlReader& e, ReadContext& ctx)
ctx.addSpanner(e.intAttribute("id", -1), g);
}
g->setShowText(false);
staff_idx_t staffIdx = track2staff(ctx.track());
Staff* staff = ctx.score()->staff(staffIdx);
if (staff) {
String instrId = staff->part()->instrumentId(g->tick());
g->setIsHarpGliss(instrId == "harp");
}
g->resetProperty(Pid::GLISS_STYLE);
g->setShowText(false);
while (e.readNextStartElement()) {
const AsciiStringView tag = e.name();

View File

@ -2692,11 +2692,22 @@ void TRead::read(Glissando* g, XmlReader& e, ReadContext& ctx)
}
g->setShowText(false);
staff_idx_t staffIdx = track2staff(ctx.track());
Staff* staff = ctx.score()->staff(staffIdx);
if (staff) {
String instrId = staff->part()->instrumentId(g->tick());
g->setIsHarpGliss(instrId == "harp");
}
g->resetProperty(Pid::GLISS_STYLE);
while (e.readNextStartElement()) {
const AsciiStringView tag = e.name();
if (tag == "text") {
g->setShowText(true);
TRead::readProperty(g, e, ctx, Pid::GLISS_TEXT);
} else if (tag == "isHarpGliss" && ctx.pasteMode()) {
g->setIsHarpGliss(e.readBool());
g->resetProperty(Pid::GLISS_STYLE);
} else if (tag == "subtype") {
g->setGlissandoType(TConv::fromXml(e.readAsciiText(), GlissandoType::STRAIGHT));
} else if (tag == "glissandoStyle") {

View File

@ -1250,6 +1250,10 @@ void TWrite::write(const Glissando* item, XmlWriter& xml, WriteContext& ctx)
xml.tag("text", item->text());
}
if (ctx.clipboardmode() && item->isHarpGliss().has_value()) {
xml.tagProperty("isHarpGliss", PropertyValue(item->isHarpGliss().value()));
}
for (auto id : { Pid::GLISS_TYPE, Pid::PLAY, Pid::GLISS_STYLE, Pid::GLISS_SHIFT, Pid::GLISS_EASEIN, Pid::GLISS_EASEOUT }) {
writeProperty(item, xml, id);
}

View File

@ -183,6 +183,9 @@ bool MStyle::readProperties(XmlReader& e)
case P_TYPE::TIE_PLACEMENT:
set(idx, TConv::fromXml(e.readAsciiText(), TiePlacement::AUTO));
break;
case P_TYPE::GLISS_STYLE:
set(idx, GlissandoStyle(e.readText().toInt()));
break;
default:
ASSERT_X(u"unhandled type " + String::number(int(type)));
}

View File

@ -1187,6 +1187,8 @@ const std::array<StyleDef::StyleValue, size_t(Sid::STYLES)> StyleDef::styleValue
{ Sid::glissandoFrameBgColor, "glissandoFrameBgColor", PropertyValue::fromValue(Color::transparent) },
{ Sid::glissandoLineWidth, "glissandoLineWidth", Spatium(0.15) },
{ Sid::glissandoText, "glissandoText", String(u"gliss.") },
{ Sid::glissandoStyle, "glissandoStyle", GlissandoStyle::CHROMATIC },
{ Sid::glissandoStyleHarp, "glissandoStyleHarp", GlissandoStyle::DIATONIC },
{ Sid::bendFontFace, "bendFontFace", "Edwin" },
{ Sid::bendFontSize, "bendFontSize", 8.0 },

View File

@ -1195,6 +1195,8 @@ enum class Sid {
glissandoFrameBgColor,
glissandoLineWidth,
glissandoText,
glissandoStyle,
glissandoStyleHarp,
bendFontFace,
bendFontSize,

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<container>
<rootfiles>
<rootfile full-path="score_style.mss"/>
<rootfile full-path="TwoNotes_Discrete_Harp_Glissando.mscx"/>
<rootfile full-path="Thumbnails/thumbnail.png"/>
<rootfile full-path="audiosettings.json"/>
<rootfile full-path="viewsettings.json"/>
</rootfiles>
</container>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?>
<museScore version="4.20">
<programVersion>4.2.0</programVersion>
<programRevision></programRevision>
<Score>
<Division>480</Division>
<showInvisible>1</showInvisible>
<showUnprintable>1</showUnprintable>
<showFrames>1</showFrames>
<showMargins>0</showMargins>
<open>1</open>
<metaTag name="arranger"></metaTag>
<metaTag name="composer">Composer / arranger</metaTag>
<metaTag name="copyright"></metaTag>
<metaTag name="creationDate">2023-09-11</metaTag>
<metaTag name="lyricist"></metaTag>
<metaTag name="movementNumber"></metaTag>
<metaTag name="movementTitle"></metaTag>
<metaTag name="platform">Apple Macintosh</metaTag>
<metaTag name="poet"></metaTag>
<metaTag name="source"></metaTag>
<metaTag name="sourceRevisionId"></metaTag>
<metaTag name="subtitle">Subtitle</metaTag>
<metaTag name="translator"></metaTag>
<metaTag name="workNumber"></metaTag>
<metaTag name="workTitle">Untitled score</metaTag>
<Order id="orchestral">
<name>Orchestral</name>
<instrument id="harp">
<family id="harps">Harps</family>
</instrument>
<section id="woodwind" brackets="true" barLineSpan="true" thinBrackets="true">
<family>flutes</family>
<family>oboes</family>
<family>clarinets</family>
<family>saxophones</family>
<family>bassoons</family>
<unsorted group="woodwinds"/>
</section>
<section id="brass" brackets="true" barLineSpan="true" thinBrackets="true">
<family>horns</family>
<family>trumpets</family>
<family>cornets</family>
<family>flugelhorns</family>
<family>trombones</family>
<family>tubas</family>
</section>
<section id="timpani" brackets="true" barLineSpan="true" thinBrackets="true">
<family>timpani</family>
</section>
<section id="percussion" brackets="true" barLineSpan="true" thinBrackets="true">
<family>keyboard-percussion</family>
<family>drums</family>
<family>unpitched-metal-percussion</family>
<family>unpitched-wooden-percussion</family>
<family>other-percussion</family>
</section>
<family>keyboards</family>
<family>harps</family>
<family>organs</family>
<family>synths</family>
<soloists/>
<section id="voices" brackets="true" barLineSpan="false" thinBrackets="true">
<family>voices</family>
<family>voice-groups</family>
</section>
<section id="strings" brackets="true" barLineSpan="true" thinBrackets="true">
<family>orchestral-strings</family>
</section>
<unsorted/>
</Order>
<Part id="1">
<Staff id="1">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
<bracket type="1" span="2" col="0" visible="1"/>
<barLineSpan>1</barLineSpan>
</Staff>
<Staff id="2">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
<defaultClef>F</defaultClef>
</Staff>
<trackName>Harp</trackName>
<Instrument id="harp">
<longName>Harp</longName>
<shortName>Hrp.</shortName>
<trackName>Harp</trackName>
<minPitchP>23</minPitchP>
<maxPitchP>104</maxPitchP>
<minPitchA>23</minPitchA>
<maxPitchA>104</maxPitchA>
<instrumentId>pluck.harp</instrumentId>
<clef staff="2">F</clef>
<Channel>
<program value="46"/>
<synti>Fluid</synti>
</Channel>
<Channel name="staccato">
<program value="45"/>
<synti>Fluid</synti>
</Channel>
<Channel name="flageoletti">
<program value="11"/>
<synti>Fluid</synti>
</Channel>
</Instrument>
</Part>
<Staff id="1">
<Measure>
<voice>
<KeySig>
<concertKey>0</concertKey>
</KeySig>
<TimeSig>
<sigN>4</sigN>
<sigD>4</sigD>
</TimeSig>
<HarpPedalDiagram>
<isDiagram>0</isDiagram>
<pedalState>
<string name="0">1</string>
<string name="1">1</string>
<string name="2">2</string>
<string name="3">1</string>
<string name="4">2</string>
<string name="5">2</string>
<string name="6">2</string>
</pedalState>
<style>harp_pedal_text_diagram</style>
<text>E<sym>csymAccidentalNatural</sym> F<sym>csymAccidentalSharp</sym> G<sym>csymAccidentalSharp</sym> A<sym>csymAccidentalSharp</sym>
D<sym>csymAccidentalNatural</sym> C<sym>csymAccidentalNatural</sym> B<sym>csymAccidentalSharp</sym> </text>
</HarpPedalDiagram>
<Chord>
<durationType>whole</durationType>
<Note>
<Accidental>
<subtype>accidentalSharp</subtype>
</Accidental>
<pitch>60</pitch>
<tpc>26</tpc>
<Spanner type="Glissando">
<Glissando>
<text>gliss.</text>
<diagonal>1</diagonal>
<anchor>3</anchor>
</Glissando>
<next>
<location>
<measures>1</measures>
</location>
</next>
</Spanner>
</Note>
</Chord>
</voice>
</Measure>
<Measure>
<voice>
<HarpPedalDiagram>
<isDiagram>0</isDiagram>
<pedalState>
<string name="0">1</string>
<string name="1">1</string>
<string name="2">1</string>
<string name="3">1</string>
<string name="4">2</string>
<string name="5">0</string>
<string name="6">1</string>
</pedalState>
<style>harp_pedal_text_diagram</style>
<text>G<sym>csymAccidentalFlat</sym> A<sym>csymAccidentalNatural</sym>
B<sym>csymAccidentalNatural</sym> </text>
</HarpPedalDiagram>
<Chord>
<durationType>whole</durationType>
<Note>
<pitch>72</pitch>
<tpc>14</tpc>
<Spanner type="Glissando">
<Glissando>
<text>gliss.</text>
<diagonal>1</diagonal>
<anchor>3</anchor>
</Glissando>
<next>
<location>
<measures>1</measures>
</location>
</next>
</Spanner>
<Spanner type="Glissando">
<prev>
<location>
<measures>-1</measures>
</location>
</prev>
</Spanner>
</Note>
</Chord>
</voice>
</Measure>
<Measure>
<voice>
<Chord>
<durationType>whole</durationType>
<Note>
<Accidental>
<subtype>accidentalSharp</subtype>
</Accidental>
<pitch>66</pitch>
<tpc>20</tpc>
<Spanner type="Glissando">
<prev>
<location>
<measures>-1</measures>
</location>
</prev>
</Spanner>
</Note>
</Chord>
</voice>
</Measure>
</Staff>
<Staff id="2">
<Measure>
<voice>
<KeySig>
<concertKey>0</concertKey>
</KeySig>
<TimeSig>
<sigN>4</sigN>
<sigD>4</sigD>
</TimeSig>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
<Measure>
<voice>
<Rest>
<durationType>measure</durationType>
<duration>4/4</duration>
</Rest>
</voice>
</Measure>
</Staff>
</Score>
</museScore>

View File

@ -0,0 +1,113 @@
{
"activeSoundProfile": "MuseScore Basic",
"aux": [
{
"out": {
"balance": 0,
"fxChain": {
"0": {
"active": true,
"chainOrder": 0,
"resourceMeta": {
"attributes": {
},
"hasNativeEditorSupport": true,
"id": "Muse Reverb",
"type": "muse_plugin",
"vendor": "Muse"
},
"unitConfiguration": {
}
}
},
"volumeDb": 0
},
"soloMuteState": {
"mute": false,
"solo": false
}
},
{
"out": {
"balance": 0,
"fxChain": {
},
"volumeDb": 0
},
"soloMuteState": {
"mute": false,
"solo": false
}
}
],
"master": {
"balance": 0,
"fxChain": {
},
"volumeDb": 0
},
"tracks": [
{
"in": {
"resourceMeta": {
"attributes": {
},
"hasNativeEditorSupport": false,
"id": "MS Basic",
"type": "fluid_soundfont",
"vendor": "Fluid"
},
"unitConfiguration": {
}
},
"instrumentId": "harp",
"out": {
"auxSends": [
{
"active": true,
"signalAmount": 0.30000001192092896
},
{
"active": true,
"signalAmount": 0.30000001192092896
}
],
"balance": 0,
"fxChain": {
},
"volumeDb": 0
},
"partId": "1",
"soloMuteState": {
"mute": false,
"solo": false
}
},
{
"in": {
"resourceMeta": {
"attributes": {
},
"hasNativeEditorSupport": false,
"id": "MS Basic",
"type": "fluid_soundfont",
"vendor": "Fluid"
},
"unitConfiguration": {
}
},
"instrumentId": "metronome",
"out": {
"balance": 0,
"fxChain": {
},
"volumeDb": 0
},
"partId": "999",
"soloMuteState": {
"mute": false,
"solo": false
}
}
]
}

View File

@ -0,0 +1,5 @@
{
"notation": {
"viewMode": "page"
}
}

View File

@ -25,8 +25,11 @@
#include "mpe/tests/utils/articulationutils.h"
#include "dom/factory.h"
#include "dom/measure.h"
#include "dom/part.h"
#include "dom/segment.h"
#include "dom/harppedaldiagram.h"
#include "utils/scorerw.h"
#include "playback/playbackeventsrenderer.h"
@ -40,6 +43,7 @@ static constexpr duration_t QUARTER_NOTE_DURATION = 500000; // duration in micro
static constexpr duration_t QUAVER_NOTE_DURATION = QUARTER_NOTE_DURATION / 2; // duration in microseconds for 4/4 120BPM
static constexpr duration_t SEMI_QUAVER_NOTE_DURATION = QUAVER_NOTE_DURATION / 2; // duration in microseconds for 4/4 120BPM
static constexpr duration_t DEMI_SEMI_QUAVER_NOTE_DURATION = QUARTER_NOTE_DURATION / 8; // duration in microseconds for 4/4 120BPM
static constexpr duration_t WHOLE_NOTE_DURATION = QUARTER_NOTE_DURATION * 4; // duration in microseconds for 4/4 120BPM
class Engraving_PlaybackEventsRendererTests : public ::testing::Test
{
@ -897,6 +901,118 @@ TEST_F(Engraving_PlaybackEventsRendererTests, TwoNotes_Glissando_NoPlay)
}
}
/**
* @brief PlaybackEventsRendererTests_TwoNotes_Discrete_Harp_Glissando
* @details Render a score with 3 measures containing whole notes (C4, C5, C4) and glissandos
* between them. Add harp pedal diagrams to the first and second measures set to a
* whole tone scale, then back to Cmaj. Check the glisses render as expected
*/
TEST_F(Engraving_PlaybackEventsRendererTests, TwoNotes_Discrete_Harp_Glissando) {
// [GIVEN]
Score* score = ScoreRW::readScore(
PLAYBACK_EVENTS_RENDERING_DIR + "TwoNotes_Discrete_Harp_Glissando/TwoNotes_Discrete_Harp_Glissando.mscx");
Measure* firstMeasure = score->firstMeasure();
ASSERT_TRUE(firstMeasure);
Segment* firstSegment = firstMeasure->segments().firstCRSegment();
ASSERT_TRUE(firstSegment);
ChordRest* chord = firstSegment->nextChordRest(0);
ASSERT_TRUE(chord);
Measure* secondMeasure = firstMeasure->nextMeasure();
ASSERT_TRUE(secondMeasure);
Segment* secondSegment = secondMeasure->segments().firstCRSegment();
ASSERT_TRUE(secondSegment);
ChordRest* secondChord = secondSegment->nextChordRest(0);
ASSERT_TRUE(secondChord);
// TEST FIRST GLISS
// [GIVEN] Expected glissando disclosure
size_t expectedSubNotesCount = 8;
float expectedDuration = static_cast<float>(WHOLE_NOTE_DURATION) / expectedSubNotesCount;
pitch_level_t nominalPitchLevel = pitchLevel(PitchClass::C, 4);
std::vector<int> pitchesWt = { 0, 0, 2, 4, 6, 8, 10, 12 };
std::vector<pitch_level_t> expectedPitches;
for (size_t i = 0; i < expectedSubNotesCount; ++i) {
expectedPitches.push_back(nominalPitchLevel + pitchesWt.at(i) * PITCH_LEVEL_STEP);
}
m_defaultProfile->setPattern(ArticulationType::DiscreteGlissando, m_dummyPattern);
PlaybackEventsMap result;
m_renderer.render(chord, dynamicLevelFromType(mu::mpe::DynamicType::Natural),
ArticulationType::Standard, m_defaultProfile, result);
for (const auto& pair : result) {
// [THEN] We expect that rendered note events number will match expectations
EXPECT_EQ(pair.second.size(), expectedSubNotesCount);
for (size_t i = 0; i < pair.second.size(); ++i) {
const mu::mpe::NoteEvent& noteEvent = std::get<mu::mpe::NoteEvent>(pair.second.at(i));
// [THEN] We expect that each note event has only one articulation applied - Discrete Glissando
EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1);
EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::DiscreteGlissando));
// [THEN] We expect that each sub-note in Discrete Glissando articulation has expected duration
EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, static_cast<int>(expectedDuration));
EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, static_cast<int>(i * expectedDuration));
// [THEN] We expect that each note event will match expected pitch disclosure
EXPECT_EQ(noteEvent.pitchCtx().nominalPitchLevel, expectedPitches.at(i));
}
}
// TEST SECOND GLISS
// [GIVEN] Expected glissando disclosure
expectedSubNotesCount = 4;
expectedDuration = static_cast<float>(WHOLE_NOTE_DURATION) / expectedSubNotesCount;
nominalPitchLevel = pitchLevel(PitchClass::C, 5);
std::vector<int> pitches2 = { 0, 1, 3, 6 };
expectedPitches.clear();
for (size_t i = 0; i < expectedSubNotesCount; ++i) {
expectedPitches.push_back(nominalPitchLevel - pitches2.at(i) * PITCH_LEVEL_STEP);
}
m_defaultProfile->setPattern(ArticulationType::DiscreteGlissando, m_dummyPattern);
PlaybackEventsMap result2;
m_renderer.render(secondChord, dynamicLevelFromType(mu::mpe::DynamicType::Natural),
ArticulationType::Standard, m_defaultProfile, result2);
for (const auto& pair : result2) {
// [THEN] We expect that rendered note events number will match expectations
EXPECT_EQ(pair.second.size(), expectedSubNotesCount);
for (size_t i = 0; i < pair.second.size(); ++i) {
const mu::mpe::NoteEvent& noteEvent = std::get<mu::mpe::NoteEvent>(pair.second.at(i));
// [THEN] We expect that each note event has only one articulation applied - Discrete Glissando
EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1);
EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::DiscreteGlissando));
// [THEN] We expect that each sub-note in Discrete Glissando articulation has expected duration
EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, static_cast<int>(expectedDuration));
EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, WHOLE_NOTE_DURATION + static_cast<int>(i * expectedDuration));
// [THEN] We expect that each note event will match expected pitch disclosure
EXPECT_EQ(noteEvent.pitchCtx().nominalPitchLevel, expectedPitches.at(i));
}
}
}
/**
* @brief PlaybackEventsRendererTests_SingleNote_Acciaccatura
* @details In this case we're gonna render a simple piece of score with a single measure,

View File

@ -12,6 +12,7 @@
<Spanner type="Glissando">
<Glissando>
<text>gliss.</text>
<isHarpGliss>0</isHarpGliss>
<ticks_f>1/4</ticks_f>
<diagonal>1</diagonal>
<anchor>3</anchor>
@ -46,6 +47,7 @@
<Spanner type="Glissando">
<Glissando>
<text>gliss.</text>
<isHarpGliss>0</isHarpGliss>
<subtype>1</subtype>
<ticks_f>1/4</ticks_f>
<diagonal>1</diagonal>

View File

@ -22,8 +22,10 @@
#include "glissandoplaybackmodel.h"
#include "translation.h"
#include "dom/glissando.h"
using namespace mu::inspector;
using namespace mu::engraving;
GlissandoPlaybackModel::GlissandoPlaybackModel(QObject* parent, IElementRepositoryService* repository)
: AbstractInspectorModel(parent, repository)
@ -42,6 +44,7 @@ void GlissandoPlaybackModel::createProperties()
void GlissandoPlaybackModel::requestElements()
{
m_elementList = m_repository->findElementsByType(mu::engraving::ElementType::GLISSANDO);
updateIsHarpGliss();
}
void GlissandoPlaybackModel::loadProperties()
@ -54,6 +57,35 @@ void GlissandoPlaybackModel::resetProperties()
m_styleType->resetToDefault();
}
void GlissandoPlaybackModel::updateIsHarpGliss()
{
bool isHarpGliss = false;
for (const EngravingItem* element : m_elementList) {
if (element->isGlissando()) {
if (toGlissando(element)->isHarpGliss().value_or(false)) {
isHarpGliss = true;
break;
}
}
}
setIsHarpGliss(isHarpGliss);
}
bool GlissandoPlaybackModel::isHarpGliss() const
{
return m_isHarpGliss;
}
void GlissandoPlaybackModel::setIsHarpGliss(bool isHarpGliss)
{
if (isHarpGliss == m_isHarpGliss) {
return;
}
m_isHarpGliss = isHarpGliss;
emit isHarpGlissChanged(m_isHarpGliss);
}
PropertyItem* GlissandoPlaybackModel::styleType() const
{
return m_styleType;

View File

@ -30,11 +30,16 @@ class GlissandoPlaybackModel : public AbstractInspectorModel
Q_OBJECT
Q_PROPERTY(PropertyItem * styleType READ styleType CONSTANT)
Q_PROPERTY(bool isHarpGliss READ isHarpGliss NOTIFY isHarpGlissChanged)
public:
explicit GlissandoPlaybackModel(QObject* parent, IElementRepositoryService* repository);
PropertyItem* styleType() const;
bool isHarpGliss() const;
signals:
void isHarpGlissChanged(bool isHarpGliss);
protected:
void createProperties() override;
@ -43,7 +48,12 @@ protected:
void resetProperties() override;
private:
void updateIsHarpGliss();
void setIsHarpGliss(bool isHarpGliss);
PropertyItem* m_styleType = nullptr;
bool m_isHarpGliss = false;
};
}

View File

@ -33,6 +33,8 @@ ExpandableBlank {
property int navigationRowEnd: contentItem.navigationRowEnd
readonly property bool isHarpGliss: model ? model.isHarpGliss : false
enabled: model ? !model.isEmpty : false
title: model ? model.title : ""
@ -49,12 +51,17 @@ ExpandableBlank {
titleText: qsTrc("inspector", "Style")
propertyItem: root.model ? root.model.styleType : null
model: [
{ text: qsTrc("inspector", "Chromatic"), value: Glissando.STYLE_CHROMATIC },
{ text: qsTrc("inspector", "White keys"), value: Glissando.STYLE_WHITE_KEYS },
{ text: qsTrc("inspector", "Black keys"), value: Glissando.STYLE_BLACK_KEYS },
{ text: qsTrc("inspector", "Diatonic"), value: Glissando.STYLE_DIATONIC },
{ text: qsTrc("inspector", "Portamento"), value: Glissando.STYLE_PORTAMENTO }
]
model: root.isHarpGliss
? [
{ text: qsTrc("inspector", "Default (Diatonic)"), value: Glissando.STYLE_DIATONIC },
{ text: qsTrc("inspector", "Chromatic"), value: Glissando.STYLE_CHROMATIC }
]
: [
{ text: qsTrc("inspector", "Chromatic"), value: Glissando.STYLE_CHROMATIC },
{ text: qsTrc("inspector", "White keys"), value: Glissando.STYLE_WHITE_KEYS },
{ text: qsTrc("inspector", "Black keys"), value: Glissando.STYLE_BLACK_KEYS },
{ text: qsTrc("inspector", "Diatonic"), value: Glissando.STYLE_DIATONIC },
{ text: qsTrc("inspector", "Portamento"), value: Glissando.STYLE_PORTAMENTO }
]
}
}

View File

@ -425,7 +425,7 @@ enum class GlissandoStyle {
WHITE_KEYS = int(mu::engraving::GlissandoStyle::WHITE_KEYS),
BLACK_KEYS = int(mu::engraving::GlissandoStyle::BLACK_KEYS),
DIATONIC = int(mu::engraving::GlissandoStyle::DIATONIC),
PORTAMENTO = int(mu::engraving::GlissandoStyle::PORTAMENTO),
PORTAMENTO = int(mu::engraving::GlissandoStyle::PORTAMENTO)
};
Q_ENUM_NS(GlissandoStyle);