/* -*- 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import freedots.math.Fraction;
import freedots.music.AugmentedPowerOfTwo;
import freedots.music.Event;
import freedots.music.MusicList;
import freedots.music.TimeSignature;
import freedots.musicxml.Note;
/**
* In braille music there are only four different types of note values.
* The full range of whole to 128th notes is covered by reusing the same
* types twice, a whole can also mean a 16th and so on.
*
* However, this only works out if there is no ambiguity in the measure
* to be printed.
*
* Class <code>ValueInterpreter</code> calculates the possible interpretations
* of a list of notes relative to a given time signature.
*/
class ValueInterpreter {
private Set<Interpretation> interpretations = new HashSet<Interpretation>();
ValueInterpreter(final MusicList music, final TimeSignature timeSignature) {
assert timeSignature != null;
List<Set<RhythmicPossibility>>
candidates = new ArrayList<Set<RhythmicPossibility>>();
for (Event event : music) {
if (event instanceof Note) {
Note note = (Note)event;
if (!note.isGrace()) {
Set<RhythmicPossibility>
candidate = new HashSet<RhythmicPossibility>(2);
candidate.add(new Large(note));
candidate.add(new Small(note));
candidates.add(candidate);
}
} /* FIXME: Handle chords as well */
}
if (!candidates.isEmpty()) {
interpretations = findInterpretations(candidates, timeSignature);
}
}
public Set<Interpretation> getInterpretations() { return interpretations; }
/** Determines if the simple distrinction of values sign can be used to
* resolve a note value ambiguity.
*
* @return the note object before which a value distinction sign should be
* inserted, or {@code null} if resolution of the value ambiguity is
* more complicated.
* @see freedots.Braille#valueDistinction
*/
public Object getSplitPoint() {
for (Interpretation interpretation: interpretations) {
if (interpretation.isCorrect()) {
int begin = 0;
int end = interpretation.size() - 1;
if (end > begin) {
boolean beginLarge = isWholeToEighth(interpretation.get(begin));
boolean endLarge = isWholeToEighth(interpretation.get(end));
if ((beginLarge && !endLarge) || (!beginLarge && endLarge)) {
int leftIndex = begin;
while (isWholeToEighth(interpretation.get(leftIndex)) == beginLarge)
leftIndex++;
int rightIndex = end;
while (isWholeToEighth(interpretation.get(rightIndex)) == endLarge)
rightIndex--;
if (rightIndex == leftIndex - 1) {
return interpretation.get(leftIndex).getNote();
}
}
}
}
}
return null;
}
private Set<Interpretation>
findInterpretations(
final List<Set<RhythmicPossibility>> candidates, final Fraction remaining
) {
Set<Interpretation> result = new HashSet<Interpretation>();
if (candidates.size() == 1) {
for (RhythmicPossibility rhythmicPossibility:candidates.get(0)) {
if (rhythmicPossibility.equals(remaining)) {
Interpretation interpretation = new Interpretation();
interpretation.add(rhythmicPossibility);
result.add(interpretation);
}
}
} else {
Set<RhythmicPossibility> head = candidates.get(0);
List<Set<RhythmicPossibility>>
tail = candidates.subList(1, candidates.size());
for (RhythmicPossibility rhythmicPossibility: head) {
if (rhythmicPossibility.compareTo(remaining) <= 0) {
for (Interpretation interpretation
: findInterpretations(tail, remaining
.subtract(rhythmicPossibility))) {
interpretation.add(0, rhythmicPossibility);
result.add(interpretation);
}
}
}
}
return result;
}
private static boolean isWholeToEighth(RhythmicPossibility item) {
return item.getPower() <= 0
&& item.getPower() >= AugmentedPowerOfTwo.QUAVER.getPower();
}
@SuppressWarnings("serial")
class Interpretation extends ArrayList<RhythmicPossibility> {
Interpretation() { super(); }
public boolean isCorrect() {
for (RhythmicPossibility rhythmicPossibility: this)
if (rhythmicPossibility.isAltered()) return false;
return true;
}
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator<RhythmicPossibility> iter = iterator();
while (iter.hasNext()) {
sb.append(iter.next().toString());
if (iter.hasNext()) sb.append(" ");
}
return sb.toString();
}
}
abstract class RhythmicPossibility extends AugmentedPowerOfTwo {
private Note note;
RhythmicPossibility(final Note note) {
super(note.getAugmentedFraction(), note.getAugmentedFraction().dots(),
note.getAugmentedFraction().normalNotes(),
note.getAugmentedFraction().actualNotes());
this.note = note;
}
boolean isAltered() {
return compareTo(note.getAugmentedFraction()) != 0;
}
Note getNote() { return note; }
}
class Large extends RhythmicPossibility {
Large(final Note note) {
super(note);
if (getPower() < QUAVER.getPower()) setPower(getPower()+4);
}
}
class Small extends RhythmicPossibility {
Small(final Note note) {
super(note);
if (getPower() > SEMIQUAVER.getPower()) setPower(getPower()-4);
}
}
}