@@ -97,7 +97,12 @@ interface MusicXmlHarmonicGroup {
9797 * @internal
9898 */
9999class StaffContext {
100- public currentDynamics = DynamicValue . F ;
100+ /**
101+ * The dynamics of a direction, shown on the next note of the staff (and of the voice, if given).
102+ */
103+ public pendingDynamics : DynamicValue | null = null ;
104+ public pendingDynamicsVoice : string = '' ;
105+ public lastDynamics : DynamicValue | null = null ;
101106 public transpose : number = 0 ;
102107 public isExplicitlyBeamed = false ;
103108}
@@ -283,7 +288,6 @@ export class MusicXmlImporter extends ScoreImporter {
283288 * Whether the exporter writes the stop of octave shifts before the last shifted note instead of after it.
284289 */
285290 private _octaveShiftEndsBeforeLastNote : boolean = false ;
286- private _currentDynamics = DynamicValue . F ;
287291
288292 public get name ( ) : string {
289293 return 'MusicXML' ;
@@ -303,6 +307,7 @@ export class MusicXmlImporter extends ScoreImporter {
303307
304308 this . _parseDom ( dom ) ;
305309 this . _applySpans ( ) ;
310+ this . _applyDynamics ( ) ;
306311 ModelUtils . consolidate ( this . _score ) ;
307312 this . _score . finish ( this . settings ) ;
308313 this . _score . rebuildRepeatGroups ( ) ;
@@ -2263,7 +2268,9 @@ export class MusicXmlImporter extends ScoreImporter {
22632268 case 'dynamics' :
22642269 const newDynamics = this . _parseDynamics ( direction ) ;
22652270 if ( newDynamics !== null ) {
2266- this . _currentDynamics = newDynamics ;
2271+ const context = this . _getStaffContext ( staff ) ;
2272+ context . pendingDynamics = newDynamics ;
2273+ context . pendingDynamicsVoice = voice ;
22672274 this . _score . stylesheet . hideDynamics = false ;
22682275 }
22692276 break ;
@@ -2653,6 +2660,15 @@ export class MusicXmlImporter extends ScoreImporter {
26532660 beat . addNote ( note ! ) ;
26542661 } else {
26552662 beat = this . _createBeat ( staff , masterBar , voiceRaw , note ) ;
2663+ const context = this . _getStaffContext ( staff ) ;
2664+ if (
2665+ note !== null &&
2666+ context . pendingDynamics !== null &&
2667+ ( context . pendingDynamicsVoice . length === 0 || context . pendingDynamicsVoice === voiceRaw )
2668+ ) {
2669+ this . _markDynamics ( context , beat , context . pendingDynamics ! , false ) ;
2670+ context . pendingDynamics = null ;
2671+ }
26562672 }
26572673
26582674 if ( note !== null ) {
@@ -2995,7 +3011,6 @@ export class MusicXmlImporter extends ScoreImporter {
29953011 // need an empty placeholder beat for the gap
29963012 if ( gap > 0 ) {
29973013 const placeholder = new Beat ( ) ;
2998- placeholder . dynamics = this . _currentDynamics ;
29993014 placeholder . isEmpty = true ;
30003015 placeholder . duration = Duration . TwoHundredFiftySixth ; // smallest we have
30013016 placeholder . overrideDisplayDuration = gap ;
@@ -3013,7 +3028,6 @@ export class MusicXmlImporter extends ScoreImporter {
30133028 this . _beats . push ( newBeat ) ;
30143029 this . _harmonicGroups . clear ( ) ;
30153030 newBeat . isEmpty = false ;
3016- newBeat . dynamics = this . _currentDynamics ;
30173031
30183032 const automations = this . _nextBeatAutomations ;
30193033 this . _nextBeatAutomations = null ;
@@ -3516,7 +3530,6 @@ export class MusicXmlImporter extends ScoreImporter {
35163530 }
35173531
35183532 const placeholder = new Beat ( ) ;
3519- placeholder . dynamics = this . _currentDynamics ;
35203533 placeholder . isEmpty = false ;
35213534 placeholder . duration = preferredDuration ;
35223535 placeholder . overrideDisplayDuration = preferredDurationTicks ;
@@ -3710,8 +3723,8 @@ export class MusicXmlImporter extends ScoreImporter {
37103723 case 'dynamics' :
37113724 const dynamics = this . _parseDynamics ( c ) ;
37123725 if ( dynamics !== null ) {
3713- beat . dynamics = dynamics ;
3714- this . _currentDynamics = dynamics ;
3726+ this . _markDynamics ( this . _getStaffContext ( beat . voice . bar . staff ) , beat , dynamics , true ) ;
3727+ this . _score . stylesheet . hideDynamics = false ;
37153728 }
37163729 break ;
37173730 case 'fermata' :
@@ -3747,6 +3760,83 @@ export class MusicXmlImporter extends ScoreImporter {
37473760 }
37483761 }
37493762
3763+ /**
3764+ * A note-attached marking repeating the previous one of the staff is not shown, as some exporters (e.g. Guitar Pro)
3765+ * write the dynamics on every note.
3766+ */
3767+ private _markDynamics ( context : StaffContext , beat : Beat , value : DynamicValue , isNoteAttached : boolean ) {
3768+ beat . dynamics = value ;
3769+ beat . showDynamics = ! isNoteAttached || context . lastDynamics !== value ;
3770+ context . lastDynamics = value ;
3771+ }
3772+
3773+ /**
3774+ * Carries the dynamics markings forward in time to all beats of their part, e.g. a single marking for both hands
3775+ * of a piano. Of markings at the same position, the one on the own staff wins.
3776+ */
3777+ private _applyDynamics ( ) {
3778+ if ( this . _score . stylesheet . hideDynamics ) {
3779+ return ;
3780+ }
3781+ for ( const track of this . _score . tracks ) {
3782+ const markings : Beat [ ] = [ ] ;
3783+ for ( const staff of track . staves ) {
3784+ for ( const bar of staff . bars ) {
3785+ for ( const voice of bar . voices ) {
3786+ for ( const beat of voice . beats ) {
3787+ if ( beat . showDynamics !== undefined ) {
3788+ markings . push ( beat ) ;
3789+ }
3790+ }
3791+ }
3792+ }
3793+ }
3794+
3795+ for ( const staff of track . staves ) {
3796+ const ordered = markings . slice ( ) ;
3797+ ordered . sort ( ( a , b ) => {
3798+ const position = MusicXmlImporter . _comparePosition ( a , b ) ;
3799+ if ( position !== 0 ) {
3800+ return position ;
3801+ }
3802+ return ( a . voice . bar . staff === staff ? 1 : 0 ) - ( b . voice . bar . staff === staff ? 1 : 0 ) ;
3803+ } ) ;
3804+
3805+ // per voice: index of the next marking and the dynamics in effect
3806+ const nextMarking : number [ ] = [ ] ;
3807+ const current : DynamicValue [ ] = [ ] ;
3808+ for ( const bar of staff . bars ) {
3809+ for ( const voice of bar . voices ) {
3810+ while ( nextMarking . length <= voice . index ) {
3811+ nextMarking . push ( 0 ) ;
3812+ current . push ( DynamicValue . F ) ;
3813+ }
3814+ for ( const beat of voice . beats ) {
3815+ let next = nextMarking [ voice . index ] ;
3816+ while (
3817+ next < ordered . length &&
3818+ MusicXmlImporter . _comparePosition ( ordered [ next ] , beat ) <= 0
3819+ ) {
3820+ current [ voice . index ] = ordered [ next ] . dynamics ;
3821+ next ++ ;
3822+ }
3823+ nextMarking [ voice . index ] = next ;
3824+ if ( beat . showDynamics === undefined ) {
3825+ beat . dynamics = current [ voice . index ] ;
3826+ beat . showDynamics = false ;
3827+ }
3828+ }
3829+ }
3830+ }
3831+ }
3832+ }
3833+ }
3834+
3835+ private static _comparePosition ( a : Beat , b : Beat ) : number {
3836+ const bar = a . voice . bar . index - b . voice . bar . index ;
3837+ return bar !== 0 ? bar : a . displayStart - b . displayStart ;
3838+ }
3839+
37503840 private _getStaffContext ( staff : Staff ) {
37513841 if ( ! this . _staffToContext . has ( staff ) ) {
37523842 const context = new StaffContext ( ) ;
0 commit comments