package com.xenoage.zong.io.musicxml.in.readers;
import com.xenoage.utils.iterators.It;
import com.xenoage.utils.math.Fraction;
import com.xenoage.zong.commands.core.music.ColumnElementWrite;
import com.xenoage.zong.commands.core.music.MeasureAddUpTo;
import com.xenoage.zong.commands.core.music.MeasureElementWrite;
import com.xenoage.zong.commands.core.music.VoiceElementWrite;
import com.xenoage.zong.core.Score;
import com.xenoage.zong.core.music.Measure;
import com.xenoage.zong.core.music.Part;
import com.xenoage.zong.core.music.Staff;
import com.xenoage.zong.core.music.Voice;
import com.xenoage.zong.core.music.clef.Clef;
import com.xenoage.zong.core.music.clef.ClefType;
import com.xenoage.zong.core.music.group.StavesRange;
import com.xenoage.zong.core.music.key.TraditionalKey;
import com.xenoage.zong.core.music.rest.Rest;
import com.xenoage.zong.core.music.time.TimeSignature;
import com.xenoage.zong.core.music.time.TimeType;
import com.xenoage.zong.core.position.MP;
import com.xenoage.zong.io.musicxml.in.util.ErrorHandling;
import com.xenoage.zong.io.musicxml.in.util.MusicReaderException;
import com.xenoage.zong.musicxml.types.MxlScorePartwise;
import com.xenoage.zong.musicxml.types.partwise.MxlMeasure;
import com.xenoage.zong.musicxml.types.partwise.MxlPart;
import lombok.RequiredArgsConstructor;
import static com.xenoage.utils.iterators.It.it;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.utils.math.Fraction._0;
import static com.xenoage.utils.math.Fraction.fr;
import static com.xenoage.zong.core.position.MP.atElement;
import static com.xenoage.zong.core.position.MP.atStaff;
import static com.xenoage.zong.io.musicxml.in.util.CommandPerformer.execute;
/**
* This class reads the actual musical contents of
* the given partwise MusicXML 2.0 document into a {@link Score}.
*
* If possible, this reader works with the voice-element
* to separate voices. TODO: if not existent or
* used unreliably within a measure, implement this algorithm:
* http://archive.mail-list.com/musicxml/msg01673.html
*
* TODO: Connect chords over staves, if they have the same
* voice element but different staff element.
*
* @author Andreas Wenger
*/
@RequiredArgsConstructor
public class ScoreReader {
private final MxlScorePartwise doc;
public void readToScore(Score score, ErrorHandling errorHandling) {
Context context = new Context(score, new ReaderSettings(errorHandling));
//create the measures of the parts
It<MxlPart> mxlParts = it(doc.getParts());
for (MxlPart mxlPart : mxlParts) {
//create measures
execute(new MeasureAddUpTo(score, mxlPart.getMeasures().size()));
//initialize each measure with a C clef
Part part = score.getStavesList().getParts().get(mxlParts.getIndex());
StavesRange stavesRange = score.getStavesList().getPartStaffIndices(part);
for (int staff : stavesRange.getRange()) {
execute(new MeasureElementWrite(new Clef(ClefType.clefTreble),
score.getMeasure(MP.atMeasure(staff, 0)), _0));
}
}
//write a 4/4 measure and C key signature in the first measure
execute(new ColumnElementWrite(new TimeSignature(TimeType.time_4_4), score.getColumnHeader(0), _0, null));
execute(new ColumnElementWrite(new TraditionalKey(0), score.getColumnHeader(0), _0, null));
//read the parts
mxlParts = it(doc.getParts());
for (MxlPart mxlPart : mxlParts) {
//clear part-dependent context values
context.beginNewPart(mxlParts.getIndex());
//read the measures
It<MxlMeasure> mxlMeasures = it(mxlPart.getMeasures());
for (MxlMeasure mxlMeasure : mxlMeasures) {
try {
MeasureReader.readToContext(mxlMeasure, mxlMeasures.getIndex(), context);
} catch (MusicReaderException ex) {
throw new RuntimeException("Error at " + ex.getContext().toString(), ex);
} catch (Exception ex) {
throw new RuntimeException("Error (roughly) around " + context.toString(), ex);
}
}
}
//remove unclosed elements
context.removeUnclosedWedges();
//go through the whole score, and fill empty measures (that means, measures where
//voice 0 has no single VoiceElement) with rests
Fraction measureDuration = fr(1, 4);
for (int iStaff = 0; iStaff < score.getStavesCount(); iStaff++) {
Staff staff = score.getStaff(atStaff(iStaff));
for (int iMeasure : range(staff.getMeasures())) {
Measure measure = staff.getMeasure(iMeasure);
TimeSignature newTime = score.getHeader().getColumnHeader(iMeasure).getTime();
if (newTime != null) {
//time signature has changed
measureDuration = newTime.getType().getMeasureBeats();
}
if (measureDuration == null) { //senza misura
measureDuration = fr(4, 4); //use whole rest
}
Voice voice0 = measure.getVoice(0);
if (voice0.isEmpty()) {
//TODO: "whole rests" or split. currently, also 3/4 rests are possible
MP mp = atElement(iStaff, iMeasure, 0, 0);
new VoiceElementWrite(score.getVoice(mp), mp, new Rest(measureDuration), null).execute();
}
}
}
}
}