/* -*- 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.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import freedots.Options;
import freedots.Braille;
import freedots.braille.Sign;
import freedots.braille.BrailleEncoding;
import freedots.braille.BrailleList;
import freedots.braille.BrailleSequence;
import freedots.braille.NewLine;
import freedots.braille.Space;
import freedots.braille.Text;
import freedots.braille.UpperNumber;
import freedots.musicxml.Direction;
import freedots.musicxml.Score;
/**
* Transcribes a {@link Score} to braille music code.
*/
public final class Transcriber {
private Score score;
/** The score which was used to generate the current transcription.
* @return the score object
*/
public Score getScore() { return score; }
/** Transcribe the given score to braille.
* @param score is the abstract representation of the music to transcribe
*/
public void setScore(final Score score) {
this.score = score;
clear();
transcribe();
}
private Options options;
Options getOptions() { return options; }
private BrailleList strings;
private int characterCount;
private int lineCount;
private int pageNumber;
boolean isLastLine() {
return lineCount == (options.getPageHeight() - 1);
}
int getCurrentColumn() { return characterCount; }
int getRemainingColumns() {
final int charsUsed = strings.lengthSince(NewLine.class);
return options.getPageWidth() - (charsUsed != -1? charsUsed: strings.length());
}
/** Find the braille sign at a given character index.
* @param index is the character index
* @return null if index is out of range
*/
public Sign getSignAtIndex(final int index) {
return strings.getSignAtIndex(index);
}
/** Find Object responsible for character at specified index.
*
* @param characterIndex indicates the position relative to {@link #toString}
*
* @return the object responsible for the character at index, or {@code null}
* if none was found / specified.
*/
public Object getScoreObjectAtIndex(final int characterIndex) {
return strings.getScoreObjectAtIndex(characterIndex);
}
/** Find the starting index of the character sequence for Object.
* <p>
* This is the reverse of {@link #getScoreObjectAtIndex}.
* @param object the score object to search in the transcribed text.
* @return the index of the first character that was generated due to object
*/
public int getIndexOfScoreObject(final Object object) {
return strings.getIndexOfScoreObject(object);
}
/** Returns the <code>char</code> value at the specified index.
* @param index the index of the <code>char</code> value to be returned.
* @return the <code>char</code> value at the specified index.
*/
public char charAt(final int index) { return strings.charAt(index); }
private Strategy strategy;
/** Construct a new transcriber object.
* @param options is used to pass in command-line or GUI options
*/
public Transcriber(final Options options) {
this.options = options;
clear();
}
private void clear() {
strings = new BrailleList();
characterCount = 0;
lineCount = 0;
pageNumber = 1;
alreadyPrintedDirections = new ArrayList<Direction>();
}
private void transcribe() {
String workNumber = score.getWorkNumber();
String workTitle = score.getWorkTitle();
String movementTitle = score.getMovementTitle();
String composer = score.getComposer();
String lyricist = score.getLyricist();
boolean headerAvailable = false;
if (isNonEmpty(workNumber)) {
printCenteredLine(new Text(workNumber) {
@Override public String getDescription() {
return "Work number";
}
});
headerAvailable = true;
}
if (isNonEmpty(workTitle)) {
printCenteredLine(new Text(workTitle) {
@Override public String getDescription() {
return "The work title";
}
});
headerAvailable = true;
}
if (isNonEmpty(movementTitle)) {
printCenteredLine(new Text(movementTitle) {
@Override public String getDescription() {
return "The movement title";
}
});
headerAvailable = true;
}
if (isNonEmpty(composer) && isNonEmpty(lyricist)) {
BrailleList line = new BrailleList();
line.add(new Text("Music by"));
line.add(new Space());
line.add(new ComposerName(composer));
printCenteredLine(line);
line = new BrailleList();
line.add(new Text("Lyrics by"));
line.add(new Space());
line.add(new Text(lyricist) {
@Override public String getDescription() {
return "The name of the lyricist";
}
});
printCenteredLine(line);
headerAvailable = true;
} else if (isNonEmpty(composer)) {
printCenteredLine(new ComposerName(composer));
headerAvailable = true;
}
if (headerAvailable) newLine();
switch (options.getMethod()) {
case SectionBySection:
strategy = new SectionBySection();
break;
case BarOverBar:
strategy = new BarOverBar();
break;
default:
throw new AssertionError(options.getMethod());
}
strategy.transcribe(this);
}
private static boolean isNonEmpty(final String string) {
return string != null && string.length() > 0;
}
void printString(final BrailleSequence braille) {
strings.add(braille);
characterCount += braille.length();
}
void printLine(final String text) {
printString(new Text(text));
newLine();
}
void printCenteredLine(final BrailleSequence text) {
int skip = (getRemainingColumns() - text.length()) / 2;
if (skip > 0) {
StringBuilder skipString = new StringBuilder();
for (int i = 0; i < skip; i++) skipString.append(" ");
printString(new Text(skipString.toString()) {
public String getDescription() {
return "Padding for centered line";
}
});
}
printString(text);
newLine();
}
void spaceOrNewLine() {
if (getRemainingColumns() > 1) printString(new Space());
else newLine();
}
void newLine() {
strings.add(new NewLine());
characterCount = 0;
lineCount += 1;
if (lineCount == options.getPageHeight()) {
BrailleSequence pageIndicator = new UpperNumber(pageNumber++);
indentTo(options.getPageWidth() - pageIndicator.length());
strings.add(pageIndicator);
strings.add(new NewLine());
characterCount = 0;
lineCount = 0;
}
}
void indentTo(final int column) {
int difference = column - characterCount;
while (difference > 0) {
strings.add(new Space());
characterCount += 1;
difference -= 1;
}
}
/** Gets the transcription result as a hierarchy of objects.
* <p>
* This could be used to extract metadata about the signs used during
* construction of a presentation.
*
* @return the hierarchial transcription result as an object structure
*/
public BrailleList getSigns() {
return strings;
}
/** Converts transcription result to a string.
*
* @param encoding specifies the braille encoding to use.
* @return the string representation of the given encoding
*/
public String toString(final BrailleEncoding encoding) {
final String string = strings.toString();
switch (encoding) {
case NorthAmericanBrailleComputerCode: {
final StringBuilder stringBuilder = new StringBuilder();
final CharacterIterator iterator = new StringCharacterIterator(string);
for(char c = iterator.first(); c != CharacterIterator.DONE;
c = iterator.next()) {
final Character ch = new Character(c);
if (Braille.BRF_TABLE.containsKey(ch)) {
final Character mapped = Braille.BRF_TABLE.get(ch);
stringBuilder.append(mapped);
} else {
stringBuilder.append(c);
}
}
return stringBuilder.toString();
}
case HTML: {
try {
return HTMLOutput.convert(strings);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
case UnicodeBraille:
default:
return string;
}
}
/** Converts transcription result to a plain string.
*
* @return result string of last transcription
*/
@Override public String toString() {
return toString(BrailleEncoding.UnicodeBraille);
}
private List<Direction> alreadyPrintedDirections;
List<Direction> getAlreadyPrintedDirections() {
return alreadyPrintedDirections;
}
/** Contains the name of the composer.
*/
public static class ComposerName extends Text {
ComposerName(final String name) { super(name); }
@Override public String getDescription() {
return "The name of the composer of this piece";
}
}
}