/* * 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 org.jfugue.pattern.Pattern; import org.jfugue.pattern.PatternProducer; import org.jfugue.provider.KeyProviderFactory; import org.jfugue.provider.NoteProviderFactory; import org.staccato.StaccatoUtil; public class ChordProgression implements PatternProducer { private String[] progressionElements; private Chord[] knownChords = null; private Key key; private String allSequence; private String eachSequence; // Private constructor for .fromChords static methods private ChordProgression() { } /** * Creates a chord progression given a Progression String, like "I vi ii V" - case is important! * Chords can be separated with spaces ("I vi ii V") or dashes ("I-vi-ii-V"). */ public ChordProgression(String progression) { createProgression(progression.split("[- ]")); // Split on either spaces or dashes // TODO Make ChordProgression parse on [- +] } /** Creates a chord progression given an array of Progression Strings, like { "I", "vi", "ii", "V" } - case is important! */ public ChordProgression(String[] progressionElements) { createProgression(progressionElements); } private void createProgression(String[] progressionElements) { this.progressionElements = progressionElements; this.key = Key.DEFAULT_KEY; } public static ChordProgression fromChords(String knownChords) { String[] knownChordStrings = knownChords.split(" +"); ChordProgression cp = new ChordProgression(); cp.knownChords = new Chord[knownChordStrings.length]; for (int i=0; i < knownChordStrings.length; i++) { cp.knownChords[i] = new Chord(knownChordStrings[i]); } return cp; } public static ChordProgression fromChords(Chord... chords) { ChordProgression cp = new ChordProgression(); cp.knownChords = chords; return cp; } /** The key usually identifies the tonic note and/or chord [Wikipedia] */ public ChordProgression setKey(String key) { return setKey(KeyProviderFactory.getKeyProvider().createKey(key)); } public ChordProgression setKey(Key key) { this.key = key; return this; } @Override public Pattern getPattern() { Pattern pattern = new Pattern(); for (Chord chord : getChords()) { pattern.add(chord); } if (allSequence != null) { pattern = replaceDollarsWithCandidates(allSequence, getChords(), new Pattern(getChords())); } if (eachSequence != null) { Pattern p2 = new Pattern(); for (String chordString : pattern.toString().split(" ")) { // TODO Should be " +" Chord chord = new Chord(chordString); p2.add(replaceDollarsWithCandidates(eachSequence, chord.getNotes(), chord)); } pattern = p2; } return pattern; } /** * Returns a list of chords represented by this chord progression. */ public Chord[] getChords() { if (knownChords != null) { return knownChords; } Chord[] chords = new Chord[progressionElements.length]; Pattern scalePattern = key.getScale().getIntervals().setRoot(key.getRoot()).getPattern(); String[] scaleNotes = scalePattern.toString().split(" "); int counter = 0; for (String progressionElement : progressionElements) { Note rootNote = NoteProviderFactory.getNoteProvider().createNote(scaleNotes[romanNumeralToIndex(progressionElement)]); rootNote.useSameDurationAs(key.getRoot()); Intervals intervals = Chord.MAJOR_INTERVALS; if ((progressionElement.charAt(0) == 'i') || (progressionElement.charAt(0) == 'v')) { // Checking to see if the progression element is lowercase intervals = Chord.MINOR_INTERVALS; } if ((progressionElement.toLowerCase().indexOf("o") > 0) || (progressionElement.toLowerCase().indexOf("d") > 0)) { // Checking to see if the progression element is diminished intervals = Chord.DIMINISHED_INTERVALS; } if (progressionElement.endsWith("7")) { if (intervals.equals(Chord.MAJOR_INTERVALS)) { intervals = Chord.MAJOR_SEVENTH_INTERVALS; } else if (intervals.equals(Chord.MINOR_INTERVALS)) { intervals = Chord.MINOR_SEVENTH_INTERVALS; } else if (intervals.equals(Chord.DIMINISHED_INTERVALS)) { intervals = Chord.DIMINISHED_SEVENTH_INTERVALS; } } if (progressionElement.endsWith("7%6")) { if (intervals.equals(Chord.MAJOR_INTERVALS)) { intervals = Chord.MAJOR_SEVENTH_SIXTH_INTERVALS; } else if (intervals.equals(Chord.MINOR_INTERVALS)) { intervals = Chord.MINOR_SEVENTH_SIXTH_INTERVALS; } } chords[counter] = new Chord(rootNote, intervals); counter++; } return chords; } /** * Only converts Roman numerals I through VII, because that's all we need in music theory... * VIII would be the octave and equal I! */ private int romanNumeralToIndex(String romanNumeral) { String s = romanNumeral.toLowerCase(); // Notice if we are dealing with a diminished interval if (s.endsWith("o") || s.endsWith("d") || s.endsWith("7")) { s = s.substring(0, s.length()-1); } if (s.endsWith("7%6")) { s = s.substring(0, s.length()-3); } // Convert Roman numerals to numeric index if (s.equals("i")) { return 0; } else if (s.equals("ii")) { return 1; } else if (s.equals("iii")) { return 2; } else if (s.equals("iv")) { return 3; } else if (s.equals("v")) { return 4; } else if (s.equals("vi")) { return 5; } else if (s.equals("vii")) { return 6; } return 0; } public String toString() { return getPattern().toString(); } public String[] toStringArray() { return getPattern().toString().split(" "); } // // // Cool ways of playing ChordProgressions! // // private Pattern replaceDollarsWithCandidates(String sequence, PatternProducer[] candidates, PatternProducer underscoreReplacement) { StringBuilder buddy = new StringBuilder(); int posPrevDollar = -1; int posNextDollar = 0; while (posNextDollar < sequence.length()) { posNextDollar = StaccatoUtil.findNextOrEnd(sequence, '$', posPrevDollar); if (posPrevDollar+1 < sequence.length()) { buddy.append(sequence.substring(posPrevDollar+1, posNextDollar)); } if (posNextDollar != sequence.length()) { String selectionString = sequence.substring(posNextDollar+1, posNextDollar+2); if (selectionString.equals("_")) { // If the underscore replacement has tokens, then the stuff after $_ needs to be applied to each token in the underscore replacement! String replacementTokens[] = underscoreReplacement.getPattern().toString().split(" "); int nextSpaceInSequence = StaccatoUtil.findNextOrEnd(sequence, ' ', posNextDollar); for (String token : replacementTokens) { buddy.append(token); buddy.append(sequence.substring(posNextDollar+2, nextSpaceInSequence)); buddy.append(" "); } posNextDollar = nextSpaceInSequence-1; } else { int selection = Integer.parseInt(sequence.substring(posNextDollar+1, posNextDollar+2)); if (selection > candidates.length) { throw new IllegalArgumentException("The selector $"+selection+" is greater than the number of items to choose from, which has "+candidates.length+" items."); } buddy.append(candidates[selection].getPattern()); } } posPrevDollar = posNextDollar+1; } return new Pattern(buddy.toString().trim()); } /** * Requires passing a string that has dollar signs followed by an index, in which case each dollar+index will be replaced * by the indexed note of the chord for each chord in the progression. For example, given a ChordProgression of "I IV V" * and a string of "$0q $1h $2w", will return "Cq E4h G4w Fq A4h C5w Gq B4h D5w". Using the underscore character instead * of an index will result in the chord itself added to the string. The final result will be returned from the getPattern() * method. */ public ChordProgression eachChordAs(String sequence) { this.eachSequence = sequence; return this; } /** * Requires passing a string that has dollar signs followed by an index, in which case each dollar+index will be replaced * by the indexed chord of the chord progression. For example, given a ChordProgression of "I IV V" * and a string of "$0q $1h $2w", will return "C4MAJq F4MAJh G4MAJw". Using the underscore character instead of an * index will result in the pattern of the ChordProgression itself added to the string. The final result will be returned * from the getPattern() method. */ public ChordProgression allChordsAs(String sequence) { this.allSequence = sequence; return this; } public ChordProgression distribute(String distribute) { for (int i=0; i < progressionElements.length; i++) { progressionElements[i] = progressionElements[i] + distribute; } return this; } }