/* -*- 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.braille; import java.util.Comparator; import java.util.Iterator; import freedots.Options; import freedots.music.AbstractPitch; import freedots.music.Accidental; import freedots.music.Fingering; import freedots.music.RhythmicElement; import freedots.music.Slur; import freedots.music.VoiceChord; import freedots.musicxml.Note; /** Represents several notes with same duration and start time, i.e., a chord. */ public class BrailleChord extends BrailleList { private final VoiceChord chord; private final BrailleNote topNote; /** Constructs the braille representation of the given chord. * @param chord is essentially a list of notes * @param comparator specifies how the list of notes should be sorted * @param lastPitch is used to decide if an {@link OctaveSign octave sign} * should be printed. It should be {@code null} if an octave sign * should be printed regardless of the previous pitch. */ public BrailleChord(final VoiceChord chord, final Comparator<RhythmicElement> comparator, final AbstractPitch lastPitch) { super(); this.chord = chord; final VoiceChord sorted = (VoiceChord)chord.clone(); java.util.Collections.sort(sorted, comparator); final Iterator<RhythmicElement> iterator = sorted.iterator(); assert iterator.hasNext(); final boolean allNotesTied = hasAllNotesTied(); final Note firstNote = (Note)iterator.next(); add(topNote = new BrailleNote(firstNote, lastPitch, !allNotesTied)); assert iterator.hasNext(); while (iterator.hasNext()) { add(new ChordStep((Note)iterator.next(), firstNote, !allNotesTied)); } if (allNotesTied) add(new ChordTieSign()); } @Override public String getDescription() { return "A chord."; } @Override public Object getScoreObject() { return chord; } /** Returns the pitch of the first note of this chord. * <p> * The first note is chord direction dependant, if the chord is written * from bottom to top note, this method returns the lowest pitch of the * chord. */ public AbstractPitch getNotePitch() { return topNote.getPitch(); } private boolean hasAllNotesTied() { final Iterator<RhythmicElement> iterator = chord.iterator(); while (iterator.hasNext()) { if (!((Note)iterator.next()).isTieStart()) return false; } return true; } /** Represents an interval in the braille chord. */ public static class ChordStep extends BrailleList { private final Note note; ChordStep(final Note note, final Note relativeTo, final boolean allowTieSign) { super(); this.note = note; final Options options = Options.getInstance(); final Accidental accidental = note.getAccidental(); if (accidental != null) add(new AccidentalSign(accidental)); AbstractPitch thisPitch = note.getPitch(); if (thisPitch == null) thisPitch = note.getUnpitched(); AbstractPitch otherPitch = relativeTo.getPitch(); if (otherPitch == null) otherPitch = relativeTo.getUnpitched(); int diatonicDiff = Math.abs(thisPitch.diatonicDifference(otherPitch)); if (diatonicDiff == 0 || diatonicDiff > 7) { add(new OctaveSign(thisPitch.getOctave())); while (diatonicDiff > 7) diatonicDiff -= 7; } add(new IntervalSign(diatonicDiff)); if (options.getShowFingering()) { final Fingering fingering = note.getFingering(); if (!fingering.getFingers().isEmpty()) add(new BrailleFingering(fingering)); } boolean addSingleSlur = false; boolean addDoubledSlur = false; for (Slur<Note> slur: note.getSlurs()) { if (slur.countArcs(note) >= options.getSlurDoublingThreshold()) { if (slur.isFirst(note)) { addDoubledSlur = true; addSingleSlur = false; } else if (slur.isLastArc(note)) { addDoubledSlur = false; addSingleSlur = true; } else { addDoubledSlur = false; addSingleSlur = false; } break; } else { if (!slur.lastNote(note)) { addDoubledSlur = false; addSingleSlur = true; break; } } } if (addDoubledSlur) { add(new SlurSign()); add(new SlurSign()); } else if (addSingleSlur) { add(new SlurSign()); } if (allowTieSign && note.isTieStart()) { add(new TieSign()); } } /** Returns the target note indicated by this interval. */ @Override public Object getScoreObject() { return note; } } /** Represents an interval. */ public static class IntervalSign extends Sign { private final int steps; IntervalSign(final int steps) { super(INTERVALS[steps]); this.steps = steps; } public String getDescription() { return "A " + INTERVAL_NAMES[steps] + " interval sign"; } private static final String[] INTERVALS = new String[] { braille(36), braille(34), braille(346), braille(3456), braille(35), braille(356), braille(25), braille(36) }; private static final String[] INTERVAL_NAMES = new String[] { "unison", "second", "third", "fourth", "fifth", "sixth", "seventh", "octave" }; } public static class ChordTieSign extends Sign { ChordTieSign() { super(braille(46, 14)); } public String getDescription() { return "Indicates that all notes of a chord are tied to the next chord"; } } }