Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/engraving/compat/midi/compatmidirender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,12 @@ void CompatMidiRender::createGraceNotesPlayEvents(const Score* score, const Frac
el.push_back(nel);
}

if (gc->playEventType() == PlayEventType::Auto) {
gc->setNoteEventLists(el);
}
// Always overwrite grace note events: MuseScore 4 exposes no UI for
// manually editing grace note MIDI timing, so any stored PlayEventType::User
// on a grace chord is a stale artifact from an earlier render. Honouring it
// would leave zero-duration events (saved by old buggy renders) in place and
// make the grace note silent in MIDI export.
gc->setNoteEventLists(el);

on += graceDuration;
}
Expand Down Expand Up @@ -634,9 +637,7 @@ void CompatMidiRender::createGraceNotesPlayEvents(const Score* score, const Frac
el.push_back(nel);
}

if (gc->playEventType() == PlayEventType::Auto) {
gc->setNoteEventLists(el);
}
gc->setNoteEventLists(el);
on += graceDuration1;
}
}
Expand Down
16 changes: 13 additions & 3 deletions src/engraving/compat/midi/compatmidirenderinternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,10 @@ static void collectNote(EventsHolder& events, const Note* note, const CollectNot
// calculate additional length due to ties forward
// taking NoteEvent length adjustments into account

int tick1 = note->tick().ticks() + noteParams.tickOffset;
// Grace notes are exported relative to their principal chord and then shifted
// earlier via grace offsets. Using the grace note's own tick here double-shifts
// acciaccaturas in the MIDI export path.
int tick1 = chord->tick().ticks() + noteParams.tickOffset;
const GuitarBend* bendFor = note->bendFor();
const GuitarBend* bendBack = note->bendBack();

Expand Down Expand Up @@ -861,8 +864,15 @@ static void collectNote(EventsHolder& events, const Note* note, const CollectNot
int eventChannel = noteChannel;
playParams.pitch = p;
playParams.velo = std::clamp(velo, 1, 127);
playParams.onTime = std::max(0, on - noteParams.graceOffsetOn);
playParams.offTime = std::max(0, off - noteParams.graceOffsetOff);
int rawOnTime = on - noteParams.graceOffsetOn;
playParams.onTime = std::max(0, rawOnTime);
if (noteParams.graceOffsetOn > 0 && rawOnTime < 0) {
// Grace before beat was clamped (e.g., first beat of piece).
// Preserve the intended duration so the grace remains audible.
playParams.offTime = playParams.onTime + noteParams.graceOffsetOn - 1;
} else {
playParams.offTime = std::max(0, off - noteParams.graceOffsetOff);
}

if (eventEffect == MidiInstrumentEffect::NONE) {
eventEffect = midiEffectFromEvent(e);
Expand Down
5 changes: 4 additions & 1 deletion src/engraving/rw/read400/tread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3184,7 +3184,10 @@ bool TRead::readProperties(Note* n, XmlReader& e, ReadContext& ctx)
}
}
n->setPlayEvents(playEvents);
if (n->chord()) {
// Don't mark grace chords as User: their stored events are always stale
// render artifacts (MuseScore 4 has no UI for editing grace note timing),
// and honouring the flag would suppress recomputation on the next render.
if (n->chord() && !n->chord()->isGrace()) {
n->chord()->setPlayEventType(PlayEventType::User);
}
} else if (tag == "offset") {
Expand Down
9 changes: 8 additions & 1 deletion src/engraving/rw/write/twrite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2468,7 +2468,14 @@ void TWrite::write(const Note* item, XmlWriter& xml, WriteContext& ctx)
writeSpannerEnd(item->tieBack(), xml, ctx, item, item->track());
}

if ((item->chord() == 0 || item->chord()->playEventType() != PlayEventType::Auto) && !item->playEvents().empty()) {
// Never persist play events for grace note chords: MuseScore 4 exposes no UI
// for editing grace note MIDI timing, so stored events are always stale render
// artifacts. Skipping them here breaks the write→load→User→skip-recompute cycle
// that caused grace notes to be silent after a score was saved and reopened.
const bool isGraceNote = item->chord() && item->chord()->isGrace();
if (!isGraceNote
&& (item->chord() == 0 || item->chord()->playEventType() != PlayEventType::Auto)
&& !item->playEvents().empty()) {
xml.startElement("Events");
for (const NoteEvent& e : item->playEvents()) {
write(&e, xml, ctx);
Expand Down
82 changes: 82 additions & 0 deletions src/importexport/midi/tests/midiexport_data/testGraceAfter.mscx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<museScore version="3.01">
<Score>
<Division>480</Division>
<Style>
<Spatium>1.76389</Spatium>
</Style>
<showInvisible>1</showInvisible>
<showUnprintable>1</showUnprintable>
<showFrames>1</showFrames>
<showMargins>0</showMargins>
<metaTag name="workTitle">TestGraceAfter</metaTag>
<Part>
<Staff id="1">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
</Staff>
<trackName>Piano</trackName>
<Instrument>
<longName>Piano</longName>
<shortName>Pno.</shortName>
<trackName>Piano</trackName>
<minPitchP>21</minPitchP>
<maxPitchP>108</maxPitchP>
<minPitchA>21</minPitchA>
<maxPitchA>108</maxPitchA>
<Articulation>
<velocity>100</velocity>
<gateTime>95</gateTime>
</Articulation>
<Channel>
<program value="0"/>
</Channel>
</Instrument>
</Part>
<Staff id="1">
<VBox>
<height>10</height>
<Text>
<style>title</style>
<text>TestGraceAfter</text>
</Text>
</VBox>
<Measure>
<voice>
<Tempo>
<tempo>1</tempo>
<followText>1</followText>
<text><sym>metNoteQuarterUp</sym> = 60</text>
</Tempo>
<Dynamic>
<subtype>mf</subtype>
<velocity>80</velocity>
</Dynamic>
<!-- Quarter note (pitch 60) with a grace-after (pitch 62, eighth) -->
<Chord>
<durationType>eighth</durationType>
<grace8after/>
<Note>
<pitch>62</pitch>
<tpc>16</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>60</pitch>
<tpc>14</tpc>
</Note>
</Chord>
<Rest>
<durationType>quarter</durationType>
</Rest>
<Rest>
<durationType>half</durationType>
</Rest>
</voice>
</Measure>
</Staff>
</Score>
</museScore>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<museScore version="3.01">
<Score>
<Division>480</Division>
<Style>
<Spatium>1.76389</Spatium>
</Style>
<showInvisible>1</showInvisible>
<showUnprintable>1</showUnprintable>
<showFrames>1</showFrames>
<showMargins>0</showMargins>
<metaTag name="workTitle">TestGraceAppoggiatura</metaTag>
<Part>
<Staff id="1">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
</Staff>
<trackName>Piano</trackName>
<Instrument>
<longName>Piano</longName>
<shortName>Pno.</shortName>
<trackName>Piano</trackName>
<minPitchP>21</minPitchP>
<maxPitchP>108</maxPitchP>
<minPitchA>21</minPitchA>
<maxPitchA>108</maxPitchA>
<Articulation>
<velocity>100</velocity>
<gateTime>95</gateTime>
</Articulation>
<Channel>
<program value="0"/>
</Channel>
</Instrument>
</Part>
<Staff id="1">
<VBox>
<height>10</height>
<Text>
<style>title</style>
<text>TestGraceAppoggiatura</text>
</Text>
</VBox>
<Measure>
<voice>
<Tempo>
<tempo>1</tempo>
<followText>1</followText>
<text><sym>metNoteQuarterUp</sym> = 60</text>
</Tempo>
<Dynamic>
<subtype>mf</subtype>
<velocity>80</velocity>
</Dynamic>
<!-- Eighth appoggiatura (pitch 71) before quarter (pitch 72) -->
<Chord>
<durationType>eighth</durationType>
<appoggiatura/>
<Note>
<pitch>71</pitch>
<tpc>19</tpc>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>72</pitch>
<tpc>14</tpc>
</Note>
</Chord>
<!-- Eighth appoggiatura (pitch 71) before half (pitch 72) -->
<Chord>
<durationType>eighth</durationType>
<appoggiatura/>
<Note>
<pitch>71</pitch>
<tpc>19</tpc>
</Note>
</Chord>
<Chord>
<durationType>half</durationType>
<Note>
<pitch>72</pitch>
<tpc>14</tpc>
</Note>
</Chord>
</voice>
</Measure>
</Staff>
</Score>
</museScore>
109 changes: 109 additions & 0 deletions src/importexport/midi/tests/midiexport_data/testGraceStaleEvents.mscx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<museScore version="3.01">
<Score>
<Division>480</Division>
<Style>
<Spatium>1.76389</Spatium>
</Style>
<showInvisible>1</showInvisible>
<showUnprintable>1</showUnprintable>
<showFrames>1</showFrames>
<showMargins>0</showMargins>
<metaTag name="workTitle">TestGraceStaleEvents</metaTag>
<Part>
<Staff id="1">
<StaffType group="pitched">
<name>stdNormal</name>
</StaffType>
</Staff>
<trackName>Piano</trackName>
<Instrument>
<longName>Piano</longName>
<shortName>Pno.</shortName>
<trackName>Piano</trackName>
<minPitchP>21</minPitchP>
<maxPitchP>108</maxPitchP>
<minPitchA>21</minPitchA>
<maxPitchA>108</maxPitchA>
<Articulation>
<velocity>100</velocity>
<gateTime>95</gateTime>
</Articulation>
<Channel>
<program value="0"/>
</Channel>
</Instrument>
</Part>
<Staff id="1">
<VBox>
<height>10</height>
<Text>
<style>title</style>
<text>TestGraceStaleEvents</text>
</Text>
</VBox>
<Measure>
<voice>
<Tempo>
<tempo>1</tempo>
<followText>1</followText>
<text><sym>metNoteQuarterUp</sym> = 60</text>
</Tempo>
<Dynamic>
<subtype>mf</subtype>
<velocity>80</velocity>
</Dynamic>
<!-- Appoggiatura (pitch 71) before half (pitch 72) at beat 1.
Grace note has a stale stored Event with len=0, as written by MuseScore
whenever a score containing grace notes is saved. The grace should still
be exported with correct timing. -->
<Chord>
<durationType>eighth</durationType>
<appoggiatura/>
<Note>
<pitch>71</pitch>
<tpc>19</tpc>
<Events>
<Event>
<len>0</len>
</Event>
</Events>
</Note>
</Chord>
<Chord>
<durationType>half</durationType>
<Note>
<pitch>72</pitch>
<tpc>14</tpc>
</Note>
</Chord>
<!-- Acciaccatura (pitch 71) before quarter (pitch 72) at beat 3.
Also has a stored Event with len=0, as saved by MuseScore. -->
<Chord>
<durationType>eighth</durationType>
<acciaccatura/>
<Note>
<pitch>71</pitch>
<tpc>19</tpc>
<Events>
<Event>
<len>0</len>
</Event>
</Events>
</Note>
</Chord>
<Chord>
<durationType>quarter</durationType>
<Note>
<pitch>72</pitch>
<tpc>14</tpc>
</Note>
</Chord>
<Rest>
<durationType>quarter</durationType>
</Rest>
</voice>
</Measure>
</Staff>
</Score>
</museScore>
Loading
Loading