1 /*
2
3 * Created on 31-dic-2005
4
5 * Redesigned on 05-March-2007
6
7 *
8
9 */
10
11 package org.herac.tuxguitar.gui.editors.chord;
12
13 import java.util.ArrayList;
14 import java.util.Collections;
15
prepare_cdb(name, target_dir)16 import java.util.Comparator;
17
18 import java.util.Iterator;
19
20 import java.util.List;
21
22 import org.herac.tuxguitar.gui.TuxGuitar;
23
24 import org.herac.tuxguitar.song.models.TGChord;
25
26 /**
27 *
28 * Class that helps to create a chord from information put in ChordSelector
29 * dialog.
30 *
run_analyzer(directory, cdb, args)31 * Also contains ChordDatabase static field.
32 *
33 * @author Nikola Kolarovic
34 *
35 * @author julian
36 *
37 */
38 public class ChordCreatorUtil {
39
40 /**
41 * Maximum number of strings variable - has twin in TrackPropertiesAction
42 * class
43 */
44 public static final int MAX_STRINGS = 7;
45
46 /** Maximum fret distance for a chord */
47
48 public static final int MAX_FRET_SPAN = 5;
49
50 /** mark for bass note type **/
51 private final int BASS_INDEX = -1;
52
53 /** mark for essential note in a chord - MUST be in */
54 private final int ESSENTIAL_INDEX = -2;
55
56 /** mark for essential note in a chord - PENALTY if not in */
57 private final int NOT_ESSENTIAL_INDEX = -3;
58
59 /** Keep the Thread control */
60 private static long runningProcess;
61
62 // ------ attributes ------
63
64 //protected ChordInfo info;
65 private long processId;
66
67 private ChordCreatorListener listener;
68
69 /** the alteration List selectionIndex */
70 private int alteration;
71
72 private int chordIndex;
73
74 /** essential notes for the chord (from ChordInfo) */
75 private int[] requiredNotes;
76
77 /** notes that expand the chord (add+-) */
78 private int[] expandingNotes;
79
80 /** is the fifth altered */
81 private int add5 = 0;
82
83 /** name of a chord */
84 private String chordName = null;
85
86 private int bassTonic;
87
88 private int chordTonic;
89
90 /** current tunning */
91 private int[] tuning;
92
93 private ChordCreatorUtil(long processId,ChordCreatorListener listener){
94 this.processId = processId;
95 this.listener = listener;
96 }
97
98 public boolean isValidProcess(){
99 return (this.processId == runningProcess);
100 }
101
102 public static long getNewProcess(){
103 return (++ runningProcess);
104 }
105
106 public static void getChords(final ChordCreatorListener listener,
107 final int[] tuning,
108 final int chordIndex,
109 final int alteration,
110 final int plusMinus,
111 final boolean add,
112 final int add5,
113 final int add9,
114 final int add11,
115 final int bassTonic,
116 final int chordTonic,
117 final boolean sharp){
118
119 final ChordCreatorUtil chordCreator = new ChordCreatorUtil(getNewProcess(), listener );
120 new Thread(new Runnable() {
121 public void run() {
122 chordCreator.getChords( tuning, chordIndex, alteration, plusMinus, add, add5, add9, add11, bassTonic, chordTonic, sharp);
123 }
124 }).start();
125 }
126
127 protected void getChords(int[] tuning,
128 int chordIndex,
129 int alteration,
130 int plusMinus,
131 boolean add,
132 int add5,
133 int add9,
134 int add11,
135 int bassTonic,
136 int chordTonic,
137 boolean sharp) {
138
139 if(!isValidProcess()){
140 return;
141 }
142
143 this.add5 = add5;
144
145 this.tuning = tuning;
146
147 this.chordIndex = chordIndex;
148
149 this.chordTonic = chordTonic;
150
151 this.bassTonic = bassTonic;
152
153 this.alteration = alteration;
154
155 this.chordName = new ChordNamingConvention().createChordName(this.chordTonic,
156 this.chordIndex,
157 this.alteration,
158 plusMinus,
159 add,
160 add5,
161 add9,
162 add11,
163 this.bassTonic,
164 sharp);
165
166
167 // find the notes that expand the chord
168 if (this.alteration!=0) {
169 if (add) {
170 this.expandingNotes = new int[1];
171 this.expandingNotes[0]= getAddNote(this.alteration-1,plusMinus);
172 }
173 else { // not just add...
174 // 9+- = 7b !9(+-) (index=1)
175 // 11+- = 7b !11(+-) 9(+-) (index=2)
176 // 13+- = 7b !13(+-) 9(+-) 11(+-) (index=3)
177 this.expandingNotes = new int[1+this.alteration];
178 this.expandingNotes[0] = 11; //7b
179 this.expandingNotes[1] = getAddNote(this.alteration-1,plusMinus); //this.alteration+-
180
181 // rest
182 for (int i=2; i<=this.alteration; i++)
183 this.expandingNotes[i]=getAddNote(i-2, i==2 ? add9 : add11); // @2=add9+-, @3=add11+- tone
184 }
185
186 }
187 else this.expandingNotes=new int[0];
188
189
190
191 // Required notes
192 //this.requiredNotes = ((ChordDatabase.ChordInfo)new ChordDatabase().getChords().get(chordIndex)).cloneRequireds();
193 this.requiredNotes = ChordDatabase.get(chordIndex).cloneRequireds();
194 //IT DON'T BUILD UNDER JRE1.4
195 //this.requiredNotes = ((ChordDatabase.ChordInfo) ChordCreatorUtil.getChordData().getChords().get(chordIndex)).getRequiredNotes().clone();
196
197
198 // adjust the subdominant if needed
199 if (add5!=0) {
200 for (int i=0; i<this.requiredNotes.length; i++)
201 if (this.requiredNotes[i]==8) // alternate the subdominant
202 this.requiredNotes[i]+=(add5==1 ? 1 : -1);
203 }
204
205 // first count different from -1
206 int count = 0;
207 for (int i=0; i<this.requiredNotes.length; i++) {
208 this.requiredNotes[i]=checkForOverlapping(this.requiredNotes[i]);
209 if (this.requiredNotes[i]!=-1)
210 count++;
211 }
212 // then fill in the new array
213 int[] tempNotes = new int[count];
214 count = 0;
215 for (int i=0; i<this.requiredNotes.length; i++)
216 if (this.requiredNotes[i]!=-1) {
217 tempNotes[count]=this.requiredNotes[i];
218 count++;
219 }
220 // and substitute them
221 this.requiredNotes = tempNotes;
222
223 //return getChords();
224 if(isValidProcess()){
225 List chords = getChords();
226 if(chords != null && isValidProcess()){
227 this.listener.notifyChords(this, chords);
228 }
229 }
230 }
231
232 /** We have to make sure that if required note is already inside
233 * expanding notes array so we don't put it twice...
234 */
235 protected int checkForOverlapping(int checkIt) {
236 for (int i=0; i<this.expandingNotes.length; i++)
237 if (this.expandingNotes[i]==checkIt)
238 return -1;
239 return checkIt;
240 }
241
242 /**
243 *
244 * Creates the chord combinations out of given data and then uses some kind
245 * of
246 *
247 * heuristics to check the most suitable formations.
248 *
249 * @return the list of TGChord structures that are most suitable
250 *
251 */
252 private java.util.List getChords() {
253 if(!isValidProcess()){
254 return null;
255 }
256 ArrayList potentialNotes = makePotentialNotes();
257
258 ArrayList combinations = makeCombinations( potentialNotes);
259
260 ArrayList priorities = determinePriority( combinations);
261
262 ArrayList theBestOnes = takeBest( priorities);
263
264 return createChords( theBestOnes);
265 }
266
267 /**
268 * Creates the TGChord ArrayList out of StringValue's ArrayLists
269 *
270 * @param Highest rated StringValues
271 * @return TGChord collection
272 */
273 private ArrayList createChords(ArrayList top) {
274 if(!isValidProcess()){
275 return null;
276 }
277
278 ArrayList chords = new ArrayList(top.size());
279
280 Iterator it = top.iterator();
281
282 while (it.hasNext()) {
283 TGChord chord = TuxGuitar.instance().getSongManager().getFactory()
284 .newChord(this.tuning.length);
285 Iterator it2 = ((ArrayList) it.next()).iterator();
286
287 while (it2.hasNext()) {
288 StringValue stringValue = (StringValue) it2.next();
289 int string = ((chord.getStrings().length - 1) - (stringValue.getString()));
290 int fret = stringValue.getFret();
291 chord.addFretValue(string, fret);
292 chord.setName(this.chordName);
293 }
294
295 chords.add(chord);
296 }
297 return chords;
298 }
299
300 /**
301 *
302 * If string/fret combination is needed for the chord formation, add it into
303 * List
304 *
305 * @return true if the note is needed for chord formation
306 *
307 */
308 private void find(int stringTone, int stringIndex, int fret,List stringList){
309 if(!isValidProcess()){
310 return;
311 }
312 boolean bassAlreadyIn=false;
313 // chord base notes
314 for (int i = 0; i < this.requiredNotes.length; i++)
315 if ((stringTone + fret) % 12 == (this.chordTonic+this.requiredNotes[i] - 1) % 12) {
316 if (!bassAlreadyIn && (stringTone + fret) % 12 == this.bassTonic)
317 bassAlreadyIn=true;
318 stringList.add(new StringValue(stringIndex, fret, i));
319 return;
320 }
321
322 // alterations
323 if (this.expandingNotes.length!=0) {
324 for (int i=0; i<this.expandingNotes.length; i++) {
325 if ((stringTone+fret)%12==(this.chordTonic+this.expandingNotes[i]-1)%12) {
326 if (!bassAlreadyIn && (stringTone + fret) % 12 == this.bassTonic)
327 bassAlreadyIn=true;
328 stringList.add(new StringValue(stringIndex,fret,(i<2 ? this.ESSENTIAL_INDEX : this.NOT_ESSENTIAL_INDEX)));
329 }
330 }
331 }
332
333 // bass tone
334 if (!bassAlreadyIn)
335 if ((stringTone + fret) % 12 == this.bassTonic) {
336 stringList.add(new StringValue(stringIndex, fret, this.BASS_INDEX));
337 return;
338 }
339
340 }
341
342 /**
343 * Returns the wanted note for ADD chord
344 *
345 * @param type
346 * 0==add9; 1==add11; 2==add13;
347 * @param selectionIndex
348 * index of selected item in the List combo
349 * @return wanted note, or -1 if nothing was selected
350 *
351 */
352 private int getAddNote(int type, int selectionIndex) {
353
354 int wantedNote = 0;
355
356 switch (type) {
357 case 0:
358 wantedNote = 3; // add9
359 break;
360 case 1:
361 wantedNote = 6; // add11
362 break;
363 case 2:
364 wantedNote = 10; // add13
365 break;
366 }
367
368 switch (selectionIndex) {
369 case 1:
370 wantedNote++;
371 break;
372 case 2:
373 wantedNote--;
374 break;
375 default:
376 break;
377 }
378
379 return wantedNote;
380 }
381
382 private ArrayList makePotentialNotes(){
383 if(!isValidProcess()){
384 return null;
385 }
386 ArrayList potentialNotes = new ArrayList(this.tuning.length);
387
388 for (int string = 0; string < this.tuning.length; string++) {
389
390 ArrayList currentStringList = new ArrayList(10);
391
392 // search all the frets
393
394 if (ChordSettings.instance().getFindChordsMin()>0 && ChordSettings.instance().isEmptyStringChords())
395 find(this.tuning[string], string, 0, currentStringList); // if it's open chord but wanted to search from different minimal fret
396
397
398 for (int fret = ChordSettings.instance().getFindChordsMin(); fret <= ChordSettings.instance().getFindChordsMax(); fret++) {
399 // put in all the needed notes
400 find(this.tuning[string], string, fret, currentStringList);
401 }
402
403 potentialNotes.add(currentStringList);
404
405 }
406 return potentialNotes;
407 }
408
409 /**
410 *
411 * Makes the all-possible combinations of found notes that can be reached by
412 * fingers
413 *
414 * @param potentialNotes
415 * list consisted of found notes for each string
416 *
417 * @return list of list of StringValues, with tones that can form a chord
418 *
419 */
420 private ArrayList makeCombinations(ArrayList potentialNotes) {
421 if(!isValidProcess()){
422 return null;
423 }
424
425 // COMBINATIONS of strings used in a chord
426 ArrayList stringCombination = new ArrayList(60);
427 ArrayList lastLevelCombination = null;
428
429 for (int i = 0; i < this.tuning.length - 1; i++)
430 {
431 lastLevelCombination = makeStringCombination(lastLevelCombination);
432
433 // lastLevelCombination after 3rd round: [[0, 1, 2, 3], [0, 1, 2,
434 // 4], [0, 1, 3, 4], [0, 2, 3, 4], [1, 2, 3, 4], [0, 1, 2, 5], [0,
435 // 1, 3, 5], [0, 2, 3, 5], [1, 2, 3, 5], [0, 1, 4, 5], [0, 2, 4, 5],
436 // [1, 2, 4, 5], [0, 3, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
437
438 stringCombination.addAll(lastLevelCombination);
439 }
440
441 ArrayList combinations = new ArrayList(800);
442
443 // --- combine the StringValues according to strings combination
444 // ----------------------=======
445
446 Iterator iterator = stringCombination.iterator();
447
448 while (iterator.hasNext()) { // go through all string combinations list
449 // take a string combinations
450 ArrayList currentStringCombination = (ArrayList) iterator.next();
451 lastLevelCombination = null;
452
453 // go through all the strings in one combination
454 for (int level = 0; level < currentStringCombination.size(); level++) {
455
456 // take the string index
457 int currentString = ((Integer) currentStringCombination.get(level)).intValue();
458
459 // take all the potential notes from currentString and combine
460 // them with potential notes from other strings
461
462 lastLevelCombination = makeStringValueCombination(lastLevelCombination,(ArrayList)potentialNotes.get(currentString));
463
464 // the structure of combinations is AL { AL(StringValue,SV,SV),
465 // AL(SV), AL(SV,SV),AL(SV,SV,SV,SV,SV,SV) }
466
467 }
468
469 if(lastLevelCombination != null){
470 combinations.addAll(lastLevelCombination);
471 }
472 }
473
474 return combinations;
475 }
476
477 /**
478 * Makes a combination of string indices
479 *
480 * @param lastLevelCombination
481 * structure to be expanded by current level
482 *
483 * @return structure of stringCombination is AL { AL(0), AL(0,1),
484 * AL(0,2),AL(0,1,3,4),AL(0,1,2,3,4,5) }
485 */
486 private ArrayList makeStringCombination(ArrayList lastLevelCombinationRef){
487 if(!isValidProcess()){
488 return null;
489 }
490
491 List lastLevelCombination = lastLevelCombinationRef;
492
493 if (lastLevelCombination == null) {
494 // first combination is AL { AL(0), AL(1), AL(2), AL(3), AL(4),
495 // ...AL(tuning.length) }
496 lastLevelCombination = new ArrayList();
497
498 for (int i = 0; i < this.tuning.length; i++) {
499 lastLevelCombination.add(new ArrayList());
500 ((ArrayList) lastLevelCombination.get(i)).add(new Integer(i));
501 }
502 }
503
504 ArrayList thisLevelCombination = new ArrayList();
505 for (int current = 1; current < this.tuning.length; current++)
506 {
507 Iterator it = lastLevelCombination.iterator();
508
509 while (it.hasNext()) {
510 ArrayList combination = (ArrayList) it.next();
511 Integer currentInteger = new Integer(current);
512 if (((Integer) combination.get(combination.size() - 1))
513 .intValue() < current
514 && !combination.contains(currentInteger)) {
515
516 // check if the string is already in combination
517 ArrayList newCombination = (ArrayList) combination.clone();
518 newCombination.add(currentInteger);
519 thisLevelCombination.add(newCombination);
520 }
521
522 }
523
524 }
525
526 return thisLevelCombination;
527
528 }
529
530 /**
531 * Makes a combination of notes by multiplying last combination and current
532 * note arrays
533 *
534 *
535 *
536 * @param lastLevelCombination
537 * structure to be expanded by current level
538 *
539 * @param notes
540 * notes that can be considered into making a chord
541 *
542 * @return structure of StringValue combinations : AL {
543 * AL(StringValue,SV,SV), AL(SV), AL(SV,SV),AL(SV,SV,SV,SV,SV,SV) }
544 *
545 */
546 private ArrayList makeStringValueCombination(ArrayList lastLevelCombination, ArrayList notes) {
547 if(!isValidProcess()){
548 return null;
549 }
550 ArrayList thisLevelCombination = null;
551
552 if (lastLevelCombination == null) { // initial combination
553
554 thisLevelCombination = new ArrayList(notes.size());
555
556 for (int i = 0; i < notes.size(); i++) {
557
558 thisLevelCombination.add(new ArrayList(6));
559
560 ((ArrayList) thisLevelCombination.get(i)).add(notes.get(i));
561
562 }
563
564 // first combination is AL { AL(firstOne), AL(anotherFret) }
565
566 }
567
568 else {
569
570 thisLevelCombination = new ArrayList();
571
572 for (int i = 0; i < notes.size(); i++)
573 for (int j = 0; j < lastLevelCombination.size(); j++) { // cartesian multiplication
574 ArrayList currentCombination = (ArrayList) ((ArrayList) lastLevelCombination.get(j)).clone();
575 currentCombination.add(notes.get(i));
576
577 // if the distance maximum between the existing frets
578 // is less than wanted, add it into potential list
579
580 if (checkCombination(currentCombination))
581 thisLevelCombination.add(currentCombination);
582
583 }
584 }
585
586 return thisLevelCombination;
587 }
588
589 /**
590 * Checks if the combination can be reached by fingers. It is reachable
591 *
592 * if the distance between lowest and highest fret is less than
593 *
594 * <i>ChordCreatorUtil.MAX_FRET_SPAN</i>.
595 *
596 * Also note that this method eliminates or includes the chords with empty
597 * strings,
598 *
599 * which is controlled with <i>boolean ChordCreatorUtil.EMPTY_STRING_CHORDS</i>
600 *
601 * @param combination
602 * current combination to be examined
603 *
604 * @return true if it can be reached
605 *
606 */
607 private boolean checkCombination(ArrayList combination) {
608
609 Iterator it = combination.iterator();
610 int maxLeft, maxRight;
611
612 maxLeft = maxRight = ((StringValue) combination.get(0)).getFret();
613
614 while (it.hasNext()) {
615
616 int fret = ((StringValue) it.next()).getFret();
617
618 //chords with empty-string are welcome
619 if (fret != 0 || !ChordSettings.instance().isEmptyStringChords()) {
620
621 if (fret < maxLeft)
622 maxLeft = fret;
623
624 if (fret > maxRight)
625 maxRight = fret;
626
627 }
628
629 }
630
631 if (Math.abs(maxLeft - maxRight) >= MAX_FRET_SPAN)
632
633 return false;
634
635 return true;
636
637 }
638
639 /**
640 * orders the StringValue ArrayList by their priority, calculated here
641 *
642 * for every single chord combination.<br>
643 *
644 * Priority is higher if:<br>
645 * - tone combination has all notes required for the chord basis<br>
646 * - has good chord semantics uses many basic tones, and all necessary
647 * tones in their place<br>
648 * - tone combination has all subsequent strings (no string skipping)<br>
649 * - has a chord bass tone as lowest tone<br>
650 * - uses more strings<br>
651 * - uses good fingering positions<br>
652 *
653 * @param allCombinations
654 * all the StringValue combinations that make some sense
655 *
656 * @return Treemap of the StringValue ArrayLists, in which the key is
657 *
658 * <i>float priority</i>.
659 *
660 */
661 private ArrayList determinePriority(ArrayList allCombinations) {
662 if(!isValidProcess()){
663 return null;
664 }
665 ArrayList ordered = new ArrayList();
666
667 Iterator it = allCombinations.iterator();
668
669 while (it.hasNext() && isValidProcess()) {
670
671 float priority = 0;
672
673 ArrayList stringValueCombination = (ArrayList) it.next();
674
675 // tone combination has all notes required for the chord basis
676
677 priority += combinationHasAllRequiredNotes(stringValueCombination);
678
679 // uses good chord semantics
680
681 priority += combinationChordSemantics(stringValueCombination);
682
683 // tone combination has all subsequent strings (no string skipping)
684
685 priority += combinationHasSubsequentStrings(stringValueCombination);
686
687 // has a chord bass tone as lowest tone
688
689 priority += combinationBassInBass(stringValueCombination);
690
691 // uses many strings
692 // 4 and less strings will be more praised in case of negative grade
693 // 4 and more strings will be more praised in case of positive grade
694 priority += ChordSettings.instance().getManyStringsGrade() / 3
695 * (stringValueCombination.size()-this.tuning.length /
696 (ChordSettings.instance().getManyStringsGrade()>0 ? 2 : 1.2) );
697
698 // uses good fingering positions
699
700 priority += combinationHasGoodFingering(stringValueCombination);
701
702 // System.out.println("OVERALL:
703 // "+priority+"----------------------------");
704
705 PriorityItem item = new PriorityItem();
706
707 item.priority = priority;
708
709 item.stringValues = stringValueCombination;
710
711 ordered.add(item);
712
713 }
714
715 return ordered;
716
717 }
718
719 /**
720 *
721 * Takes the StringValue ArrayLists that has the best priority rating
722 *
723 */
724 private ArrayList takeBest(ArrayList priorityItems) {
725 if(!isValidProcess()){
726 return null;
727 }
728
729 int maximum = ChordSettings.instance().getChordsToDisplay();
730
731 ArrayList bestOnes = new ArrayList(maximum);
732
733 Collections.sort(priorityItems, new PriorityComparator());
734 for(int i = 0; i < priorityItems.size() && isValidProcess(); i ++){
735 PriorityItem item = (PriorityItem)priorityItems.get(i);
736 if (!checkIfSubset(item.stringValues, bestOnes) ){
737 bestOnes.add(item.stringValues);
738
739 if( bestOnes.size() >= maximum ){
740 break;
741 }
742 }
743 }
744
745 return bestOnes;
746
747 }
748
749 /** adds points if the combination has all the notes in the basis of chord */
750 private float combinationHasAllRequiredNotes(ArrayList stringValueCombination) {
751 if(!isValidProcess()){
752 return 0;
753 }
754 Iterator it = stringValueCombination.iterator();
755 int[] values = new int[this.requiredNotes.length];
756 int currentIndex = 0;
757
758 while (it.hasNext()) {
759 StringValue sv = (StringValue) it.next();
760
761 if (sv.getRequiredNoteIndex() >= 0) { // only basis tones
762 boolean insert = true;
763
764 for (int i = 0; i < currentIndex; i++)
765 if (values[i] == sv.getRequiredNoteIndex() + 1)
766 insert = false;
767
768 // sv.requiredNoteIndex+1, because we have index 0 and we don't
769 // want it inside
770
771 if (insert) {
772 values[currentIndex] = sv.getRequiredNoteIndex() + 1;
773 currentIndex++;
774 }
775
776 }
777 }
778
779 if (currentIndex == this.requiredNotes.length) {
780 return ChordSettings.instance().getRequiredBasicsGrade();
781 }
782
783 if (currentIndex == this.requiredNotes.length - 1) {
784
785 boolean existsSubdominant = false;
786
787 Iterator it2 = stringValueCombination.iterator();
788 while (it2.hasNext()) {
789 StringValue current = (StringValue)it2.next();
790 if ((this.tuning[current.getString()] + current.getFret()) % 12 == (this.chordTonic + 7) %12)
791 existsSubdominant = true;
792 }
793
794 if (!existsSubdominant && currentIndex == this.requiredNotes.length-1) {
795 // if not riff. "sus" chord, or chord with altered fifth allow chord without JUST subdominant (fifth) with small penalty
796
797 //if ( !((ChordInfo)new ChordDatabase().getChords().get(this.chordIndex)).getName().contains("sus") && this.requiredNotes.length!=2 && this.add5==0) {
798 //String.contains(String) is not available at JRE1.4
799 //Replaced by "String.indexOf(String) >= 0"
800 if ( ChordDatabase.get(this.chordIndex).getName().indexOf("sus") >= 0 && this.requiredNotes.length != 2 && this.add5 == 0) {
801 return ( ChordSettings.instance().getRequiredBasicsGrade() * 4 / 5 );
802 }
803 }
804
805 }
806
807 // required notes count should decrease the penalty
808 int noteCount = (this.alteration == 0 ? 0 : 1+ this.alteration)+currentIndex+ (this.bassTonic == this.chordTonic ? 0 : 1);
809
810 // sometimes, when noteCount is bigger then tunning length, this pennalty will become positive, which may help
811 return -ChordSettings.instance().getRequiredBasicsGrade()
812 * (this.tuning.length - noteCount) / this.tuning.length * 2;
813
814 }
815
816 /** adds points if the combination has strings in a row */
817 private float combinationHasSubsequentStrings(ArrayList stringValueCombination) {
818 if(!isValidProcess()){
819 return 0;
820 }
821 boolean stumbled = false, noMore = false, penalty = false;
822
823 for (int i = 0; i < this.tuning.length; i++) {
824 boolean found = false;
825 Iterator it = stringValueCombination.iterator();
826 while (it.hasNext())
827 if (((StringValue) it.next()).getString() == i)
828 found = true;
829 if (found) {
830 if (!stumbled)
831 stumbled = true;
832 if (noMore)
833 penalty = true;
834 if (penalty) // penalty for skipped strings
835 return -ChordSettings.instance().getSubsequentGrade();
836 }
837 else
838 if (stumbled)
839 noMore = true;
840 }
841
842 if (penalty)
843 return 0.0f;
844
845 return ChordSettings.instance().getSubsequentGrade();
846 }
847
848 /** checks if the bass tone is the lowest tone in chord */
849 private float combinationBassInBass(ArrayList stringValueCombination) {
850 if(!isValidProcess()){
851 return 0;
852 }
853 for (int i = 0; i < this.tuning.length; i++) {
854
855 Iterator it = stringValueCombination.iterator();
856
857 while (it.hasNext()) {
858 StringValue sv = (StringValue) it.next();
859
860 if (sv.getString() == i) { // stumbled upon lowest tone
861 if ( (this.tuning[sv.getString()]+sv.getFret()) % 12 == this.bassTonic )
862 return ChordSettings.instance().getBassGrade();
863 // else
864 return -ChordSettings.instance().getBassGrade();
865 }
866 }
867
868 }
869
870 return 0;
871 }
872
873 /**
874 * grades the fingering in a chord.
875 *
876 * fingering is good if:<br>
877 * - uses as little as possible fret positions<br>
878 * - uses less than 3 fret positions<br>
879 * - distributes good among fingers<br>
880 * - can be placed capo <br>
881 *
882 */
883 private float combinationHasGoodFingering(ArrayList stringValueCombination) {
884 if(!isValidProcess()){
885 return 0;
886 }
887 // init: copy into simple array
888 float finalGrade = 0;
889 int[] positions = new int[this.tuning.length];
890 for (int i = 0; i < this.tuning.length; i++)
891 positions[i] = -1;
892 {
893 Iterator it = stringValueCombination.iterator();
894
895 while (it.hasNext()) {
896 StringValue sv = (StringValue) it.next();
897 positions[sv.getString()] = sv.getFret();
898 }
899 }
900 // algorithm
901
902 // distance between fingers
903 int min = ChordSettings.instance().getFindChordsMax()+2, max = 0, maxCount=0;
904 boolean openChord = false, zeroString = false;
905
906 for (int i = 0; i < this.tuning.length; i++) {
907
908 openChord|= ChordSettings.instance().isEmptyStringChords() && positions[i] == 0;
909 zeroString |= positions[i]==0;
910
911 if (positions[i] < min && positions[i] != 0 && positions[i]!=-1)
912 min = positions[i];
913
914 if (positions[i] > max) {
915 max = positions[i];
916 maxCount=1;
917 }
918 else
919 if (positions[i]==max)
920 maxCount++;
921
922 }
923
924 // finger as capo
925
926 int count = 0;
927
928 for (int i = 0; i < this.tuning.length; i++)
929 if (positions[i] == min)
930 count++;
931
932 if (!openChord) {
933 if (zeroString)
934 finalGrade += ChordSettings.instance().getFingeringGrade()/8;
935 else
936 if (count >= 2)
937 finalGrade += ChordSettings.instance().getFingeringGrade()/8;
938 }
939 else
940 if (openChord)
941 finalGrade += ChordSettings.instance().getFingeringGrade()/8;
942
943 // position distance: 1-2 nice 3 good 4 bad 5 disaster
944 float distanceGrade;
945
946 switch(Math.abs(max-min)) {
947 case 0 : distanceGrade=ChordSettings.instance().getFingeringGrade()/5;
948 break;
949 case 1 : distanceGrade=ChordSettings.instance().getFingeringGrade()/(5+maxCount);
950 break;
951 case 2 : distanceGrade=ChordSettings.instance().getFingeringGrade()/(6+maxCount);
952 if (min<5) distanceGrade*=0.9;
953 break;
954 case 3 : distanceGrade=-ChordSettings.instance().getFingeringGrade()/10*maxCount;
955 // I emphasize the penalty if big difference is on some
956 // lower frets (it is greater distance then)
957 if (min<5) distanceGrade*=1.3;
958 break;
959 case 4 : distanceGrade=-ChordSettings.instance().getFingeringGrade()/4*maxCount;
960 if (min<=5) distanceGrade*=1.8;
961 break;
962 default : distanceGrade=-ChordSettings.instance().getFingeringGrade()*maxCount;
963 break;
964 }
965 finalGrade += distanceGrade;
966
967 // ============== finger position abstraction ==================
968 // TODO: what to do with e.g. chord -35556 (C7)
969 // ... it can be held with capo on 5th fret, but very hard :)
970 // ... This is the same as with "capo after", I didn't consider that (e.g. chord -35555)
971 ArrayList[] fingers={new ArrayList(2),new ArrayList(2),new ArrayList(2),new ArrayList(2)};
972 // TODO: still no thumb, sorry :)
973
974 // STRUCTURE: ArrayList consists of Integers - first is fret
975 // - others are strings
976 /*
977 for (int i=0; i<this.tuning.length; i++)
978 System.out.print(" "+positions[i]);
979 System.out.println("");
980 */
981
982 // if chord is open, then we can have capo only in strings before open string
983 int lastZeroIndex = 0;
984
985 if (zeroString)
986 for (int i=0; i<positions.length; i++)
987 if (positions[i]==0) lastZeroIndex=i;
988
989 // open or not not open chord,
990 // index finger is always on lowest fret possible
991 fingers[0].add(new Integer(min));
992
993 for (int i=lastZeroIndex; i<positions.length; i++)
994 if (positions[i]==min) {
995 fingers[0].add(new Integer(i));
996 positions[i]=-1;
997 }
998
999 // other fingers
1000 // if not zero-fret, occupy fingers respectivly
1001 int finger=1;
1002 for (int i=0; i<positions.length; i++) {
1003 if (positions[i]!=0 && positions[i]!=-1) {
1004 if (finger<4) {
1005 fingers[finger].add(new Integer(positions[i]));
1006 fingers[finger].add(new Integer(i));
1007 positions[i]=-1;
1008 }
1009 finger++;
1010 }
1011 }
1012
1013 /* System.out.println("Positions:");
1014 for (int i=0; i<4; i++) {
1015 if (fingers[i].size()>1)
1016 System.out.print("G"+(i+1)+"R"+((Integer)fingers[i].get(0)).intValue()+"S"+((Integer)fingers[i].get(1)).intValue()+" ");
1017 }
1018 */
1019
1020 if (finger>4)
1021 finalGrade-=ChordSettings.instance().getFingeringGrade();
1022 else
1023 finalGrade+=ChordSettings.instance().getFingeringGrade()*0.1*(15-2*finger);
1024
1025 // TODO: maybe to put each finger's distance from the minimum
1026 return finalGrade;
1027
1028 }
1029
1030 /**
1031 * grades the chord semantics, based on theory.
1032 *
1033 * Tone semantics is good if:<br>
1034 * - there appear tones from chord basis or bass tone<br>
1035 * - there appear alteration tones on their specific places<br><br>
1036 *
1037 * Algorithm:<br>
1038 * - search for chord tonic. If some note is found before (and it's not bass) do penalty<br>
1039 * - make penalty if the bass tone is not in bass<br>
1040 * - check if all the expanding notes are here. If some are not, do penalty<br>
1041 * - if expanding note isn't higher than tonic octave, then priority should be less<br>
1042 * - If there are not some with NON_ESSENTIAL_INDEX are not here, penalty should be less<br>
1043 */
1044 private float combinationChordSemantics(ArrayList stringValueCombination) {
1045 if(!isValidProcess()){
1046 return 0;
1047 }
1048 float finalGrade = 0;
1049
1050 int foundTonic = -1;
1051
1052 int[] foundExpanding = new int[this.expandingNotes.length];
1053 int stringDepth=0;
1054
1055 for (int string = 0; string < this.tuning.length; string++) {
1056 // we have to go string-by-string because of the octave
1057 Iterator it = stringValueCombination.iterator();
1058 StringValue current = null;
1059 boolean found=false;
1060
1061 while (it.hasNext() && !found) {
1062 StringValue sv = (StringValue) it.next();
1063 if (sv.getString() == string &&!found && sv.getFret()!=-1) { // stumbled upon next string
1064 current = sv;
1065 found=true;
1066 stringDepth++;
1067 }
1068 }
1069
1070 // grade algorithms----
1071 if (current != null) {
1072 // search for tonic
1073 if (foundTonic==-1 && current.getRequiredNoteIndex()==0)
1074 foundTonic=this.tuning[current.getString()]+current.getFret();
1075
1076 // specific bass not in bass?
1077 if (stringDepth>1) {
1078 if (current.getRequiredNoteIndex()==this.BASS_INDEX)
1079 finalGrade -= ChordSettings.instance().getGoodChordSemanticsGrade();
1080
1081 if (current.getRequiredNoteIndex()<0) { // expanding tones
1082 // expanding tone found before the tonic
1083 if (foundTonic==-1)
1084 finalGrade -= ChordSettings.instance().getGoodChordSemanticsGrade()/2;
1085 else {
1086 // if expanding note isn't higher than tonic's octave
1087 if (foundTonic+11 > this.tuning[current.getString()]+current.getFret())
1088 finalGrade -= ChordSettings.instance().getGoodChordSemanticsGrade()/3;
1089 }
1090
1091 // search for distinct expanding notes
1092 for (int i=0; i<this.expandingNotes.length; i++)
1093 if ((this.tuning[string]+current.getFret())%12==(this.chordTonic+this.expandingNotes[i]-1)%12)
1094 if (foundExpanding[i]==0)
1095 foundExpanding[i]=current.getRequiredNoteIndex();
1096
1097 }
1098 }
1099 }
1100 }
1101
1102 // penalties for not founding an expanding note
1103 if (this.alteration!=0) {
1104 int essentials=0, nonEssentials=0;
1105 for (int i=0; i<foundExpanding.length; i++) {
1106 if (foundExpanding[i]==this.ESSENTIAL_INDEX)
1107 essentials++;
1108 else
1109 if (foundExpanding[i]!=0)
1110 nonEssentials++;
1111 }
1112
1113 if (essentials+nonEssentials==this.expandingNotes.length)
1114 finalGrade+=ChordSettings.instance().getGoodChordSemanticsGrade();
1115 else {
1116 if (essentials==2) // if all essentials are there, it's good enough
1117 finalGrade+=ChordSettings.instance().getGoodChordSemanticsGrade()/2;
1118
1119 // but if some are missing, that's BAD:
1120 else {
1121 finalGrade+= (essentials+nonEssentials-this.expandingNotes.length)*ChordSettings.instance().getGoodChordSemanticsGrade();
1122 // half of the penalty for non-essential notes
1123 finalGrade+= nonEssentials*ChordSettings.instance().getGoodChordSemanticsGrade()/2;
1124 }
1125 }
1126 }
1127
1128 return finalGrade;
1129
1130 }
1131
1132 /**
1133 * If current StringValue is a subset or superset of already better ranked
1134 * chords, it shouldn't be put inside, because it is duplicate.
1135 * @param stringValues current StringValue to be examined
1136 * @param betterOnes ArrayList of already stored StringList chords
1137 * @return true if it is duplicate, false if it is unique
1138 */
1139 private boolean checkIfSubset(List stringValues, List betterOnes) {
1140 if(!isValidProcess()){
1141 return false;
1142 }
1143 Iterator it = betterOnes.iterator();
1144 while (it.hasNext()) {
1145 List currentStringValue = (List)it.next();
1146 boolean foundDifferentFret = false;
1147 // repeat until gone through all strings, or found something different
1148 for (int i=0; i<currentStringValue.size(); i++) {
1149 int currentString = ((ChordCreatorUtil.StringValue)currentStringValue.get(i)).getString() ;
1150 // search for the same string - if not found do nothing
1151 for (int j=0; j<stringValues.size(); j++)
1152 if ( ((ChordCreatorUtil.StringValue)stringValues.get(j)).getString() == currentString) {
1153 // if the frets on the same string differ, then chords are not subset/superset of each other
1154 if (((ChordCreatorUtil.StringValue)stringValues.get(j)).getFret() != ((ChordCreatorUtil.StringValue)currentStringValue.get(i)).getFret())
1155 foundDifferentFret=true;
1156 }
1157
1158 }
1159 if (!foundDifferentFret)// nothing is different
1160 return true;
1161 }
1162
1163 return false;
1164 }
1165
1166 /**
1167 * contains information about the note: string, fret and tone function in a
1168 * chord
1169 *
1170 * @author julian
1171 *
1172 */
1173
1174 private class StringValue {
1175
1176 protected int string;
1177 protected int fret;
1178 protected int requiredNoteIndex;
1179
1180 public StringValue(int string, int fret, int requiredNoteIndex) {
1181 this.string = string;
1182 this.fret = fret;
1183 this.requiredNoteIndex = requiredNoteIndex;
1184
1185 }
1186
1187 public int getString() {
1188 return this.string;
1189 }
1190
1191 public int getFret() {
1192 return this.fret;
1193 }
1194
1195 public int getRequiredNoteIndex() {
1196 return this.requiredNoteIndex;
1197 }
1198 }
1199
1200 /** used just to sort StringValue ArrayLists by priorities */
1201 protected class PriorityItem {
1202
1203 ArrayList stringValues;
1204 float priority;
1205
1206 }
1207
1208 /** used to sort the array */
1209 protected class PriorityComparator implements Comparator {
1210
1211 public int compare(Object o1, Object o2) {
1212 return Math.round(((PriorityItem) o2).priority - ((PriorityItem) o1).priority);
1213 }
1214
1215 }
1216 }