/*
* 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.jfugue.theory;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import org.jfugue.pattern.Pattern;
import org.jfugue.pattern.PatternProducer;
import org.jfugue.provider.ChordProviderFactory;
public class Chord implements PatternProducer
{
public static Map<String, Intervals> chordMap;
static {
// @formatter:off
chordMap = new TreeMap<String, Intervals>(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
int result = compareLength(s1, s2);
if (result == 0) { result = s1.compareTo(s2); }
return result;
}
/** Compare two strings and the bigger of the two is deemed to come first in order */
private int compareLength(String s1, String s2) {
if (s1.length() < s2.length()) {
return 1;
} else if (s1.length() > s2.length()) {
return -1;
} else {
return 0;
}
}
});
// Major Chords
chordMap.put("MAJ", new Intervals("1 3 5"));
chordMap.put("MAJ6", new Intervals("1 3 5 6"));
chordMap.put("MAJ7", new Intervals("1 3 5 7"));
chordMap.put("MAJ9", new Intervals("1 3 5 7 9"));
chordMap.put("ADD9", new Intervals("1 3 5 9"));
chordMap.put("MAJ6%9", new Intervals("1 3 5 6 9"));
chordMap.put("MAJ7%6", new Intervals("1 3 5 6 7"));
chordMap.put("MAJ13", new Intervals("1 3 5 7 9 13"));
// Minor Chords
chordMap.put("MIN", new Intervals("1 b3 5"));
chordMap.put("MIN6", new Intervals("1 b3 5 6"));
chordMap.put("MIN7", new Intervals("1 b3 5 b7"));
chordMap.put("MIN9", new Intervals("1 b3 5 b7 9"));
chordMap.put("MIN11", new Intervals("1 b3 5 b7 9 11"));
chordMap.put("MIN7%11", new Intervals("1 b3 5 b7 11"));
chordMap.put("MINADD9", new Intervals("1 b3 5 9"));
chordMap.put("MIN6%9", new Intervals("1 b3 5 6"));
chordMap.put("MINMAJ7", new Intervals("1 b3 5 7"));
chordMap.put("MINMAJ9", new Intervals("1 b3 5 7 9"));
// Dominant Chords
chordMap.put("DOM7", new Intervals("1 3 5 b7"));
chordMap.put("DOM7%6", new Intervals("1 3 5 6 b7"));
chordMap.put("DOM7%11", new Intervals("1 3 5 b7 11"));
chordMap.put("DOM7SUS", new Intervals("1 4 5 b7"));
chordMap.put("DOM7%6SUS", new Intervals("1 4 5 6 b7"));
chordMap.put("DOM9", new Intervals("1 3 5 b7 9"));
chordMap.put("DOM11", new Intervals("1 3 5 b7 9 11"));
chordMap.put("DOM13", new Intervals("1 3 5 b7 9 13"));
chordMap.put("DOM13SUS", new Intervals("1 3 5 b7 11 13"));
chordMap.put("DOM7%6%11", new Intervals("1 3 5 b7 9 11 13"));
// Augmented Chords
chordMap.put("AUG", new Intervals("1 3 #5"));
chordMap.put("AUG7", new Intervals("1 3 #5 b7"));
// Diminished Chords
chordMap.put("DIM", new Intervals("1 b3 b5"));
chordMap.put("DIM7", new Intervals("1 b3 b5 6"));
// Suspended Chords
chordMap.put("SUS4", new Intervals("1 4 5"));
chordMap.put("SUS2", new Intervals("1 2 5"));
// @formatter:on
}
public static String[] getChordNames() {
return chordMap.keySet().toArray(new String[0]);
}
public static void addChord(String name, String intervalPattern) {
Chord.addChord(name, new Intervals(intervalPattern));
}
public static void addChord(String name, Intervals intervalPattern) {
chordMap.put(name, intervalPattern);
}
public static Intervals getIntervals(String name) {
return chordMap.get(name);
}
public static void removeChord(String name) {
chordMap.remove(name);
}
private Note rootNote;
private Intervals intervals;
private int inversion;
public Chord(String s) {
this(ChordProviderFactory.getChordProvider().createChord(s));
}
public Chord(Chord chord) {
this.rootNote = chord.getRoot();
this.intervals = chord.getIntervals();
this.inversion = chord.getInversion();
}
public Chord(Note root, Intervals intervals) {
this.rootNote = root;
this.intervals = intervals;
}
public Chord(Key key) {
this.rootNote = key.getRoot();
this.intervals = key.getScale().getIntervals();
}
public Note getRoot() {
return this.rootNote;
}
public Intervals getIntervals() {
return this.intervals;
}
public int getInversion() {
return this.inversion;
}
public Chord setInversion(int nth) {
this.inversion = nth;
return this;
}
public Chord setBassNote(String newBass) {
return setBassNote(new Note(newBass));
}
public Chord setBassNote(Note newBass) {
if (rootNote == null) {
return this;
}
for (int i=0; i < intervals.size(); i++) {
if (newBass.getValue() % 12 == (rootNote.getValue() + Intervals.getHalfsteps(intervals.getNthInterval(i))) % 12) {
this.inversion = i;
}
}
return this;
}
public Note[] getNotes() {
int[] halfsteps = this.intervals.toHalfstepArray();
Note[] retVal = new Note[halfsteps.length];
retVal[0] = new Note(this.getRoot());
for (int i=0; i < halfsteps.length-1; i++) {
retVal[i+1] = new Note(retVal[i].getValue() + halfsteps[i+1] - halfsteps[i]).setFirstNote(false).setMelodicNote(false).setHarmonicNote(true).useSameDurationAs(getRoot());
}
// For notes Now calculate inversion
for (int i=0; i < this.inversion; i++) {
if (i < retVal.length) {
retVal[i].setValue((byte)(retVal[i].getValue() + OCTAVE));
}
}
return retVal;
}
public String insertChordNameIntoNote(Note note, String chordName) {
StringBuilder buddy = new StringBuilder();
buddy.append(Note.getToneString(note.getValue()));
buddy.append(chordName);
if (note.isDurationExplicitlySet()) {
buddy.append(Note.getDurationString(note.getDuration()));
}
buddy.append(note.getVelocityString());
return buddy.toString();
}
@Override
public Pattern getPattern() {
Pattern pattern = new Pattern();
boolean foundChord = false;
for (Map.Entry<String, Intervals> entry : chordMap.entrySet()) {
if (this.getIntervals().equals(entry.getValue())) {
pattern.add(insertChordNameIntoNote(this.rootNote, entry.getKey()));
foundChord = true;
}
}
if (!foundChord) {
return getPatternWithNotes();
}
return pattern;
}
public Pattern getPatternWithNotes() {
// A better way of creating a Chord: Check to see if the intervals are in the map; if so, use the associated name.
// (Then you'd need to check for inversions, too)
StringBuilder buddy = new StringBuilder();
Note[] notes = getNotes();
for (int i=0; i < notes.length-1; i++) {
buddy.append(notes[i].getPattern());
buddy.append("+");
}
buddy.append(notes[notes.length-1]);
return new Pattern(buddy.toString());
}
public boolean isMajor() {
return this.intervals.equals(MAJOR_INTERVALS);
}
public boolean isMinor() {
return this.intervals.equals(MINOR_INTERVALS);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Chord)) {
return false;
}
Chord c2 = (Chord)o;
return (c2.rootNote.equals(rootNote) &&
c2.intervals.equals(intervals) &&
(c2.inversion == inversion));
}
@Override
public String toString() {
return getPattern().toString();
}
public String toDebugString() {
StringBuilder buddy = new StringBuilder();
int counter = 0;
for (Note note : getNotes()) {
buddy.append("Note ").append(counter++).append(": ").append(note.toDebugString()).append("\n");
}
buddy.append("Chord Intervals = "+getIntervals().toString());
return buddy.toString();
}
public static final Intervals MAJOR_INTERVALS = new Intervals("1 3 5");
public static final Intervals MINOR_INTERVALS = new Intervals("1 b3 5");
public static final Intervals DIMINISHED_INTERVALS = new Intervals("1 b3 b5");
public static final Intervals MAJOR_SEVENTH_INTERVALS = new Intervals("1 3 5 7");
public static final Intervals MINOR_SEVENTH_INTERVALS = new Intervals("1 b3 5 b7");
public static final Intervals DIMINISHED_SEVENTH_INTERVALS = new Intervals("1 b3 b5 6");
public static final Intervals MAJOR_SEVENTH_SIXTH_INTERVALS = new Intervals("1 3 5 6 7");
public static final Intervals MINOR_SEVENTH_SIXTH_INTERVALS = new Intervals("1 3 5 6 7");
public static final byte OCTAVE = 12;
}