/* * JFugue, an Application Programming Interface (API) for Music Programming * http://www.jfugue.org * * Copyright (C) 2003-2013 David Koelle * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jfugue.integration; import java.util.StringTokenizer; import org.jfugue.midi.MidiDictionary; import org.jfugue.parser.ParserListenerAdapter; import org.jfugue.theory.Chord; import org.jfugue.theory.Note; /** * This class listens to events from the MusicString parser. In response to this * events, a Lilypond string is produced. The Lilypond string is produced with * relative octave notation. * * @author Hans Beemsterboer */ public class LilyPondParserListener extends ParserListenerAdapter { boolean closeStaff = false; boolean handleChord = false; private boolean closeChord = false; private boolean handlePolyphony = false; private boolean closePolyphony = false; private NoteWindow noteWindow = new NoteWindow(); private StringBuffer lyString = new StringBuffer(" "); private boolean isDebug = false; private void log(String message) { if (isDebug) { System.out.println(message); } } @Override public void onTrackChanged(byte track) { log("Track change: " + track); if (lyString.length() > 1) { save(true); handleLastNote(); noteWindow.emptyAll(); lyString.append("}\n"); } else { lyString = new StringBuffer(); } closeStaff = true; lyString.append("\\new Staff { "); } @Override public void onInstrumentParsed(byte instrument) { log("Instrument change"); String id = Byte.toString(instrument); String instrument2 = "\\set Staff.instrumentName = \"" + MidiDictionary.INSTRUMENT_BYTE_TO_STRING.get(Byte.parseByte(id)) + "\" "; lyString.append(instrument2); } @Override public void onNoteParsed(Note note2) { noteWindow.addNote(note2); if (note2.isFirstNote()) { handleChord = false; if (handlePolyphony) { handlePolyphony = false; } } else { if (note2.isHarmonicNote()) { handleChord = true; handlePolyphony = false; } else { handleChord = false; handlePolyphony = true; } } if (noteWindow.getSecondPreviousNote() != null) { save(false); } } private void printChord(Chord chord) { log("Chord parsed: rootnote = " + chord.getRoot().getValue() + " intervals = " + chord.getIntervals().toString() + " duration = " + chord.getRoot().getDuration() + " attack = " + chord.getRoot().getOnVelocity() + " decay = " + chord.getRoot().getOffVelocity()); log(chord.getPatternWithNotes().toString()); for (Note note : chord.getNotes()) { printNote(note); } } private void printNote(Note note) { log(note.toDebugString()); } @Override public void onChordParsed(Chord chord) { printChord(chord); noteWindow.addChordOctave(chord.getRoot()); String musicString = chord.getPatternWithNotes().toString(); String duration = LilyPondNoteDurationHelper.getDuration2(Double.toString(chord.getRoot().getDuration())); parallelNoteEvent(musicString, duration, chord.getRoot().originalString); lyString.append(">"); lyString.append(duration); lyString.append(" "); } private void parallelNoteEvent(String musicString, String duration, String rootNote) { boolean isFirst = true; lyString.append("<"); StringTokenizer tokenizer = new StringTokenizer(musicString, "+"); while (tokenizer.hasMoreElements()) { String note = tokenizer.nextToken(); String firstLetter = note.substring(0, 1).toLowerCase(); lyString.append(firstLetter); if (isFirst) { int octaveChange = noteWindow.getOctaveChange(firstLetter.charAt(0)); if (octaveChange > 0) { for (int i = 0; i < octaveChange; i++) { log("Add octave change"); lyString.append("'"); } } if (octaveChange < 0) { for (int i = 0; i > octaveChange; i--) { log("Add octave change"); lyString.append(","); } } isFirst = false; noteWindow.setLastNote(firstLetter.charAt(0)); } if (tokenizer.hasMoreElements()) { lyString.append(" "); } } } public String getLyString() { noteWindow.print(); save(true); handleLastNote(); lyString.append("}"); return lyString.toString(); } private void handleLastNote() { log("Current note: " + noteWindow.getCurrentNote()); if (noteWindow.getCurrentNoteLy().length() > 0 && !closeChord) { lyString.append(noteWindow.getCurrentNoteLy()); } if (noteWindow.getCurrentNoteDuration() != null && !closeChord) { lyString.append(noteWindow.getCurrentNoteDuration()); } if (noteWindow.getCurrentNote() != null && !closeChord) { lyString.append(" "); } if (!lyString.toString().contains("new Staff")) { closeStaff = true; StringBuffer lyBuffer = new StringBuffer(); lyBuffer.append("\\new Staff {"); lyBuffer.append(lyString); lyString = lyBuffer; } if (closeChord) { closeChord = false; lyString.append(noteWindow.getCurrentNoteLy()); lyString.append(">"); lyString.append(noteWindow.getCurrentNoteDuration()); lyString.append(" "); } if (closePolyphony) { lyString.append("} >> "); } } private void save(boolean isLastSave) { log("==> Save called, lyString before: " + lyString + ", last save: " + isLastSave); noteWindow.print(); if (!isLastSave) { log("secondPreviousNote: " + noteWindow.getSecondPreviousNote().originalString); if (noteWindow.getSecondPreviousNote().isFirstNote() && noteWindow.getPreviousNote().isHarmonicNote() && !noteWindow.getCurrentNote().isMelodicNote()) { lyString.append("<"); lyString.append(noteWindow.getSecondPreviousNoteLy()); lyString.append(" "); closeChord = true; } else if (noteWindow.getSecondPreviousNote().isFirstNote() && handlePolyphony) { if (closePolyphony) { closePolyphony = false; lyString.append("} >>"); } lyString.append("<< { "); closePolyphony = true; lyString.append(noteWindow.getSecondPreviousNoteLy()); lyString.append(noteWindow.getSecondPreviousNoteDuration()); if (noteWindow.getPreviousNote().isHarmonicNote()) { lyString.append(" } \\\\ { "); } } else if (noteWindow.getSecondPreviousNote().isHarmonicNote() && noteWindow.getPreviousNote().isFirstNote()) { // close parallel lyString.append(noteWindow.getSecondPreviousNoteLy()); lyString.append(">"); lyString.append(noteWindow.getPreviousNoteDuration()); lyString.append(" "); closeChord = false; } else { lyString.append(noteWindow.getSecondPreviousNoteLy()); if (!noteWindow.getSecondPreviousNote().isHarmonicNote()) { lyString.append(noteWindow.getSecondPreviousNoteDuration()); } lyString.append(" "); } if (noteWindow.getSecondPreviousNote().isStartOfTie()) { lyString.append("~ "); } } if (!isLastSave && noteWindow.getPreviousNote() != null && noteWindow.getPreviousNote().isFirstNote() && noteWindow.getCurrentNote().isHarmonicNote()) { log("We don't know yet"); return; } if (noteWindow.getSecondPreviousNote() != null && !noteWindow.getCurrentNote().isHarmonicNote() && (noteWindow.getPreviousNote() != null || noteWindow.getCurrentNote() != null)) { lyString.append(noteWindow.getPreviousNoteLy()); } if (noteWindow.getPreviousNote() != null) { log("previousnote: " + noteWindow.getPreviousNote().originalString); if (isLastSave && noteWindow.getPreviousNote().isFirstNote() && noteWindow.getCurrentNote().isHarmonicNote()) { lyString.append("<"); lyString.append(noteWindow.getPreviousNoteLy()); lyString.append(" "); closeChord = true; } if (noteWindow.getPreviousNote().isHarmonicNote() && noteWindow.getCurrentNote().isHarmonicNote()) { lyString.append(noteWindow.getPreviousNoteLy()); lyString.append(" "); } if (noteWindow.getPreviousNote().isHarmonicNote() && noteWindow.getCurrentNote().isFirstNote()) { // close parallel lyString.append(">"); lyString.append(noteWindow.getPreviousNoteDuration()); lyString.append(" "); closeChord = false; } else if (!noteWindow.getCurrentNote().isHarmonicNote()) { if (noteWindow.getSecondPreviousNote() == null) { lyString.append(noteWindow.getPreviousNoteLy()); } lyString.append(noteWindow.getPreviousNoteDuration()); lyString.append(" "); } if (noteWindow.getPreviousNote().isStartOfTie()) { lyString.append("~ "); } } log("Current note not handled: " + noteWindow.getCurrentNoteLy()); log("==> Save called, lyString after: " + lyString); noteWindow.empty(); } } class NoteWindow { private Note currentNote = null; private Note previousNote = null; private Note secondPreviousNote = null; private StringBuffer currentNoteLy = new StringBuffer(); private String currentNoteDuration = null; private String previousNoteLy = null; private String previousNoteDuration = null; private String secondPreviousNoteLy = null; private String secondPreviousNoteDuration = null; private int currentOctave = 4; private char lastNote = 'c'; private int lastOctave = 4; boolean isDebug = false; void log(String message) { if (isDebug) { System.out.println(message); } } void empty() { secondPreviousNote = null; previousNote = null; } void emptyAll() { secondPreviousNote = null; previousNote = null; currentNote = null; currentNoteLy = new StringBuffer(); currentNoteDuration = null; } void addNote(Note note) { log(note.toDebugString()); secondPreviousNote = previousNote; secondPreviousNoteLy = previousNoteLy; previousNote = currentNote; previousNoteLy = currentNoteLy.toString(); currentNoteLy = new StringBuffer(); currentNote = note; if (!note.isRest()) { String firstLetter = note.originalString.substring(0, 1).toLowerCase(); currentNoteLy.append(firstLetter); currentOctave = note.getOctave(); if (note.originalString.length() > 1) { String secondLetter = note.originalString.substring(1, 2).toLowerCase(); log("Second letter: " + secondLetter); if (secondLetter.equals("b")) { currentNoteLy.append("es"); } else if (secondLetter.equals("#")) { currentNoteLy.append("is"); } } int octaveChange = getOctaveChange(firstLetter.charAt(0)); if (octaveChange > 0) { for (int i = 0; i < octaveChange; i++) { log("Add octave change"); currentNoteLy.append("'"); } } if (octaveChange < 0) { for (int i = 0; i > octaveChange; i--) { log("Add octave change"); currentNoteLy.append(","); } } lastNote = firstLetter.charAt(0); } else { currentNoteLy.append("r"); } secondPreviousNoteDuration = previousNoteDuration; previousNoteDuration = currentNoteDuration; currentNoteDuration = LilyPondNoteDurationHelper.getDuration2(Double.toString(note.getDuration())); } void addChordOctave(Note rootNote) { log("chord rootnote: " + rootNote.toDebugString()); currentOctave = rootNote.getOctave(); log("Chord octave: " + currentOctave); } /** * This method determines octave changes that are relative to the previous * note. * <ul> * <li>JFugue: [ c4 d4 e4 f4 g4 a4 b4 ] c5 d5 e5 f5 g5 a5 b5 | c6</li> * <li>Lilypond: c d e f [g' a b c d e f ] g' a b c</li> * </ul> * * @param lyString */ int getOctaveChange(char currentNoteChar) { int octaveChange = currentOctave - lastOctave; log("Current octave: " + currentOctave + ", last octave: " + lastOctave); int lilypondChange = 0; if (previousNote != null && !previousNote.isRest()) { lilypondChange = lilypondRelativeDirection(previousNote.originalString.toLowerCase().charAt(0), currentNoteChar); } else { lilypondChange = lilypondRelativeDirection(lastNote, currentNoteChar); } log("LilyPond change: " + lilypondChange); int jfugueChange = 0; if (previousNote != null && !previousNote.isRest()) { jfugueChange = jfugueOctaveChange(previousNote.originalString.toLowerCase().charAt(0), currentNoteChar, lilypondChange); } else { jfugueChange = jfugueOctaveChange(lastNote, currentNoteChar, lilypondChange); } octaveChange += jfugueChange; lastOctave = currentOctave; return octaveChange; } private int lilypondRelativeDirection(char firstNote, char secondNote) { char curChar = firstNote; if (firstNote == secondNote) { return 0; } for (int i = 1; i < 4; i++) { curChar++; if (curChar > 'g') { curChar = 'a'; } if (curChar == secondNote) { return i; } } curChar = firstNote; for (int i = 1; i < 4; i++) { curChar--; if (curChar < 'a') { curChar = 'g'; } if (curChar == secondNote) { return -i; } } return 0; } private int jfugueOctaveChange(char firstNote, char secondNote, int lilypondDirection) { log("jfugue change: firstNote: " + firstNote + ", secondNote: " + secondNote); char curChar = firstNote; int steps = Math.abs(lilypondDirection) + 1; for (int i = 1; Math.abs(i) < steps; i += 1) { if (lilypondDirection > 0) { curChar++; if (curChar > 'g') { curChar = 'a'; } if ((firstNote < 'c' || firstNote >= 'g') && curChar > 'b') { return -1; } } else { curChar--; if (curChar < 'a') { curChar = 'g'; } if ((firstNote >= 'c' && firstNote < 'f') && curChar < 'c') { return 1; } } } return 0; } void print() { log("Note window: " + secondPreviousNoteLy + "&" + secondPreviousNoteDuration + "&" + previousNoteLy + "&" + previousNoteDuration + "&" + currentNoteLy + "&" + currentNoteDuration); } Note getCurrentNote() { return currentNote; } Note getPreviousNote() { return previousNote; } Note getSecondPreviousNote() { return secondPreviousNote; } public StringBuffer getCurrentNoteLy() { return currentNoteLy; } public String getPreviousNoteLy() { return previousNoteLy; } public String getSecondPreviousNoteLy() { return secondPreviousNoteLy; } public String getCurrentNoteDuration() { return currentNoteDuration; } public String getPreviousNoteDuration() { return previousNoteDuration; } public String getSecondPreviousNoteDuration() { return secondPreviousNoteDuration; } public int getCurrentOctave() { return currentOctave; } public void setCurrentOctave(int currentOctave) { this.currentOctave = currentOctave; } public char getLastNote() { return lastNote; } public void setLastNote(char lastNote) { this.lastNote = lastNote; } public int getLastOctave() { return lastOctave; } public void setLastOctave(int lastOctave) { this.lastOctave = lastOctave; } } class LilyPondNoteDurationHelper { static String getDuration2(String duration) { String durationLy = "4"; double durationVal = Double.parseDouble(duration); if (durationVal == 0.0625) { durationLy = "16"; } else if (durationVal == 0.125) { durationLy = "8"; } else if (durationVal == 0.25) { durationLy = "4"; } else if (durationVal == 0.375) { durationLy = "4."; } else if (durationVal == 0.5) { durationLy = "2"; } else if (durationVal == 0.75) { durationLy = "2."; } else if (durationVal == 1.0) { durationLy = "1"; } else if (durationVal == 2.0) { durationLy = "\\breve"; } else if (durationVal == 3.0) { durationLy = "\\breve."; } else if (durationVal == 4.0) { durationLy = "\\longa"; } else { durationLy = "4"; } return durationLy; } }