Skip to content

Commit 3339e9c

Browse files
jonashogstromclaude
andcommitted
Honour an explicit dynamics="0" on MusicXML note import
The dynamics attribute of the <note> element expresses the note velocity as a percentage of the MIDI 1.0 default forte value of 90. Positive values were imported correctly, but an explicit dynamics="0" (a silent note, e.g. a ghost note or an unpitched gesture with an x notehead) was indistinguishable from an absent attribute and silently dropped, so the note played at full default velocity. Detect the attribute's presence instead of testing the computed value, and clamp the velocity to [1, 127]: the score model reserves velocity 0 for "unset", so 1 is the lowest representable (effectively silent) value, and the upper bound matches the clamping already applied to direction-level <sound dynamics> values. Resolves: #33803 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d53a8ed commit 3339e9c

4 files changed

Lines changed: 257 additions & 2 deletions

File tree

src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6929,7 +6929,11 @@ Note* MusicXmlParserPass2::note(const String& partId,
69296929
Color beamColor;
69306930
bool noteheadParentheses = false;
69316931
String noteheadFilled;
6932-
int velocity = round(m_e.doubleAttribute("dynamics") * 0.9);
6932+
// velocity as a percentage of the MIDI 1.0 default forte value of 90;
6933+
// an explicit dynamics="0" means a silent note, which the score model can
6934+
// only represent as velocity 1 (velocity 0 means "unset")
6935+
const bool hasDynamics = m_e.hasAttribute("dynamics");
6936+
const int velocity = std::clamp(int(round(m_e.doubleAttribute("dynamics") * 0.9)), 1, 127);
69336937
bool graceSlash = false;
69346938
bool printObject = m_e.asciiAttribute("print-object") != "no";
69356939
bool printLyric = (printObject && m_e.asciiAttribute("print-lyric") != "no") || m_e.asciiAttribute("print-lyric") == "yes";
@@ -7260,7 +7264,7 @@ Note* MusicXmlParserPass2::note(const String& partId,
72607264
}
72617265
}
72627266

7263-
if (velocity > 0) {
7267+
if (hasDynamics) {
72647268
note->setUserVelocity(velocity);
72657269
}
72667270

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<!--
3+
The dynamics attribute of <note> maps to the note's velocity as a
4+
percentage of the MIDI 1.0 default forte value of 90. The expected
5+
imported velocities (visible as <velocity> in the reference .mscx) are:
6+
- no dynamics attribute -> no <velocity> element (unset)
7+
- dynamics="0" -> velocity 1 (silent note; 0 means "unset"
8+
in the model, so it is clamped
9+
to the lowest representable value)
10+
- dynamics="50" -> velocity 45 (50% of forte = 90)
11+
- dynamics="200" -> velocity 127 (clamped to the MIDI maximum)
12+
-->
13+
<score-partwise version="4.0">
14+
<movement-title>Note dynamics attribute</movement-title>
15+
<part-list>
16+
<score-part id="P1">
17+
<part-name>Voice</part-name>
18+
</score-part>
19+
</part-list>
20+
<part id="P1">
21+
<measure number="1">
22+
<attributes>
23+
<divisions>480</divisions>
24+
<key>
25+
<fifths>0</fifths>
26+
</key>
27+
<time>
28+
<beats>5</beats>
29+
<beat-type>4</beat-type>
30+
</time>
31+
<clef>
32+
<sign>G</sign>
33+
<line>2</line>
34+
</clef>
35+
</attributes>
36+
<note>
37+
<pitch>
38+
<step>C</step>
39+
<octave>4</octave>
40+
</pitch>
41+
<duration>480</duration>
42+
<voice>1</voice>
43+
<type>quarter</type>
44+
</note>
45+
<note dynamics="0">
46+
<pitch>
47+
<step>D</step>
48+
<octave>4</octave>
49+
</pitch>
50+
<duration>480</duration>
51+
<voice>1</voice>
52+
<type>quarter</type>
53+
<notehead>x</notehead>
54+
</note>
55+
<note dynamics="50">
56+
<pitch>
57+
<step>E</step>
58+
<octave>4</octave>
59+
</pitch>
60+
<duration>480</duration>
61+
<voice>1</voice>
62+
<type>quarter</type>
63+
</note>
64+
<note dynamics="200">
65+
<pitch>
66+
<step>F</step>
67+
<octave>4</octave>
68+
</pitch>
69+
<duration>480</duration>
70+
<voice>1</voice>
71+
<type>quarter</type>
72+
</note>
73+
<note>
74+
<pitch>
75+
<step>G</step>
76+
<octave>4</octave>
77+
</pitch>
78+
<duration>480</duration>
79+
<voice>1</voice>
80+
<type>quarter</type>
81+
</note>
82+
</measure>
83+
</part>
84+
</score-partwise>
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<museScore version="5.00">
3+
<Score>
4+
<eid>B_B</eid>
5+
<Division>480</Division>
6+
<Style>
7+
<hideInstrumentNameIfOneInstrument>0</hideInstrumentNameIfOneInstrument>
8+
<spatium>1.75</spatium>
9+
</Style>
10+
<showInvisible>1</showInvisible>
11+
<showUnprintable>1</showUnprintable>
12+
<showFrames>1</showFrames>
13+
<showMargins>0</showMargins>
14+
<metaTag name="arranger"></metaTag>
15+
<metaTag name="composer"></metaTag>
16+
<metaTag name="copyright"></metaTag>
17+
<metaTag name="lyricist"></metaTag>
18+
<metaTag name="movementNumber"></metaTag>
19+
<metaTag name="movementTitle">Note dynamics attribute</metaTag>
20+
<metaTag name="source"></metaTag>
21+
<metaTag name="translator"></metaTag>
22+
<metaTag name="workNumber"></metaTag>
23+
<metaTag name="workTitle"></metaTag>
24+
<Part id="1">
25+
<eid>C_C</eid>
26+
<Staff>
27+
<eid>D_D</eid>
28+
<StaffType group="pitched">
29+
<name>stdNormal</name>
30+
</StaffType>
31+
</Staff>
32+
<Instrument id="voice">
33+
<InstrumentLabel>
34+
<longName>Voice</longName>
35+
</InstrumentLabel>
36+
<trackName></trackName>
37+
<instrumentId>voice.vocals</instrumentId>
38+
<Articulation>
39+
<velocity>100</velocity>
40+
<gateTime>100</gateTime>
41+
</Articulation>
42+
<Articulation name="staccatissimo">
43+
<velocity>100</velocity>
44+
<gateTime>33</gateTime>
45+
</Articulation>
46+
<Articulation name="staccato">
47+
<velocity>100</velocity>
48+
<gateTime>50</gateTime>
49+
</Articulation>
50+
<Articulation name="portato">
51+
<velocity>100</velocity>
52+
<gateTime>67</gateTime>
53+
</Articulation>
54+
<Articulation name="tenuto">
55+
<velocity>100</velocity>
56+
<gateTime>100</gateTime>
57+
</Articulation>
58+
<Articulation name="marcato">
59+
<velocity>120</velocity>
60+
<gateTime>67</gateTime>
61+
</Articulation>
62+
<Articulation name="sforzato">
63+
<velocity>150</velocity>
64+
<gateTime>100</gateTime>
65+
</Articulation>
66+
<Articulation name="sforzatoStaccato">
67+
<velocity>150</velocity>
68+
<gateTime>50</gateTime>
69+
</Articulation>
70+
<Articulation name="marcatoStaccato">
71+
<velocity>120</velocity>
72+
<gateTime>50</gateTime>
73+
</Articulation>
74+
<Articulation name="marcatoTenuto">
75+
<velocity>120</velocity>
76+
<gateTime>100</gateTime>
77+
</Articulation>
78+
<Channel>
79+
<program value="0"/>
80+
<controller ctrl="10" value="63"/>
81+
</Channel>
82+
</Instrument>
83+
</Part>
84+
<Staff id="1">
85+
<VBox>
86+
<height>10</height>
87+
<eid>E_E</eid>
88+
<Text>
89+
<eid>F_F</eid>
90+
<style>title</style>
91+
<text>Note dynamics attribute</text>
92+
</Text>
93+
</VBox>
94+
<Measure>
95+
<eid>G_G</eid>
96+
<voice>
97+
<Clef>
98+
<concertClefType>G</concertClefType>
99+
<transposingClefType>G</transposingClefType>
100+
<isHeader>1</isHeader>
101+
<eid>H_H</eid>
102+
</Clef>
103+
<TimeSig>
104+
<eid>I_I</eid>
105+
<sigN>5</sigN>
106+
<sigD>4</sigD>
107+
</TimeSig>
108+
<Chord>
109+
<eid>J_J</eid>
110+
<durationType>quarter</durationType>
111+
<Note>
112+
<eid>K_K</eid>
113+
<pitch>60</pitch>
114+
<tpc>14</tpc>
115+
</Note>
116+
</Chord>
117+
<Chord>
118+
<eid>L_L</eid>
119+
<durationType>quarter</durationType>
120+
<Note>
121+
<eid>M_M</eid>
122+
<pitch>62</pitch>
123+
<tpc>16</tpc>
124+
<head>cross</head>
125+
<velocity>1</velocity>
126+
</Note>
127+
</Chord>
128+
<Chord>
129+
<eid>N_N</eid>
130+
<durationType>quarter</durationType>
131+
<Note>
132+
<eid>O_O</eid>
133+
<pitch>64</pitch>
134+
<tpc>18</tpc>
135+
<velocity>45</velocity>
136+
</Note>
137+
</Chord>
138+
<Chord>
139+
<eid>P_P</eid>
140+
<durationType>quarter</durationType>
141+
<Note>
142+
<eid>Q_Q</eid>
143+
<pitch>65</pitch>
144+
<tpc>13</tpc>
145+
<velocity>127</velocity>
146+
</Note>
147+
</Chord>
148+
<Chord>
149+
<eid>R_R</eid>
150+
<durationType>quarter</durationType>
151+
<Note>
152+
<eid>S_S</eid>
153+
<pitch>67</pitch>
154+
<tpc>15</tpc>
155+
</Note>
156+
</Chord>
157+
<BarLine>
158+
<eid>T_T</eid>
159+
</BarLine>
160+
</voice>
161+
</Measure>
162+
</Staff>
163+
</Score>
164+
</museScore>

src/importexport/musicxml/tests/musicxml_tests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,9 @@ TEST_F(MusicXml_Tests, noteAttributes3) {
10241024
TEST_F(MusicXml_Tests, noteColor) {
10251025
musicXmlIoTest("testNoteColor");
10261026
}
1027+
TEST_F(MusicXml_Tests, noteDynamics) {
1028+
musicXmlImportTestRef("testNoteDynamics");
1029+
}
10271030
TEST_F(MusicXml_Tests, noteheadNames) {
10281031
musicXmlIoTest("testNoteheadNames");
10291032
}

0 commit comments

Comments
 (0)