package com.xenoage.zong.io.musicxml.in.util; import com.xenoage.utils.math.VSide; import com.xenoage.zong.core.music.Pitch; import com.xenoage.zong.core.music.chord.Chord; import com.xenoage.zong.core.music.slur.SlurType; import com.xenoage.zong.core.music.slur.SlurWaypoint; import com.xenoage.zong.core.position.MP; import com.xenoage.zong.io.musicxml.in.readers.Context; import java.util.Map; import static com.xenoage.utils.collections.CollectionUtils.map; /** * Unclosed unnumbered tied elements, needed during MusicXML import. * * The elements can be distinguished by number, however this * is rarely done, and when, this is handled by registerSlur method * in the {@link Context} class. * * Note pitches will usually suffice to distinguish tieds. * However, we saw MusicXML files where a tied note in the middle * of a long tie (which means that it has both a start and a stop * element) provided the start element for the following tied first * and then a stop element for the preceding tied. * To support this case, we remember up to two tieds. When a tied * is closed at the same musical position where the last one was * opened, the earlier tied is used instead. * * @author Andreas Wenger */ public class OpenUnnumberedTieds { private Map<Pitch, OpenSlur> openTieds = map(); private Map<Pitch, OpenSlur> openEarlierTieds = map(); /** * Starts a tie. */ public void startTied(SlurWaypoint wp, VSide side) { Chord chord = wp.getChord(); Pitch pitch = chord.getNotes().get(wp.getNoteIndex()).getPitch(); //already a tied open for this pitch? then remember it, too if (openTieds.get(pitch) != null) openEarlierTieds.put(pitch, openTieds.get(pitch)); //add new tied OpenSlur openTied = new OpenSlur(); openTied.type = SlurType.Tie; openTied.start = new OpenSlur.Waypoint(wp, side); openTieds.put(pitch, openTied); } /** * Ends an existing tie and returns the {@link OpenSlur}. * When the tie does not exist, null is returned. */ public OpenSlur stopTied(SlurWaypoint wp, VSide side, Context context) { Chord chord = wp.getChord(); Pitch pitch = chord.getNotes().get(wp.getNoteIndex()).getPitch(); //get tied for this pitch OpenSlur openTied = openTieds.remove(pitch); if (openTied == null) { context.reportError("Can not stop non-existing tied"); return null; } //does start chord exist? could have been overwritten by backup element MP startMp = openTied.start.wp.getChord().getMP(); if (startMp == null) { context.reportError("Tied can not be closed; start chord does not exist"); return null; } //is tied closed at the same musical position where it was opened? //then close the earlier open tied instead, if there is one if (startMp.equals(context.getMp())) { openTieds.put(pitch, openTied); //remember last tied openTied = openEarlierTieds.remove(pitch); //use earlier tied instead if (openTied == null) { context.reportError("Tied can not be stopped on starting position, " + "and there is no earlier tied"); return null; } } openTied.stop = new OpenSlur.Waypoint(wp, side); return openTied; } }