gt:         AbstractPitch pitch = (AbstractPitch)note.getPitch();
context: /* -*- 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.Comparator;
  import java.util.List;
  import java.util.logging.Logger;
  import java.util.Set;
  
  import freedots.Braille;
  import freedots.braille.BrailleChord;
  import freedots.braille.BrailleDynamics;
  import freedots.braille.BrailleList;
  import freedots.braille.BrailleNote;
  import freedots.braille.BrailleSequence;
  import freedots.braille.BrailleWords;
  import freedots.braille.FullMeasureInAccordSign;
  import freedots.braille.PartMeasureInAccordSign;
  import freedots.braille.PartMeasureInAccordDivisionSign;
  import freedots.braille.RestSign;
  import freedots.braille.SimileSign;
  import freedots.braille.Text;
  import freedots.music.AbstractPitch;
  import freedots.music.AugmentedPowerOfTwo;
  import freedots.music.Event;
  import freedots.music.MusicList;
  import freedots.music.RhythmicElement;
  import freedots.music.TimeSignature;
  import freedots.music.Voice;
  import freedots.music.VoiceChord;
  import freedots.musicxml.Direction;
  import freedots.musicxml.Note;
  
  class BrailleMeasure {
    private static final Logger LOG = Logger.getLogger(BrailleMeasure.class.getName());
  
    private BrailleMeasure previous = null;
    private MusicList events = new MusicList();
    public MusicList getEvents() { return events; }
    private AbstractPitch finalPitch = null;
    private TimeSignature timeSignature = null;
    private int voiceDirection = -1; /* By default, top to bottom */
    private Comparator<RhythmicElement> chordComparator =
      new VoiceChord.DescendingComparator();
    private final Transcriber transcriber;
    BrailleMeasure(final Transcriber transcriber) {
      this.transcriber = transcriber;
    }
    BrailleMeasure(final Transcriber transcriber, final BrailleMeasure previous) {
      this(transcriber);
      this.previous = previous;
    }
  
    public void add(Event event) { events.add(event); }
  
    public void setTimeSignature(TimeSignature timeSignature) {
      this.timeSignature = timeSignature;
    }
    public void setChordDirection(final int direction) {
      if (direction < 0) chordComparator = new VoiceChord.DescendingComparator();
      else chordComparator = new VoiceChord.AscendingComparator();
    }
    public void setVoiceDirection(int direction) {
      voiceDirection = direction;
    }
    public void unlinkPrevious() { previous = null; }
  
    private List<Object> brailleVoices = new ArrayList<Object>();
    private boolean fullSimile = false;
  
    /**
     * Detect simile and find voice overlaps for part measure in-accord.
     * <p>
     * This code is very hairy and definitely needs an overhaul.
     * It is also incomplete as it does not handle individual voices repeating.
     */
     // TODO: Find a data structure to represent part/full measure in-accord
    public void process() {
      if (previous != null) {
        if (previous.getEvents().equalsIgnoreOffset(this.events)) {
          fullSimile = true;
        }
      }
  
      if (!fullSimile) {
        brailleVoices = new ArrayList<Object>();
  
        List<Voice> voices = events.getVoices(voiceDirection);
        FullMeasureInAccord fmia = new FullMeasureInAccord();
        PartMeasureInAccord pmia = new PartMeasureInAccord();
  
        while (voices.size() > 0) {
          Voice voice = voices.get(0);
          boolean foundOverlap = false;
          int headLength = 0;
  
          for (int j = 1; j < voices.size(); j++) {
            int equalsAtBeginning = voice.countEqualsAtBeginning(voices.get(j));
            if (equalsAtBeginning > 0) {
              headLength = equalsAtBeginning;
              MusicList head = new MusicList();
              for (int k = 0; k < equalsAtBeginning; k++) {
                head.add(voice.get(k));
                voices.get(j).remove(0);
              }
              pmia.setHead(head);
              pmia.addPart(voice);
              pmia.addPart(voices.get(j));
              voices.remove(voices.get(j));
              foundOverlap = true;
            } else if (foundOverlap && equalsAtBeginning == headLength) {
              for (int k = 0; k < equalsAtBeginning; k++) {
                voices.get(j).remove(k);
              }
              pmia.addPart(voices.get(j));
              voices.remove(voices.get(j));
            }
          }
          
          if (foundOverlap) {
            for (int k = 0; k < headLength; k++) {
              voice.remove(0);
            }
          } else {
            fmia.addPart(voice);
          }
  
          voices.remove(voice);
        }
        if (fmia.getParts().size() > 0) brailleVoices.add(fmia);
        if (pmia.getParts().size() > 0) brailleVoices.add(pmia);
      }
    }
    public AbstractPitch getFinalPitch() { return finalPitch; }
  
    private BrailleList tail;
    public BrailleList tail() { return tail; }
  
    class State {
      private int width;
      private AbstractPitch lastPitch;
      private BrailleList head = new BrailleList();
      private BrailleList tail = new BrailleList();
      private boolean hyphenated = false;
  
      State(final int width, final AbstractPitch lastPitch) {
        this.width = width;
        this.lastPitch = lastPitch;
      }
      void append(String braille) {
        append(new Text(braille));
      }
      void append(BrailleSequence braille) {
        if (head.length() + braille.length() < width && !hyphenated) {
          head.add(braille);
        } else {
          hyphenated = true;
          tail.add(braille);
        }
      }
      AbstractPitch getLastPitch() { return lastPitch; }
      void setLastPitch(AbstractPitch lastPitch) { this.lastPitch = lastPitch; }
  
      BrailleList getHead() { return head; }
      BrailleList getTail() { return tail; }
    }
  
    /** Actually performs the task of transcribing events given the amount
     *  of remaining characters on this line.
     * @see #tail
     */
    public BrailleList head(int width, boolean lastLine) {
      State state = new State(width,
                              previous != null? previous.getFinalPitch(): null);
  
      if (fullSimile) {
        state.append(new SimileSign());
      } else {
        for (int i = 0; i < brailleVoices.size(); i++) {
          if (brailleVoices.get(i) instanceof PartMeasureInAccord) {
            final PartMeasureInAccord pmia = (PartMeasureInAccord)brailleVoices.get(i);
            if (i > 0) {
              state.append(new FullMeasureInAccordSign());
  
              /* The octave mark must be shown for
               * the first note after an in-accord.
               ************************************/
              state.setLastPitch(null);
            }
            MusicList pmiaHead = pmia.getHead();
            if (pmiaHead.size() > 0) {
              printNoteList(pmiaHead, state, null);
              state.append(new PartMeasureInAccordSign());
            }
            for (int p = 0; p < pmia.getParts().size(); p++) {
              printNoteList(pmia.getParts().get(p), state, null);
              if (p < pmia.getParts().size() - 1) {
                state.append(new PartMeasureInAccordDivisionSign());
  
                /* The octave mark must be shown for
                 * the first note after an in-accord.
                 ************************************/
                state.setLastPitch(null);
              }
            }
            MusicList pmiaTail = pmia.getTail();
            if (pmiaTail.size() > 0) {
              state.append(new PartMeasureInAccordSign());
              printNoteList(pmiaTail, state, null);
            }
          } else if (brailleVoices.get(i) instanceof FullMeasureInAccord) {
            FullMeasureInAccord fmia = (FullMeasureInAccord)brailleVoices.get(i);
            for (int p = 0; p < fmia.getParts().size(); p++) {
              Object splitPoint = null;
              ValueInterpreter valueInterpreter =
                new ValueInterpreter(fmia.getParts().get(p), timeSignature);
              Set<ValueInterpreter.Interpretation>
                interpretations = valueInterpreter.getInterpretations();
              if (interpretations.size() > 1) {
                splitPoint = valueInterpreter.getSplitPoint();
                if (splitPoint == null) {
                  StringBuilder sb = new StringBuilder();
  
                  sb.append("Unimplemented: "
                            + interpretations.size()
                            + " possible interpretations:\n");
                  for (ValueInterpreter.Interpretation
                         interpretation:interpretations) {
                    sb.append((interpretation.isCorrect()?" * ":"   ")
                              + interpretation.toString() + "\n");
                  }
                  LOG.warning(sb.toString());
                }
              }
              printNoteList(fmia.getParts().get(p), state, splitPoint);
              if (p < fmia.getParts().size() - 1) {
                state.append(new FullMeasureInAccordSign());
  
                /* The octave mark must be shown for
                 * the first note after an in-accord.
                 ************************************/
                state.setLastPitch(null);
              }
            }
          }
        }
  
        if (brailleVoices.size() == 0) {
          state.append(new RestSign(new AugmentedPowerOfTwo(AugmentedPowerOfTwo.BREVE,
                                                            0) {
                                      public String getDescription() {
                                        return "A whole measure rest";
                                      }
                                    }));
        }
  
        /* 5-12. The octave mark must be shown for the first note after an
         * in-accord and _at the beginning of the next measure_, whether or not
         * that measure contains an in-accord.
         ***********************************************************************/
        if (brailleVoices.size() == 1
            && !((FullMeasureInAccord)brailleVoices.get(0)).isInAccord())
          finalPitch = state.getLastPitch();
      }
      tail = state.getTail();
      return state.getHead();
    }
    void printNoteList(MusicList musicList, State state, Object splitPoint) {
      for (Event element:musicList) {
        if (element instanceof Note) {
          Note note = (Note)element;
          if (splitPoint != null && splitPoint == note) {
            state.append(Braille.valueDistinction.toString());
          }
          BrailleNote brailleNote = new BrailleNote(note, state.getLastPitch());
  