/* -*- c-basic-offset: 2; indent-tabs-mode: nil; -*- */ /* * FreeDots -- MusicXML to braille music transcription * * Copyright 2008-2010 Mario Lang All Rights Reserved. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details (a copy is included in the LICENSE.txt file that * accompanied this code). * * You should have received a copy of the GNU General Public License * along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * This file is maintained by Mario Lang <mlang@delysid.org>. */ package freedots.transcription; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import freedots.braille.AlternativeEnding; import freedots.braille.ArtificialWholeRest; import freedots.braille.BrailleHarmony; import freedots.braille.BrailleKeySignature; import freedots.braille.BrailleList; import freedots.braille.BrailleSyllable; import freedots.braille.BrailleTimeSignature; import freedots.braille.DottedDoubleBarSign; import freedots.braille.DoubleBarSign; import freedots.braille.HarmonyPart; import freedots.braille.LeftHandPart; import freedots.braille.MusicHyphen; import freedots.braille.MusicPart; import freedots.braille.PostDottedDoubleBarSign; import freedots.braille.RightHandPart; import freedots.braille.Text; import freedots.braille.TextPart; import freedots.braille.LowerRange; import freedots.braille.UpperNumber; import freedots.math.Fraction; import freedots.music.ClefChange; import freedots.music.EndBar; import freedots.music.Event; import freedots.music.GlobalKeyChange; import freedots.music.KeyChange; import freedots.music.KeySignature; import freedots.music.Lyric; import freedots.music.MusicList; import freedots.music.Staff; import freedots.music.StartBar; import freedots.music.Syllabic; import freedots.musicxml.Direction; import freedots.musicxml.Harmony; import freedots.musicxml.Note; import freedots.musicxml.Part; import freedots.musicxml.Score; import freedots.Options; class SectionBySection implements Strategy { private Options options = null; private Score score = null; private Transcriber transcriber = null; /** Main entry point to invoke implemented transcription Strategy. * * @param transcriber is the Transcriber object to use */ public void transcribe(final Transcriber transcriber) { this.transcriber = transcriber; options = transcriber.getOptions(); score = transcriber.getScore(); for (Part part: score.getParts()) { String name = part.getName(); if (name != null && !name.isEmpty()) transcriber.printLine(name); List<Direction> directives = part.getDirectives(); String directiveText = ""; if (directives.size() == 1) { final Direction directive = directives.get(0); directiveText = directives.get(0).getWords().trim() + " "; transcriber.getAlreadyPrintedDirections().add(directive); } BrailleTimeSignature bTimeSig = new BrailleTimeSignature(part.getTimeSignature()); if (!directiveText.isEmpty()) { transcriber.printString(new Text(directiveText) { @Override public String getDescription() { return "A directive at the beginning"; } }); } // TODO: FIXME transcriber.printString(new BrailleKeySignature(part.getKeySignature())); transcriber.printString(bTimeSig); transcriber.newLine(); List<Section> sections = getSections(part); for (Section section: sections) transcribeSection(part, section, sections.indexOf(section) + 1, sections.size() > 1); if (transcriber.getCurrentColumn() > 0) transcriber.newLine(); transcriber.newLine(); } } /** Transcribes a section of music (possibly consisting of several staves * and including lyrics and chords). */ private void transcribeSection(final Part part, final Section section, final int sectionNumber, final boolean numbering) { final int staffCount = section.getStaffCount(); for (int staffIndex = 0; staffIndex < staffCount; staffIndex++) { final Staff staff = section.getStaff(staffIndex); if (transcriber.getCurrentColumn() > 0) transcriber.newLine(); if (numbering && staffIndex == 0) { transcriber.printString(new UpperNumber(sectionNumber)); transcriber.spaceOrNewLine(); transcriber.printString(new LowerRange(section.getFirstMeasureNumber(), section.getLastMeasureNumber())); transcriber.spaceOrNewLine(); } else { transcriber.indentTo(2); } int chordDirection = -1; if (staffCount == 1 && staff.containsHarmony()) { transcriber.printString(new MusicPart()); } else if (staffCount == 2) { if (staffIndex == 0) { transcriber.printString(new RightHandPart()); chordDirection = -1; } else if (staffIndex == 1) { transcriber.printString(new LeftHandPart()); chordDirection = 1; } } transcribeMusic(staff, chordDirection); if (staff.containsHarmony()) { if (transcriber.getCurrentColumn() > 0) transcriber.newLine(); transcribeHarmony(staff); } String lyricText = staff.getLyricText(); if (lyricText.length() > 0) transcribeLyrics(staff); } } private void transcribeMusic(Staff staff, final int chordDirection) { BrailleMeasure measure = new BrailleMeasure(transcriber); measure.setChordDirection(chordDirection); measure.setVoiceDirection(chordDirection); StartBar startBar = null; KeySignature currentSignature = staff.getKeySignature(staff.get(0).getMoment()); for (int staffElementIndex = 0; staffElementIndex < staff.size(); staffElementIndex++) { Event event = staff.get(staffElementIndex); if (event instanceof StartBar) { startBar = (StartBar)event; measure.setTimeSignature(startBar.getTimeSignature()); } else if (event instanceof KeyChange) { KeyChange kc = (KeyChange)event; if (!kc.getKeySignature().equals(currentSignature)) { currentSignature = kc.getKeySignature(); // TODO: FIXME transcriber.printString(new BrailleKeySignature(currentSignature)); transcriber.spaceOrNewLine(); } } else if (event instanceof EndBar) { EndBar rightBar = (EndBar)event; int charactersLeft = transcriber.getRemainingColumns(); if (charactersLeft <= 2) { transcriber.newLine(); charactersLeft = transcriber.getRemainingColumns(); } if (startBar != null) { if (startBar.getRepeatForward()) { transcriber.printString(new PostDottedDoubleBarSign()); } if (startBar.getEndingStart() > 0) { transcriber.printString(new AlternativeEnding(startBar.getEndingStart())); } } boolean lastLine = transcriber.isLastLine(); measure.process(); BrailleList head = measure.head(charactersLeft, lastLine); BrailleList tail = measure.tail(); if (head.length() <= tail.length() / 10) { transcriber.newLine(); charactersLeft = transcriber.getRemainingColumns(); head = measure.head(charactersLeft, lastLine); tail = measure.tail(); } transcriber.printString(head); if (tail.length() > 0) { transcriber.printString(new MusicHyphen()); transcriber.newLine(); transcriber.printString(tail); } if (rightBar.getRepeat()) transcriber.printString(new DottedDoubleBarSign()); else if (rightBar.getEndOfMusic()) transcriber.printString(new DoubleBarSign()); if (!rightBar.getEndOfMusic()) transcriber.spaceOrNewLine(); measure = new BrailleMeasure(transcriber, measure); measure.setChordDirection(chordDirection); measure.setVoiceDirection(chordDirection); } else { measure.add(event); } } } private void transcribeLyrics(Staff staff) { if (transcriber.getCurrentColumn() > 0) transcriber.newLine(); transcriber.printString(new TextPart()); Syllabic lastSyllabic = null; for (Event event: staff) { if (event instanceof Note) { Lyric lyric = ((Note)event).getLyric(); if (lyric != null) { final String text = lyric.getText(); if (text.length() <= transcriber.getRemainingColumns()) { transcriber.printString(new BrailleSyllable(text, (Note)event)); } else { if (lastSyllabic != Syllabic.SINGLE && lastSyllabic != Syllabic.END) { transcriber.printString(new Text("-")); } transcriber.newLine(); transcriber.printString(new BrailleSyllable(text, (Note)event)); } if (lyric.getSyllabic() == Syllabic.SINGLE || lyric.getSyllabic() == Syllabic.END) { transcriber.spaceOrNewLine(); } lastSyllabic = lyric.getSyllabic(); } } } } private void transcribeHarmony(Staff staff) { transcriber.printString(new HarmonyPart()); MeasureOfHarmonies measure = new MeasureOfHarmonies(); Iterator<Event> staffIterator = staff.iterator(); while (staffIterator.hasNext()) { Event event = staffIterator.next(); if (event instanceof Harmony) { measure.add(new HarmonyInfo((Harmony)event)); } else if (event instanceof EndBar) { EndBar endBar = (EndBar)event; if (measure.size() > 0) { measure.calculateDurations(((EndBar)event).getMoment()); boolean includeStems = !measure.isEvenRhythm(); Iterator<HarmonyInfo> iterator = measure.iterator(); boolean first = true; while (iterator.hasNext()) { HarmonyInfo current = iterator.next(); BrailleHarmony chord = new BrailleHarmony(current.getHarmony(), (includeStems && iterator.hasNext()), current.getDuration()); if (chord.length() <= transcriber.getRemainingColumns()) // TODO: FIXME transcriber.printString(chord); else { if (!first) transcriber.printString(new MusicHyphen()); transcriber.newLine(); transcriber.printString(chord); } first = false; } } else { transcriber.printString(new ArtificialWholeRest()); } measure.clear(); if (!endBar.getEndOfMusic()) transcriber.spaceOrNewLine(); else transcriber.printString(new DoubleBarSign()); } } } /** A simple container for storing calculated duration. */ private class HarmonyInfo { private Harmony harmony; HarmonyInfo(final Harmony harmony) { this.harmony = harmony; } Harmony getHarmony() { return harmony; } Fraction getMoment() { return harmony.getMoment(); } private Fraction duration = null; void setDuration(final Fraction duration) { this.duration = duration; } Fraction getDuration() { return duration; } } private class MeasureOfHarmonies extends ArrayList<HarmonyInfo> { /** Calculates the durations of the various Harmony elements contained * in this measure. * This should probably be done in the core MusicXML library instead. */ void calculateDurations(final Fraction measureEnd) { if (size() > 0) { Iterator<HarmonyInfo> iterator = iterator(); HarmonyInfo last = iterator.next(); while (iterator.hasNext()) { HarmonyInfo current = iterator.next(); last.setDuration(current.getMoment().subtract(last.getMoment())); last = current; } last.setDuration(measureEnd.subtract(last.getMoment())); } } /** Determines if all harmonies are of the same duration. * @return false if any duration differs from any other. */ boolean isEvenRhythm() { Iterator<HarmonyInfo> iterator = iterator(); if (iterator.hasNext()) { HarmonyInfo last = iterator.next(); while (iterator.hasNext()) { HarmonyInfo current = iterator.next(); if (!last.getDuration().equals(current.getDuration())) return false; last = current; } } return true; } } /** A container class for keeping sections of music apart. * This should probably be moved into package {@link freedots.music}. */ private class Section extends MusicList { private Part part; private StartBar firstMeasure = null, lastMeasure = null; Section(final Part part) { super(); this.part = part; } @Override public boolean add(Event newElement) { if (newElement instanceof StartBar) { StartBar startBar = (StartBar)newElement; if (firstMeasure == null) firstMeasure = startBar; else lastMeasure = startBar; } return super.add(newElement); } public int getFirstMeasureNumber() { return firstMeasure.getMeasureNumber(); } public int getLastMeasureNumber() { if (lastMeasure != null) return lastMeasure.getMeasureNumber(); return getFirstMeasureNumber(); } @Override public Staff getStaff(final int index) { Staff staff = super.getStaff(index); /* We need to populate key/clef/timeList with events from the past */ if (staff != null && !staff.isEmpty()) { final Fraction startMoment = staff.get(0).getMoment(); for (Event event: part.getMusicList()) { if (event.getMoment().compareTo(startMoment) < 0) { if (event instanceof GlobalKeyChange) { GlobalKeyChange globalKeyChange = (GlobalKeyChange)event; staff.keyList.put(globalKeyChange.getMoment(), globalKeyChange.getKeySignature()); } else if (event instanceof KeyChange) { KeyChange keyChange = (KeyChange)event; if (keyChange.getStaffNumber() == index) { staff.keyList.put(keyChange.getMoment(), keyChange.getKeySignature()); } } else if (event instanceof ClefChange) { ClefChange clefChange = (ClefChange)event; if (clefChange.getStaffNumber() == index) { staff.clefList.put(clefChange.getMoment(), clefChange.getClef()); } } } } } return staff; } } private List<Section> getSections(Part part) { List<Section> sections = new ArrayList<Section>(); Section currentSection = new Section(part); sections.add(currentSection); MusicList musicList = part.getMusicList(); int index = 0; int measureCount = 0; final boolean newSystemEndsSection = options.getNewSystemEndsSection() && score.encodingSupports("print", "new-system", true); while (true) { while (index < musicList.size()) { Event event = musicList.get(index++); currentSection.add(event); if (event instanceof EndBar) { measureCount++; break; } } if (index == musicList.size()) return sections; if (!(musicList.get(index) instanceof StartBar)) throw new RuntimeException(); StartBar startBar = (StartBar)musicList.get(index); if ((startBar.getStaffCount() != currentSection.getStaffCount()) || (newSystemEndsSection && startBar.getNewSystem()) || (measureCount == options.getMeasuresPerSection())) { currentSection = new Section(part); sections.add(currentSection); measureCount = 0; } } } }