/** Reads the given MusicXML document and returns the score. */ public static Score read( MxlScorePartwise doc, Score baseScore, ErrorProcessing err, boolean ignoreErrors) { MusicReaderContext context = new MusicReaderContext(baseScore, new MusicReaderSettings(err, ignoreErrors)); // read the parts int staffIndexOffset = 0; It<MxlPart> mxlParts = it(doc.getParts()); for (MxlPart mxlPart : mxlParts) { // clear part-dependent context values int stavesCount = baseScore.getStavesList().getParts().get(mxlParts.getIndex()).getStavesCount(); context = context.beginNewPart(mxlParts.getIndex()); staffIndexOffset += stavesCount; // read the measures It<MxlMeasure> mxlMeasures = it(mxlPart.getMeasures()); for (MxlMeasure mxlMeasure : mxlMeasures) { try { context = readMeasure(context, mxlMeasure, mxlMeasures.getIndex()); } catch (MusicReaderException ex) { throw new RuntimeException("Error at " + ex.getContext().toString(), ex); } catch (Exception ex) { throw new RuntimeException("Error (roughly) around " + context.toString(), ex); } } } // 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 < context.getScore().getStavesCount(); iStaff++) { Staff staff = context.getScore().getStaff(atStaff(iStaff)); for (int iMeasure = 0; iMeasure < staff.getMeasures().size(); iMeasure++) { Measure measure = staff.getMeasures().get(iMeasure); Time newTime = context.getScore().getScoreHeader().getColumnHeader(iMeasure).getTime(); if (newTime != null) { // time signature has changed if (newTime instanceof NormalTime) { measureDuration = ((NormalTime) newTime).getBeatsPerMeasure(); } else { measureDuration = fr(1, 4); // default: 1/4 } } Voice voice0 = measure.getVoices().get(0); if (voice0.isEmpty()) { // TODO: "whole rests" or split. currently, also 3/4 rests are possible context = context.withScore( ScoreController.writeVoiceElement( context.getScore(), mp(iStaff, iMeasure, 0, _0), new Rest(measureDuration))); } } } return context.getScore(); }
/** Reads the given barline element. Currently only left and right barlines are supported. */ private static MusicReaderContext readBarline(MusicReaderContext context, MxlBarline mxlBarline) { MxlRightLeftMiddle location = mxlBarline.getLocation(); MxlRepeat repeat = mxlBarline.getRepeat(); int measureIndex = context.getMP().getMeasure(); BarlineStyle style = null; if (mxlBarline.getBarStyle() != null) style = BarlineStyleReader.read(mxlBarline.getBarStyle().getBarStyle()); if (repeat != null) { // repeat barline if (location == MxlRightLeftMiddle.Left) { // left barline if (repeat.getDirection() == MxlBackwardForward.Forward) { style = notNull(style, BarlineStyle.HeavyLight); context = context.withScore( ScoreController.writeColumnStartBarline( context.getScore(), measureIndex, createForwardRepeatBarline(style))); } } else if (location == MxlRightLeftMiddle.Right) { // right barline if (repeat.getDirection() == MxlBackwardForward.Backward) { style = notNull(style, BarlineStyle.LightHeavy); int times = notNull(repeat.getTimes(), 1).intValue(); context = context.withScore( ScoreController.writeColumnEndBarline( context.getScore(), measureIndex, createBackwardRepeatBarline(style, times))); } } } else { // regular barline style = notNull(style, BarlineStyle.Regular); if (location == MxlRightLeftMiddle.Left) { // left barline context = context.withScore( ScoreController.writeColumnStartBarline( context.getScore(), measureIndex, createBarline(style))); } else if (location == MxlRightLeftMiddle.Right) { // right barline context = context.withScore( ScoreController.writeColumnEndBarline( context.getScore(), measureIndex, createBarline(style))); } } return context; }
/** Reads the given measure element. */ private static MusicReaderContext readMeasure( MusicReaderContext context, MxlMeasure mxlMeasure, int measureIndex) { // begin a new measure context = context.beginNewMeasure(measureIndex); // list all elements PVector<MxlMusicDataContent> content = mxlMeasure.getMusicData().getContent(); for (int i = 0; i < content.size(); i++) { MxlMusicDataContent mxlMDC = content.get(i); switch (mxlMDC.getMusicDataContentType()) { case Note: { // collect all directly following notes with have a chord-element PVector<MxlNote> mxlNotes = pvec((MxlNote) mxlMDC); for (int i2 = i + 1; i2 < content.size(); i2++) { boolean found = false; MxlMusicDataContent mxlMDC2 = content.get(i2); if (mxlMDC2.getMusicDataContentType() == MxlMusicDataContentType.Note) { MxlNote mxlNote2 = (MxlNote) mxlMDC2; if (mxlNote2.getContent().getNoteContentType() == MxlNoteContentType.Normal) { if (((MxlNormalNote) mxlNote2.getContent()).getFullNote().isChord()) { mxlNotes = mxlNotes.plus(mxlNote2); i++; found = true; } } } if (!found) break; } context = readChord(context, mxlNotes); break; } case Attributes: context = readAttributes(context, (MxlAttributes) mxlMDC); break; case Backup: context = readBackup(context, (MxlBackup) mxlMDC); break; case Forward: context = readForward(context, (MxlForward) mxlMDC); break; case Print: context = readPrint(context, (MxlPrint) mxlMDC); break; case Direction: context = readDirection(context, (MxlDirection) mxlMDC); break; case Barline: context = readBarline(context, (MxlBarline) mxlMDC); break; } } return context; }
/** Reads the given direction element. */ private static MusicReaderContext readDirection( MusicReaderContext context, MxlDirection mxlDirection) { // staff int staff = notNull(mxlDirection.getStaff(), 1) - 1; // direction-types Words words = null; for (MxlDirectionType mxlType : mxlDirection.getDirectionTypes()) { MxlDirectionTypeContent mxlDTC = mxlType.getContent(); MxlDirectionTypeContentType mxlDTCType = mxlDTC.getDirectionTypeContentType(); switch (mxlDTCType) { case Dynamics: { // dynamics DynamicsType type = ((MxlDynamics) mxlDTC).getElement(); Dynamics dynamics = new Dynamics(type); context = context.writeMeasureElement(dynamics, staff); break; } case Pedal: { // pedal MxlPedal mxlPedal = (MxlPedal) mxlDTC; Pedal.Type type = null; switch (mxlPedal.getType()) { case Start: type = Type.Start; break; case Stop: type = Type.Stop; break; } if (type != null) { Pedal pedal = new Pedal( type, readPosition( mxlPedal.getPrintStyle().getPosition(), context.getTenthMm(), context.getStaffLinesCount(staff))); context = context.writeMeasureElement(pedal, staff); } break; } case Wedge: { // wedge MxlWedge mxlWedge = (MxlWedge) mxlDTC; int number = mxlWedge.getNumber(); Position pos = readPosition( mxlWedge.getPosition(), context.getTenthMm(), context.getStaffLinesCount(staff)); switch (mxlWedge.getType()) { case Crescendo: Wedge crescendo = new Crescendo(null, pos); context = context.writeMeasureElement(crescendo, staff); context = context.openWedge(number, crescendo); break; case Diminuendo: Wedge diminuendo = new Diminuendo(null, pos); context = context.writeMeasureElement(diminuendo, staff); context = context.openWedge(number, diminuendo); break; case Stop: Tuple2<MusicReaderContext, Wedge> t = context.closeWedge(number); context = t.get1(); Wedge wedge = t.get2(); if (wedge == null) throw new RuntimeException("Wedge " + (number + 1) + " is not open!"); context = context.writeMeasureElement(wedge.getWedgeEnd(), staff); break; } break; } case Words: { // words (currently only one element is supported) if (words == null) { MxlWords mxlWords = (MxlWords) mxlDTC; MxlFormattedText mxlFormattedText = mxlWords.getFormattedText(); FontInfo fontInfo = readFontInfo(mxlFormattedText.getPrintStyle().getFont()); Position position = readPosition( mxlFormattedText.getPrintStyle().getPosition(), context.getTenthMm(), context.getStaffLinesCount(staff)); words = new Words(mxlFormattedText.getValue(), fontInfo, position); } break; } } } // sound MxlSound mxlSound = mxlDirection.getSound(); if (mxlSound != null) { // tempo if (mxlSound.getTempo() != null) { // always expressed in quarter notes per minute int quarterNotesPerMinute = mxlSound.getTempo().intValue(); // if there were words found, use them for the tempo Tempo tempo; if (words != null) { tempo = new Tempo(fr(1, 4), quarterNotesPerMinute, words.getText(), words.getPosition()); words = null; // words were used now } else { tempo = new Tempo(fr(1, 4), quarterNotesPerMinute, null, null); } // write to measure context = context.writeMeasureElement(tempo, staff); } } // if there are words that were not used for the tempo, write them now if (words != null) { context = context.writeMeasureElement(words, staff); } return context; }
/** Reads the given print element. */ private static MusicReaderContext readPrint(MusicReaderContext context, MxlPrint mxlPrint) { MxlPrintAttributes mxlPA = mxlPrint.getPrintAttributes(); // system and page break Boolean newSystem = mxlPA.getNewSystem(); SystemBreak systemBreak = (newSystem == null ? null : (newSystem ? SystemBreak.NewSystem : SystemBreak.NoNewSystem)); Boolean newPage = mxlPA.getNewPage(); PageBreak pageBreak = (newPage == null ? null : (newPage ? PageBreak.NewPage : PageBreak.NoNewPage)); context = context.withScore( ScoreController.writeColumnElement( context.getScore(), atMeasure(context.getMP().getMeasure()), new Break(pageBreak, systemBreak))); // we assume that custom system layout information is just used in combination with // forced system/page breaks. so we ignore system-layout elements which are not combined // with system/page breaks. // the first measure of a score is also ok. if (context.getMP().getMeasure() == 0 || systemBreak == SystemBreak.NewSystem || pageBreak == PageBreak.NewPage) { // first page or new page? boolean isPageBreak = pageBreak == PageBreak.NewPage; boolean isPageStarted = (context.getMP().getMeasure() == 0 || isPageBreak); if (isPageBreak) { // increment page index context = context.incPageIndex(); } // first system or new system? boolean isSystemBreak = isPageBreak || systemBreak == SystemBreak.NewSystem; if (isSystemBreak) { // increment system index context = context.incSystemIndex(); } // read system layout, if there MxlSystemLayout mxlSystemLayout = mxlPrint.getLayout().getSystemLayout(); if (mxlSystemLayout != null) { SystemLayoutReader.Value sl = SystemLayoutReader.read(mxlSystemLayout, context.getTenthMm()); SystemLayout systemLayout = sl.systemLayout; // for first systems on a page, use top-system-distance if (isPageStarted) { systemLayout = systemLayout.withSystemDistance(sl.topSystemDistance); } // apply values ScoreHeader scoreHeader = context .getScore() .getScoreHeader() .withSystemLayout(context.getSystemIndex(), systemLayout); context = context.withScore(context.getScore().withScoreHeader(scoreHeader)); } } // staff layouts for (MxlStaffLayout mxlStaffLayout : mxlPrint.getLayout().getStaffLayouts()) { int staffIndex = mxlStaffLayout.getNumberNotNull() - 1; context = context.withScore( ScoreController.withStaffLayout( context.getScore(), context.getSystemIndex(), context.getPartStavesIndices().getStart() + staffIndex, readStaffLayout(mxlStaffLayout, context.getTenthMm()).staffLayout)); } return context; }
/** Returns the duration as a {@link Fraction} from the given duration in divisions. */ public static Fraction readDuration(MusicReaderContext context, int duration) { if (duration == 0) { throw new RuntimeException("Element has a duration of 0."); } return fr(duration, 4 * context.getDivisions()); }
/** Reads the given forward element. */ private static MusicReaderContext readForward(MusicReaderContext context, MxlForward mxlForward) { // duration Fraction duration = readDuration(context, mxlForward.getDuration()); // move cursor return context.moveCurrentBeat(duration); }
/** Reads the given backup element. */ private static MusicReaderContext readBackup(MusicReaderContext context, MxlBackup mxlBackup) { // duration Fraction duration = readDuration(context, mxlBackup.getDuration()).invert(); // move cursor return context.moveCurrentBeat(duration); }
/** Reads the given attributes element. */ private static MusicReaderContext readAttributes( MusicReaderContext context, MxlAttributes mxlAttributes) { // divisions Integer divisions = mxlAttributes.getDivisions(); if (divisions != null) { context = context.withDivisions(divisions); } // key signature MxlKey mxlKey = mxlAttributes.getKey(); if (mxlKey != null) { // only the fifths element is supported int mxlFifths = mxlKey.getFifths(); // write to column header (TODO: attribute "number" for single staves) Key key = new TraditionalKey(mxlFifths); context = context.writeColumnElement(key); } // time signature MxlTime mxlTime = mxlAttributes.getTime(); if (mxlTime != null) { Time time = null; MxlTimeContentType type = mxlTime.getContent().getTimeContentType(); if (type == MxlTimeContentType.SenzaMisura) { // senza misura time = new SenzaMisura(); } else if (type == MxlTimeContentType.NormalTime) { // at the moment we read only one beats/beat-type // currently we accept only integers > 0 MxlNormalTime mxlNormalTime = (MxlNormalTime) mxlTime.getContent(); time = new NormalTime(mxlNormalTime.getBeats(), mxlNormalTime.getBeatType()); } // write to column header (TODO: attribute "number" for single staves) if (time != null) { context = context.writeColumnElement(time); } } // clefs MxlClef mxlClef = mxlAttributes.getClef(); if (mxlClef != null) { Clef clef = null; switch (mxlClef.getSign()) { /* TODO case C: clef = new Clef(ClefType.C); break; */ case F: clef = new Clef(ClefType.F); break; case G: clef = new Clef(ClefType.G); break; /* TODO case NONE: clef = new Clef(ClefType.C); break; case PERCUSSION: clef = new Clef(ClefType.C); break; case TAB: clef = new Clef(ClefType.C); break; */ } // staff (called "number" in MusicXML), first staff is default int staff = mxlClef.getNumber() - 1; // add to staff if (clef != null) { context = context.writeMeasureElement(clef, staff); } } /* TODO: transposition changes ~= instrument changes //transposition changes MxlTranspose mxlTranspose = mxlAttributes.getTranspose(); if (mxlTranspose != null) { int chromatic = mxlTranspose.getChromatic(); Transpose transpose = new Transpose(chromatic); //write to all staves of this part for (int staff = 0; staff < context.getPartStavesIndices().getCount(); staff++) { writeNoVoiceElement(transpose, staff); } } */ return context; }