/*
* JFugue, an Application Programming Interface (API) for Music Programming
* http://www.jfugue.org
*
* Copyright (C) 2003-2014 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.staccato;
import org.jfugue.parser.ParserException;
import org.jfugue.provider.ChordProviderFactory;
import org.jfugue.provider.KeyProvider;
import org.jfugue.theory.Key;
import org.jfugue.theory.Note;
import org.jfugue.theory.Scale;
import org.jfugue.theory.TimeSignature;
/**
* Parses both Instrument and Layer tokens. Each has values that are parsed as bytes.
*
* @author dkoelle
*/
public class SignatureSubparser implements Subparser, KeyProvider
{
public static final String KEY_SIGNATURE = "KEY:";
public static final String TIME_SIGNATURE = "TIME:";
public static final String SEPARATOR = "/";
private static SignatureSubparser instance;
public static SignatureSubparser getInstance() {
if (instance == null) {
instance = new SignatureSubparser();
}
return instance;
}
@Override
public boolean matches(String music) {
return (matchesKeySignature(music) || matchesTimeSignature(music));
}
public boolean matchesKeySignature(String music) {
return (music.length() >= KEY_SIGNATURE.length()) && (music.substring(0, KEY_SIGNATURE.length()).equals(KEY_SIGNATURE));
}
public boolean matchesTimeSignature(String music) {
return (music.length() >= TIME_SIGNATURE.length()) && (music.substring(0, TIME_SIGNATURE.length()).equals(TIME_SIGNATURE));
}
@Override
public int parse(String music, StaccatoParserContext context) {
if (matchesKeySignature(music)) {
int posNextSpace = StaccatoUtil.findNextOrEnd(music, ' ', 0);
Key key = createKey(music.substring(KEY_SIGNATURE.length(), posNextSpace));
context.setKey(key);
context.getParser().fireKeySignatureParsed(key.getRoot().getPositionInOctave(), key.getScale().getMajorOrMinorIndicator());
return posNextSpace + 1;
} else if (matchesTimeSignature(music)) {
int posNextSpace = StaccatoUtil.findNextOrEnd(music, ' ', 0);
String timeString = music.substring(TIME_SIGNATURE.length(), posNextSpace);
int posOfSlash = timeString.indexOf(SEPARATOR);
if (posOfSlash == -1) {
throw new ParserException(StaccatoMessages.NO_TIME_SIGNATURE_SEPARATOR, timeString);
}
byte numerator = Byte.parseByte(timeString.substring(0, posOfSlash));
byte denominator = Byte.parseByte(timeString.substring(posOfSlash+1, timeString.length()));
TimeSignature timeSignature = new TimeSignature(numerator, denominator);
context.setTimeSignature(timeSignature);
context.getParser().fireTimeSignatureParsed(numerator, denominator);
return posNextSpace + 1;
}
return 0;
}
@Override
public Key createKey(String keySignature) {
// If the key signature starts with K, it is expected to contain a set of flat or sharp characters equal to the number of flats
// or sharps one would see on the staff for the corresponding key. Defaults to MAJOR key.
if (keySignature.charAt(0) == 'K' && (keySignature.indexOf(SHARP_CHAR) == 1 || (keySignature.toUpperCase().indexOf(FLAT_CHAR) == 1))) {
return createKeyFromAccidentals(keySignature);
}
// Otherwise, pass the string value - something like "Cmaj" - to createChord and generate a Key from the intervals in that chord
return new Key(ChordProviderFactory.getChordProvider().createChord(keySignature));
}
/** Returns a Key given a string containing as many flats or sharps as the number one would see on a staff for the corresponding key; e.g., "Kbbbb" = Ab Major */
public Key createKeyFromAccidentals(String keySignature) {
return new Key(MAJOR_KEY_SIGNATURES[KEYSIG_MIDPOINT + countAccidentals(keySignature)] + MAJOR_ABBR);
}
private byte countAccidentals(String keySignatureAsFlatsOrSharps) {
byte keySig = 0;
for (char ch: keySignatureAsFlatsOrSharps.toUpperCase().toCharArray()) {
if (ch == FLAT_CHAR) keySig--;
if (ch == SHARP_CHAR) keySig++;
}
return keySig;
}
@Override
public String createKeyString(byte notePositionInOctave, byte scale) {
StringBuilder buddy = new StringBuilder();
buddy.append(Note.NOTE_NAMES_COMMON[notePositionInOctave]);
if (scale == Scale.MAJOR_INDICATOR) {
buddy.append(MAJOR_ABBR);
} else {
buddy.append(MINOR_ABBR);
}
return buddy.toString();
}
@Override
public byte convertKeyToByte(Key key) {
String noteName = Note.getDispositionedToneStringWithoutOctave(key.getScale().getDisposition(), key.getRoot().getValue());
if (noteName == null) {
return 0;
}
for (byte b = -KEYSIG_MIDPOINT; b < KEYSIG_MIDPOINT+1; b++) {
if (Note.isSameNote(noteName, (key.getScale() == Scale.MAJOR) ? MAJOR_KEY_SIGNATURES[KEYSIG_MIDPOINT + b] : MINOR_KEY_SIGNATURES[KEYSIG_MIDPOINT + b])) {
return (byte)(b * key.getScale().getDisposition());
}
}
return 0;
}
// Major and Minor Key Signatures
// For the Major Key Signatures, 'C' is at the center position. Key signatures defined with flats are to the left of C; key signatures defined with sharps are to the right of C
// For the Minor Key Signatures, 'A' is at the center position. Key signatures defined with flats are to the left of A; key signatures defined with sharps are to the right of A
//
// 7b 6b 5b 4b 3b 2b 1b MID 1# 2# 3# 4# 5# 6# 7#
public static final String[] MAJOR_KEY_SIGNATURES = new String[] { "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#" };
public static final String[] MINOR_KEY_SIGNATURES = new String[] { "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "D#", "A#" };
public static final int KEYSIG_MIDPOINT = 7;
public static final String MAJOR_ABBR = "maj";
public static final String MINOR_ABBR = "min";
public static final char SHARP_CHAR = '#';
public static final char FLAT_CHAR = 'B';
}