ChordCreatorUtil.java :  » Music » TuxGuitar-1.2 » org » herac » tuxguitar » gui » editors » chord » Java Open Source

Java Open Source » Music » TuxGuitar 1.2 
TuxGuitar 1.2 » org » herac » tuxguitar » gui » editors » chord » ChordCreatorUtil.java
/*

 * Created on 31-dic-2005

 * Redesigned on 05-March-2007

 * 

 */

package org.herac.tuxguitar.gui.editors.chord;

import java.util.ArrayList;
import java.util.Collections;

import java.util.Comparator;

import java.util.Iterator;

import java.util.List;

import org.herac.tuxguitar.gui.TuxGuitar;

import org.herac.tuxguitar.song.models.TGChord;

/**
 * 
 * Class that helps to create a chord from information put in ChordSelector
 * dialog.
 * 
 * Also contains ChordDatabase static field.
 * 
 * @author Nikola Kolarovic
 * 
 * @author julian
 * 
 */
public class ChordCreatorUtil {
  
  /**
   * Maximum number of strings variable - has twin in TrackPropertiesAction
   * class
   */
  public static final int MAX_STRINGS = 7;
  
  /** Maximum fret distance for a chord */
  
  public static final int MAX_FRET_SPAN = 5;
  
  /** mark for bass note type **/
  private final int BASS_INDEX = -1;
  
  /** mark for essential note in a chord - MUST be in */
  private final int ESSENTIAL_INDEX = -2;
  
  /** mark for essential note in a chord - PENALTY if not in */
  private final int NOT_ESSENTIAL_INDEX = -3;
  
  /** Keep the Thread control */
  private static long runningProcess;
  
  // ------ attributes ------
  
  //protected ChordInfo info;
  private long processId;
  
  private ChordCreatorListener listener;
  
  /** the alteration List selectionIndex */
  private int alteration;
  
  private int chordIndex;
  
  /** essential notes for the chord (from ChordInfo) */
  private int[] requiredNotes;
  
  /** notes that expand the chord (add+-) */
  private int[] expandingNotes;
  
  /** is the fifth altered */
  private int add5 = 0;
  
  /** name of a chord */
  private String chordName = null;
  
  private int bassTonic; 
  
  private int chordTonic;
  
  /** current tunning */
  private int[] tuning;
  
  private ChordCreatorUtil(long processId,ChordCreatorListener listener){
    this.processId = processId;
    this.listener = listener;
  }
  
  public boolean isValidProcess(){
    return (this.processId == runningProcess);
  }
  
  public static long getNewProcess(){
    return (++ runningProcess);
  }
  
  public static void getChords(final ChordCreatorListener listener,
                               final int[] tuning,
                               final int chordIndex,
                               final int alteration,
                               final int plusMinus,
                               final boolean add,
                               final int add5,
                               final int add9,
                               final int add11,
                               final int bassTonic,
                               final int chordTonic,
                               final boolean sharp){
    
    final ChordCreatorUtil chordCreator = new ChordCreatorUtil(getNewProcess(), listener );
    new Thread(new Runnable() {
      public void run() {
        chordCreator.getChords( tuning, chordIndex, alteration, plusMinus, add, add5, add9, add11, bassTonic, chordTonic, sharp);
      }
    }).start();
  }
  
  protected void getChords(int[] tuning,
                           int chordIndex,
                           int alteration,
                           int plusMinus,
                           boolean add,
                           int add5,
                           int add9,
                           int add11,
                           int bassTonic,
                           int chordTonic,
                           boolean sharp) {
    
    if(!isValidProcess()){
      return;
    }
    
    this.add5 = add5;
    
    this.tuning = tuning;
    
    this.chordIndex = chordIndex;
    
    this.chordTonic = chordTonic;
    
    this.bassTonic = bassTonic;
    
    this.alteration = alteration;
    
    this.chordName = new ChordNamingConvention().createChordName(this.chordTonic,
                                                                 this.chordIndex,
                                                                 this.alteration,
                                                                 plusMinus,
                                                                 add,
                                                                 add5,
                                                                 add9,
                                                                 add11,
                                                                 this.bassTonic,
                                                                 sharp);
    
    
    // find the notes that expand the chord
    if (this.alteration!=0) {
      if (add) {
        this.expandingNotes = new int[1];
        this.expandingNotes[0]= getAddNote(this.alteration-1,plusMinus);
      }
      else { // not just add...
        // 9+- = 7b !9(+-)    (index=1)
        // 11+- = 7b !11(+-) 9(+-)  (index=2)
        // 13+- = 7b !13(+-) 9(+-) 11(+-) (index=3)
        this.expandingNotes = new int[1+this.alteration];
        this.expandingNotes[0] = 11; //7b
        this.expandingNotes[1] = getAddNote(this.alteration-1,plusMinus); //this.alteration+-
        
        // rest
        for (int i=2; i<=this.alteration; i++)
          this.expandingNotes[i]=getAddNote(i-2, i==2 ? add9 : add11); // @2=add9+-, @3=add11+- tone
      }
      
    }
    else this.expandingNotes=new int[0];
    
    
    
    // Required notes
    //this.requiredNotes = ((ChordDatabase.ChordInfo)new ChordDatabase().getChords().get(chordIndex)).cloneRequireds();
    this.requiredNotes = ChordDatabase.get(chordIndex).cloneRequireds();
    //IT DON'T BUILD UNDER JRE1.4
    //this.requiredNotes = ((ChordDatabase.ChordInfo) ChordCreatorUtil.getChordData().getChords().get(chordIndex)).getRequiredNotes().clone();
    
    
    // adjust the subdominant if needed
    if (add5!=0) {
      for (int i=0; i<this.requiredNotes.length; i++)
        if (this.requiredNotes[i]==8) // alternate the subdominant
          this.requiredNotes[i]+=(add5==1 ? 1 : -1);
    }
    
    // first count different from -1
    int count = 0;
    for (int i=0; i<this.requiredNotes.length; i++) {
      this.requiredNotes[i]=checkForOverlapping(this.requiredNotes[i]);
      if (this.requiredNotes[i]!=-1)
        count++;
    }
    // then fill in the new array
    int[] tempNotes = new int[count];
    count = 0;
    for (int i=0; i<this.requiredNotes.length; i++)
      if (this.requiredNotes[i]!=-1) {
        tempNotes[count]=this.requiredNotes[i];
        count++;
      }
    // and substitute them
    this.requiredNotes = tempNotes;
    
    //return getChords();
    if(isValidProcess()){
      List chords = getChords();
      if(chords != null && isValidProcess()){
        this.listener.notifyChords(this, chords);
      }
    }
  }
  
  /** We have to make sure that if required note is already inside
   * expanding notes array so we don't put it twice...
   */
  protected int checkForOverlapping(int checkIt) {
    for (int i=0; i<this.expandingNotes.length; i++)
      if (this.expandingNotes[i]==checkIt)
        return -1;
    return checkIt;
  }
  
  /**
   * 
   * Creates the chord combinations out of given data and then uses some kind
   * of
   * 
   * heuristics to check the most suitable formations.
   * 
   * @return the list of TGChord structures that are most suitable
   * 
   */
  private java.util.List getChords() {
    if(!isValidProcess()){
      return null;
    }
    ArrayList potentialNotes = makePotentialNotes();
    
    ArrayList combinations = makeCombinations( potentialNotes);
    
    ArrayList priorities = determinePriority( combinations);
    
    ArrayList theBestOnes = takeBest( priorities);
    
    return createChords( theBestOnes);
  }
  
  /**
   * Creates the TGChord ArrayList out of StringValue's ArrayLists
   * 
   * @param Highest rated StringValues
   * @return TGChord collection
   */
  private ArrayList createChords(ArrayList top) {
    if(!isValidProcess()){
      return null;
    }
    
    ArrayList chords = new ArrayList(top.size());
    
    Iterator it = top.iterator();
    
    while (it.hasNext()) {
      TGChord chord = TuxGuitar.instance().getSongManager().getFactory()
          .newChord(this.tuning.length);
      Iterator it2 = ((ArrayList) it.next()).iterator();
      
      while (it2.hasNext()) {
        StringValue stringValue = (StringValue) it2.next();
        int string = ((chord.getStrings().length - 1) - (stringValue.getString()));
        int fret = stringValue.getFret();
        chord.addFretValue(string, fret);
        chord.setName(this.chordName);
      }
      
      chords.add(chord);
    }
    return chords;
  }
  
  /**
   * 
   * If string/fret combination is needed for the chord formation, add it into
   * List
   * 
   * @return true if the note is needed for chord formation
   * 
   */
  private void find(int stringTone, int stringIndex, int fret,List stringList){
    if(!isValidProcess()){
      return;
    }
    boolean bassAlreadyIn=false;
    // chord base notes
    for (int i = 0; i < this.requiredNotes.length; i++)
      if ((stringTone + fret) % 12 == (this.chordTonic+this.requiredNotes[i] - 1) % 12) {
        if (!bassAlreadyIn && (stringTone + fret) % 12 == this.bassTonic)
          bassAlreadyIn=true;
        stringList.add(new StringValue(stringIndex, fret, i));
        return;
      }
    
    // alterations
    if (this.expandingNotes.length!=0) {
      for (int i=0; i<this.expandingNotes.length; i++) {
        if ((stringTone+fret)%12==(this.chordTonic+this.expandingNotes[i]-1)%12) {
          if (!bassAlreadyIn && (stringTone + fret) % 12 == this.bassTonic)
            bassAlreadyIn=true;
          stringList.add(new StringValue(stringIndex,fret,(i<2 ? this.ESSENTIAL_INDEX : this.NOT_ESSENTIAL_INDEX)));
        }
      }
    }
    
    // bass tone
    if (!bassAlreadyIn)
      if ((stringTone + fret) % 12 == this.bassTonic) {
        stringList.add(new StringValue(stringIndex, fret, this.BASS_INDEX));
        return;
      }
    
  }
  
  /**
   * Returns the wanted note for ADD chord
   * 
   * @param type
   *            0==add9; 1==add11; 2==add13; 
   * @param selectionIndex
   *            index of selected item in the List combo
   * @return wanted note, or -1 if nothing was selected
   * 
   */
  private int getAddNote(int type, int selectionIndex) {
    
    int wantedNote = 0;
    
    switch (type) {
      case 0:
        wantedNote = 3; // add9
        break;
      case 1:
        wantedNote = 6; // add11
        break;
      case 2:
        wantedNote = 10; // add13
        break;
    }
    
    switch (selectionIndex) {
      case 1:
        wantedNote++;
        break;
      case 2:
        wantedNote--;
        break;
      default:
        break;
    }
    
    return wantedNote;
  }
  
  private ArrayList makePotentialNotes(){
    if(!isValidProcess()){
      return null;
    }
    ArrayList potentialNotes = new ArrayList(this.tuning.length);
    
    for (int string = 0; string < this.tuning.length; string++) {
      
      ArrayList currentStringList = new ArrayList(10);
      
      // search all the frets
      
      if (ChordSettings.instance().getFindChordsMin()>0 && ChordSettings.instance().isEmptyStringChords())
        find(this.tuning[string], string, 0, currentStringList); // if it's open chord but wanted to search from different minimal fret
        
      
      for (int fret = ChordSettings.instance().getFindChordsMin(); fret <= ChordSettings.instance().getFindChordsMax(); fret++) {
        // put in all the needed notes
        find(this.tuning[string], string, fret, currentStringList);
      }
      
      potentialNotes.add(currentStringList);
      
    }
    return potentialNotes;
  }
  
  /**
   * 
   * Makes the all-possible combinations of found notes that can be reached by
   * fingers
   * 
   * @param potentialNotes
   *            list consisted of found notes for each string
   * 
   * @return list of list of StringValues, with tones that can form a chord
   * 
   */
  private ArrayList makeCombinations(ArrayList potentialNotes) {
    if(!isValidProcess()){
      return null;
    }
    
    // COMBINATIONS of strings used in a chord
    ArrayList stringCombination = new ArrayList(60);
    ArrayList lastLevelCombination = null;
    
    for (int i = 0; i < this.tuning.length - 1; i++)
    {
      lastLevelCombination = makeStringCombination(lastLevelCombination);
      
      // lastLevelCombination after 3rd round: [[0, 1, 2, 3], [0, 1, 2,
      // 4], [0, 1, 3, 4], [0, 2, 3, 4], [1, 2, 3, 4], [0, 1, 2, 5], [0,
      // 1, 3, 5], [0, 2, 3, 5], [1, 2, 3, 5], [0, 1, 4, 5], [0, 2, 4, 5],
      // [1, 2, 4, 5], [0, 3, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
      
      stringCombination.addAll(lastLevelCombination);
    }
    
    ArrayList combinations = new ArrayList(800);
    
    // --- combine the StringValues according to strings combination
    // ----------------------=======
    
    Iterator iterator = stringCombination.iterator();
    
    while (iterator.hasNext()) { // go through all string combinations list
      // take a string combinations
      ArrayList currentStringCombination = (ArrayList) iterator.next();
      lastLevelCombination = null;
      
      // go through all the strings in one combination
      for (int level = 0; level < currentStringCombination.size(); level++) { 
        
        // take the string index
        int currentString = ((Integer) currentStringCombination.get(level)).intValue();
        
        // take all the potential notes from currentString and combine
        // them with potential notes from other strings
        
        lastLevelCombination = makeStringValueCombination(lastLevelCombination,(ArrayList)potentialNotes.get(currentString));
        
        // the structure of combinations is AL { AL(StringValue,SV,SV),
        // AL(SV), AL(SV,SV),AL(SV,SV,SV,SV,SV,SV) }
        
      }
      
      if(lastLevelCombination != null){
        combinations.addAll(lastLevelCombination);
      }
    }
    
    return combinations;
  }
  
  /**
   * Makes a combination of string indices
   * 
   * @param lastLevelCombination
   *            structure to be expanded by current level
   * 
   * @return structure of stringCombination is AL { AL(0), AL(0,1),
   *         AL(0,2),AL(0,1,3,4),AL(0,1,2,3,4,5) }
   */
  private ArrayList makeStringCombination(ArrayList lastLevelCombinationRef){
    if(!isValidProcess()){
      return null;
    }
    
    List lastLevelCombination = lastLevelCombinationRef;
    
    if (lastLevelCombination == null) {
      // first combination is AL { AL(0), AL(1), AL(2), AL(3), AL(4),
      // ...AL(tuning.length) }
      lastLevelCombination = new ArrayList();
      
      for (int i = 0; i < this.tuning.length; i++) {
        lastLevelCombination.add(new ArrayList());
        ((ArrayList) lastLevelCombination.get(i)).add(new Integer(i));
      }
    }
    
    ArrayList thisLevelCombination = new ArrayList();
    for (int current = 1; current < this.tuning.length; current++)
    {
      Iterator it = lastLevelCombination.iterator();
      
      while (it.hasNext()) {
        ArrayList combination = (ArrayList) it.next();
        Integer currentInteger = new Integer(current);
        if (((Integer) combination.get(combination.size() - 1))
            .intValue() < current
            && !combination.contains(currentInteger)) {
          
          // check if the string is already in combination
          ArrayList newCombination = (ArrayList) combination.clone();
          newCombination.add(currentInteger);
          thisLevelCombination.add(newCombination);
        }
        
      }
      
    }
    
    return thisLevelCombination;
    
  }
  
  /**
   * Makes a combination of notes by multiplying last combination and current
   * note arrays
   * 
   * 
   * 
   * @param lastLevelCombination
   *            structure to be expanded by current level
   * 
   * @param notes
   *            notes that can be considered into making a chord
   * 
   * @return structure of StringValue combinations : AL {
   *         AL(StringValue,SV,SV), AL(SV), AL(SV,SV),AL(SV,SV,SV,SV,SV,SV) }
   * 
   */
  private ArrayList makeStringValueCombination(ArrayList lastLevelCombination, ArrayList notes) {
    if(!isValidProcess()){
      return null;
    }
    ArrayList thisLevelCombination = null;
    
    if (lastLevelCombination == null) { // initial combination
      
      thisLevelCombination = new ArrayList(notes.size());
      
      for (int i = 0; i < notes.size(); i++) {
        
        thisLevelCombination.add(new ArrayList(6));
        
        ((ArrayList) thisLevelCombination.get(i)).add(notes.get(i));
        
      }
      
      // first combination is AL { AL(firstOne), AL(anotherFret) }
      
    }
    
    else {
      
      thisLevelCombination = new ArrayList();
      
      for (int i = 0; i < notes.size(); i++)
        for (int j = 0; j < lastLevelCombination.size(); j++) { // cartesian multiplication
          ArrayList currentCombination = (ArrayList) ((ArrayList) lastLevelCombination.get(j)).clone();
          currentCombination.add(notes.get(i));
          
          // if the distance maximum between the existing frets
          // is less than wanted, add it into potential list
          
          if (checkCombination(currentCombination))
            thisLevelCombination.add(currentCombination);
          
        }
    }
    
    return thisLevelCombination;
  }
  
  /**
   * Checks if the combination can be reached by fingers. It is reachable
   * 
   * if the distance between lowest and highest fret is less than
   * 
   * <i>ChordCreatorUtil.MAX_FRET_SPAN</i>.
   * 
   * Also note that this method eliminates or includes the chords with empty
   * strings,
   * 
   * which is controlled with <i>boolean ChordCreatorUtil.EMPTY_STRING_CHORDS</i>
   * 
   * @param combination
   *            current combination to be examined
   * 
   * @return true if it can be reached
   * 
   */
  private boolean checkCombination(ArrayList combination) {
    
    Iterator it = combination.iterator();
    int maxLeft, maxRight;
    
    maxLeft = maxRight = ((StringValue) combination.get(0)).getFret();
    
    while (it.hasNext()) {
      
      int fret = ((StringValue) it.next()).getFret();
      
      //chords with empty-string are welcome
      if (fret != 0 || !ChordSettings.instance().isEmptyStringChords()) {
        
        if (fret < maxLeft)
          maxLeft = fret;
        
        if (fret > maxRight)
          maxRight = fret;
        
      }
      
    }
    
    if (Math.abs(maxLeft - maxRight) >= MAX_FRET_SPAN)
      
      return false;
    
    return true;
    
  }
  
  /**
   * orders the StringValue ArrayList by their priority, calculated here
   * 
   * for every single chord combination.<br>
   * 
   * Priority is higher if:<br>
   *  - tone combination has all notes required for the chord basis<br>
   *  - has good chord semantics uses many basic tones, and all necessary
   * tones in their place<br>
   *  - tone combination has all subsequent strings (no string skipping)<br>
   *  - has a chord bass tone as lowest tone<br>
   *  - uses more strings<br>
   *  - uses good fingering positions<br>
   * 
   * @param allCombinations
   *            all the StringValue combinations that make some sense
   * 
   * @return Treemap of the StringValue ArrayLists, in which the key is
   * 
   * <i>float priority</i>.
   * 
   */
  private ArrayList determinePriority(ArrayList allCombinations) {
    if(!isValidProcess()){
      return null;
    }
    ArrayList ordered = new ArrayList();
    
    Iterator it = allCombinations.iterator();
    
    while (it.hasNext() && isValidProcess()) {
      
      float priority = 0;
      
      ArrayList stringValueCombination = (ArrayList) it.next();
      
      // tone combination has all notes required for the chord basis
      
      priority += combinationHasAllRequiredNotes(stringValueCombination);
      
      // uses good chord semantics
      
      priority += combinationChordSemantics(stringValueCombination);
      
      // tone combination has all subsequent strings (no string skipping)
      
      priority += combinationHasSubsequentStrings(stringValueCombination);
      
      // has a chord bass tone as lowest tone
      
      priority += combinationBassInBass(stringValueCombination);
      
      // uses many strings
      // 4 and less strings will be more praised in case of negative grade
      // 4 and more strings will be more praised in case of positive grade 
      priority += ChordSettings.instance().getManyStringsGrade() / 3
        * (stringValueCombination.size()-this.tuning.length / 
            (ChordSettings.instance().getManyStringsGrade()>0 ? 2 : 1.2) );
      
      // uses good fingering positions
      
      priority += combinationHasGoodFingering(stringValueCombination);
      
      // System.out.println("OVERALL:
      // "+priority+"----------------------------");
      
      PriorityItem item = new PriorityItem();
      
      item.priority = priority;
      
      item.stringValues = stringValueCombination;
      
      ordered.add(item);
      
    }
    
    return ordered;
    
  }
  
  /**
   * 
   * Takes the StringValue ArrayLists that has the best priority rating
   * 
   */
  private ArrayList takeBest(ArrayList priorityItems) {
    if(!isValidProcess()){
      return null;
    }
    
    int maximum = ChordSettings.instance().getChordsToDisplay();
    
    ArrayList bestOnes = new ArrayList(maximum);
    
    Collections.sort(priorityItems, new PriorityComparator());
    for(int i = 0; i < priorityItems.size() && isValidProcess(); i ++){
      PriorityItem item = (PriorityItem)priorityItems.get(i);
      if (!checkIfSubset(item.stringValues, bestOnes) ){
        bestOnes.add(item.stringValues);
        
        if( bestOnes.size() >= maximum ){
          break;
        }
      }
    }
    
    return bestOnes;
    
  }
  
  /** adds points if the combination has all the notes in the basis of chord */
  private float combinationHasAllRequiredNotes(ArrayList stringValueCombination) {
    if(!isValidProcess()){
      return 0;
    }
    Iterator it = stringValueCombination.iterator();
    int[] values = new int[this.requiredNotes.length];
    int currentIndex = 0;
    
    while (it.hasNext()) {
      StringValue sv = (StringValue) it.next();
      
      if (sv.getRequiredNoteIndex() >= 0) { // only basis tones
        boolean insert = true;
        
        for (int i = 0; i < currentIndex; i++)
          if (values[i] == sv.getRequiredNoteIndex() + 1)
            insert = false;
        
        // sv.requiredNoteIndex+1, because we have index 0 and we don't
        // want it inside
        
        if (insert) {
          values[currentIndex] = sv.getRequiredNoteIndex() + 1;
          currentIndex++;
        }
        
      }
    }
    
    if (currentIndex == this.requiredNotes.length) {
      return ChordSettings.instance().getRequiredBasicsGrade();
    }
    
    if (currentIndex == this.requiredNotes.length - 1) {
      
      boolean existsSubdominant = false;
      
      Iterator it2 = stringValueCombination.iterator();
      while (it2.hasNext()) {
        StringValue current = (StringValue)it2.next();
        if ((this.tuning[current.getString()] + current.getFret()) % 12 == (this.chordTonic + 7) %12)
          existsSubdominant = true;
      }
      
      if (!existsSubdominant && currentIndex == this.requiredNotes.length-1) {
        // if not riff. "sus" chord, or chord with altered fifth allow chord without JUST subdominant (fifth) with small penalty 
        
        //if ( !((ChordInfo)new ChordDatabase().getChords().get(this.chordIndex)).getName().contains("sus") && this.requiredNotes.length!=2 && this.add5==0) {
        //String.contains(String) is not available at JRE1.4
        //Replaced by "String.indexOf(String) >= 0"
        if ( ChordDatabase.get(this.chordIndex).getName().indexOf("sus") >= 0 && this.requiredNotes.length != 2 && this.add5 == 0) {
          return ( ChordSettings.instance().getRequiredBasicsGrade() * 4 / 5 );
        }
      }
      
    }
    
    // required notes count should decrease the penalty
    int noteCount = (this.alteration == 0 ? 0 : 1+ this.alteration)+currentIndex+ (this.bassTonic == this.chordTonic ? 0 : 1);
    
    // sometimes, when noteCount is bigger then tunning length, this pennalty will become positive, which may help
    return -ChordSettings.instance().getRequiredBasicsGrade()
        * (this.tuning.length - noteCount) / this.tuning.length * 2;
    
  }
  
  /** adds points if the combination has strings in a row */
  private float combinationHasSubsequentStrings(ArrayList stringValueCombination) {
    if(!isValidProcess()){
      return 0;
    }
    boolean stumbled = false, noMore = false, penalty = false;
    
    for (int i = 0; i < this.tuning.length; i++) {
      boolean found = false;
      Iterator it = stringValueCombination.iterator();
      while (it.hasNext())
        if (((StringValue) it.next()).getString() == i)
          found = true;
      if (found) {
        if (!stumbled)
          stumbled = true;
        if (noMore)
          penalty = true;
        if (penalty) // penalty for skipped strings
          return -ChordSettings.instance().getSubsequentGrade();
      }
      else
      if (stumbled)
        noMore = true;
    }
    
    if (penalty)
      return 0.0f;
    
    return ChordSettings.instance().getSubsequentGrade();
  }
  
  /** checks if the bass tone is the lowest tone in chord */
  private float combinationBassInBass(ArrayList stringValueCombination) {
    if(!isValidProcess()){
      return 0;
    }
    for (int i = 0; i < this.tuning.length; i++) {
      
      Iterator it = stringValueCombination.iterator();
      
      while (it.hasNext()) {
        StringValue sv = (StringValue) it.next();
        
        if (sv.getString() == i) { // stumbled upon lowest tone
          if ( (this.tuning[sv.getString()]+sv.getFret()) % 12 == this.bassTonic  )
            return ChordSettings.instance().getBassGrade();
          // else
          return -ChordSettings.instance().getBassGrade();
        }
      }
      
    }
    
    return 0;
  }
  
  /**
   * grades the fingering in a chord.
   * 
   * fingering is good if:<br>
   *  - uses as little as possible fret positions<br>
   *  - uses less than 3 fret positions<br>
   *  - distributes good among fingers<br>
   *  - can be placed capo <br>
   * 
   */
  private float combinationHasGoodFingering(ArrayList stringValueCombination) {
    if(!isValidProcess()){
      return 0;
    }
    // init: copy into simple array
    float finalGrade = 0;
    int[] positions = new int[this.tuning.length];
    for (int i = 0; i < this.tuning.length; i++)
      positions[i] = -1;
    {
      Iterator it = stringValueCombination.iterator();
      
      while (it.hasNext()) {
        StringValue sv = (StringValue) it.next();
        positions[sv.getString()] = sv.getFret();
      }
    }
    // algorithm
    
    // distance between fingers
    int min = ChordSettings.instance().getFindChordsMax()+2, max = 0, maxCount=0;
    boolean openChord = false, zeroString = false;
    
    for (int i = 0; i < this.tuning.length; i++) {
      
      openChord|= ChordSettings.instance().isEmptyStringChords() && positions[i] == 0;
      zeroString |= positions[i]==0;
      
      if (positions[i] < min && positions[i] != 0 && positions[i]!=-1)
        min = positions[i];
      
      if (positions[i] > max) {
        max = positions[i];
        maxCount=1;
      }
      else 
        if (positions[i]==max) 
          maxCount++;
      
    }
    
    // finger as capo
    
    int count = 0;
    
    for (int i = 0; i < this.tuning.length; i++)
      if (positions[i] == min)
        count++;
    
    if (!openChord) { 
      if (zeroString)
        finalGrade += ChordSettings.instance().getFingeringGrade()/8;
      else
        if (count >= 2)
          finalGrade += ChordSettings.instance().getFingeringGrade()/8;
    }
    else
      if (openChord)
        finalGrade += ChordSettings.instance().getFingeringGrade()/8;
    
    // position distance: 1-2 nice 3 good 4 bad 5 disaster
    float distanceGrade;
    
    switch(Math.abs(max-min)) {
      case 0 : distanceGrade=ChordSettings.instance().getFingeringGrade()/5;
          break;
      case 1 : distanceGrade=ChordSettings.instance().getFingeringGrade()/(5+maxCount);
          break;
      case 2 : distanceGrade=ChordSettings.instance().getFingeringGrade()/(6+maxCount);
           if (min<5) distanceGrade*=0.9;
          break;
      case 3 : distanceGrade=-ChordSettings.instance().getFingeringGrade()/10*maxCount;
          // I emphasize the penalty if big difference is on some 
          // lower frets (it is greater distance then)
          if (min<5) distanceGrade*=1.3;
          break;
      case 4 : distanceGrade=-ChordSettings.instance().getFingeringGrade()/4*maxCount;
          if (min<=5) distanceGrade*=1.8;
          break;
      default : distanceGrade=-ChordSettings.instance().getFingeringGrade()*maxCount;
          break;
    }
    finalGrade += distanceGrade;
    
    // ============== finger position abstraction ==================
    // TODO: what to do with e.g. chord -35556 (C7)
    // ... it can be held with capo on 5th fret, but very hard :)
    // ... This is the same as with "capo after", I didn't consider that (e.g. chord -35555)
    ArrayList[] fingers={new ArrayList(2),new ArrayList(2),new ArrayList(2),new ArrayList(2)};
    // TODO: still no thumb, sorry :)
    
    // STRUCTURE: ArrayList consists of Integers - first is fret
    //                                           - others are strings
/*    
    for (int i=0; i<this.tuning.length; i++)
      System.out.print(" "+positions[i]);
    System.out.println("");
*/
    
    // if chord is open, then we can have capo only in strings before open string
    int lastZeroIndex = 0;
    
    if (zeroString)
      for (int i=0; i<positions.length; i++)
        if (positions[i]==0) lastZeroIndex=i;
    
    // open or not not open chord,
    // index finger is always on lowest fret possible
    fingers[0].add(new Integer(min));
    
    for (int i=lastZeroIndex; i<positions.length; i++)
        if (positions[i]==min) {
          fingers[0].add(new Integer(i));
          positions[i]=-1;
        }
    
    // other fingers
    // if not zero-fret, occupy fingers respectivly
    int finger=1;
    for (int i=0; i<positions.length; i++) {
      if (positions[i]!=0 && positions[i]!=-1) {
        if (finger<4) {
          fingers[finger].add(new Integer(positions[i]));
          fingers[finger].add(new Integer(i));
          positions[i]=-1;
        }
        finger++;
      }
    }
    
/*    System.out.println("Positions:");
    for (int i=0; i<4; i++) {
      if (fingers[i].size()>1)
        System.out.print("G"+(i+1)+"R"+((Integer)fingers[i].get(0)).intValue()+"S"+((Integer)fingers[i].get(1)).intValue()+" ");
    }
*/    
    
    if (finger>4) 
      finalGrade-=ChordSettings.instance().getFingeringGrade();
     else
      finalGrade+=ChordSettings.instance().getFingeringGrade()*0.1*(15-2*finger);
    
    // TODO: maybe to put each finger's distance from the minimum
    return finalGrade;
    
  }
  
  /**
   * grades the chord semantics, based on theory.
   * 
   * Tone semantics is good if:<br>
   *  - there appear tones from chord basis or bass tone<br>
   *  - there appear alteration tones on their specific places<br><br>
   *
   * Algorithm:<br>
   *  - search for chord tonic. If some note is found before (and it's not bass) do penalty<br>
   *  - make penalty if the bass tone is not in bass<br>
   *  - check if all the expanding notes are here. If some are not, do penalty<br>
   *  - if expanding note isn't higher than tonic octave, then priority should be less<br>
   *  - If there are not some with NON_ESSENTIAL_INDEX are not here, penalty should be less<br>
   */
  private float combinationChordSemantics(ArrayList stringValueCombination) {
    if(!isValidProcess()){
      return 0;
    }
    float finalGrade = 0;
    
    int foundTonic = -1;
    
    int[] foundExpanding = new int[this.expandingNotes.length];
    int stringDepth=0;
    
    for (int string = 0; string < this.tuning.length; string++) {
      // we have to go string-by-string because of the octave
      Iterator it = stringValueCombination.iterator();
      StringValue current = null;
      boolean found=false;
      
      while (it.hasNext() && !found) {
        StringValue sv = (StringValue) it.next();
        if (sv.getString() == string &&!found && sv.getFret()!=-1) { // stumbled upon next string
          current = sv;
          found=true;
          stringDepth++;
        }
      }
      
      // grade algorithms----
      if (current != null) {
        // search for tonic
        if (foundTonic==-1 && current.getRequiredNoteIndex()==0)
          foundTonic=this.tuning[current.getString()]+current.getFret();
        
        // specific bass not in bass?
        if (stringDepth>1) {
          if (current.getRequiredNoteIndex()==this.BASS_INDEX)  
            finalGrade -= ChordSettings.instance().getGoodChordSemanticsGrade();
          
          if (current.getRequiredNoteIndex()<0) { // expanding tones
            // expanding tone found before the tonic
            if (foundTonic==-1)
              finalGrade -= ChordSettings.instance().getGoodChordSemanticsGrade()/2;
            else {
              // if expanding note isn't higher than tonic's octave
              if (foundTonic+11 > this.tuning[current.getString()]+current.getFret())
                finalGrade -= ChordSettings.instance().getGoodChordSemanticsGrade()/3;
            }
            
            // search for distinct expanding notes
            for (int i=0; i<this.expandingNotes.length; i++)
              if ((this.tuning[string]+current.getFret())%12==(this.chordTonic+this.expandingNotes[i]-1)%12)
                if (foundExpanding[i]==0)
                  foundExpanding[i]=current.getRequiredNoteIndex();
            
          }
        }
      }
    }
    
    // penalties for not founding an expanding note
    if (this.alteration!=0) {
      int essentials=0, nonEssentials=0;
      for (int i=0; i<foundExpanding.length; i++) {
        if (foundExpanding[i]==this.ESSENTIAL_INDEX)
          essentials++;
        else
          if (foundExpanding[i]!=0)
            nonEssentials++;
      }
      
      if (essentials+nonEssentials==this.expandingNotes.length)
        finalGrade+=ChordSettings.instance().getGoodChordSemanticsGrade();
      else {
        if (essentials==2) // if all essentials are there, it's good enough
          finalGrade+=ChordSettings.instance().getGoodChordSemanticsGrade()/2;
        
        // but if some are missing, that's BAD:
        else {
          finalGrade+= (essentials+nonEssentials-this.expandingNotes.length)*ChordSettings.instance().getGoodChordSemanticsGrade();
          // half of the penalty for non-essential notes
          finalGrade+= nonEssentials*ChordSettings.instance().getGoodChordSemanticsGrade()/2;
        }
      }
    }
    
    return finalGrade;
    
  }
  
  /**
   *  If current StringValue is a subset or superset of already better ranked
   *  chords, it shouldn't be put inside, because it is duplicate.
   *  @param stringValues current StringValue to be examined
   *  @param betterOnes ArrayList of already stored StringList chords
   *  @return true if it is duplicate, false if it is unique
   */
  private boolean checkIfSubset(List stringValues, List betterOnes) {
    if(!isValidProcess()){
      return false;
    }
    Iterator it = betterOnes.iterator();
    while (it.hasNext()) {
      List currentStringValue = (List)it.next();
      boolean foundDifferentFret = false;
      // repeat until gone through all strings, or found something different
      for (int i=0; i<currentStringValue.size(); i++) {
        int currentString = ((ChordCreatorUtil.StringValue)currentStringValue.get(i)).getString() ;
        // search for the same string - if not found do nothing
        for (int j=0; j<stringValues.size(); j++)
        if ( ((ChordCreatorUtil.StringValue)stringValues.get(j)).getString() == currentString) {
          // if the frets on the same string differ, then chords are not subset/superset of each other
          if (((ChordCreatorUtil.StringValue)stringValues.get(j)).getFret() != ((ChordCreatorUtil.StringValue)currentStringValue.get(i)).getFret())
            foundDifferentFret=true;
        }
        
      }
      if (!foundDifferentFret)// nothing is different
        return true;
    }
    
    return false;
  }
  
  /**
   * contains information about the note: string, fret and tone function in a
   * chord
   * 
   * @author julian
   * 
   */
  
  private class StringValue {
    
    protected int string;
    protected int fret;
    protected int requiredNoteIndex;
    
    public StringValue(int string, int fret, int requiredNoteIndex) {
      this.string = string;
      this.fret = fret;
      this.requiredNoteIndex = requiredNoteIndex;
      
    }
    
    public int getString() {
      return this.string;
    }
    
    public int getFret() {
      return this.fret;
    }
    
    public int getRequiredNoteIndex() {
      return this.requiredNoteIndex;
    }
  }
  
  /** used just to sort StringValue ArrayLists by priorities */
  protected class PriorityItem {
    
    ArrayList stringValues;
    float priority;
    
  }
  
  /** used to sort the array */
  protected class PriorityComparator implements Comparator {
    
    public int compare(Object o1, Object o2) {
      return Math.round(((PriorityItem) o2).priority - ((PriorityItem) o1).priority);
    }
    
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.