1 package org.herac.tuxguitar.gui.editors.chord;
2 /* Created on 05-March-2007
3 */
4 
5 import java.util.ArrayList;
6 import java.util.Iterator;
7 
8 import org.eclipse.swt.SWT;
9 import org.eclipse.swt.events.SelectionAdapter;
10 import org.eclipse.swt.events.SelectionEvent;
11 import org.eclipse.swt.layout.GridData;
12 import org.eclipse.swt.layout.GridLayout;
13 import org.eclipse.swt.widgets.Composite;
14 import org.eclipse.swt.widgets.List;
15 import org.herac.tuxguitar.song.models.TGChord;
16 import org.herac.tuxguitar.util.TGSynchronizer;
17 
18 /**
19  * @author Nikola Kolarovic
20  *
21  */
22 public class ChordRecognizer extends Composite {
23 
24 	// index for parameter array
25 	protected static final int TONIC_INDEX = 0;
26 	protected static final int CHORD_INDEX = 1;
27 	protected static final int ALTERATION_INDEX = 2;
28 	protected static final int PLUSMINUS_INDEX = 3;
29 	protected static final int BASS_INDEX = 4;
30 	protected static final int ADDCHK_INDEX = 5;
31 	protected static final int I5_INDEX = 6;
32 	protected static final int I9_INDEX = 7;
33 	protected static final int I11_INDEX = 8;
34 
35 	private ChordDialog dialog;
36 	private List proposalList;
37 	private java.util.List proposalParameters;
38 
39 	// this var keep a control to running threads.
40 	private long runningProcess;
41 
ChordRecognizer(ChordDialog dialog, Composite parent,int style)42 	public ChordRecognizer(ChordDialog dialog, Composite parent,int style) {
43 		super(parent,style);
44 		this.setLayout(dialog.gridLayout(1,false,0,0));
45 		this.setLayoutData(makeGridData());
46 		this.runningProcess = 0;
47 		this.dialog = dialog;
48 		this.init();
49 	}
50 
makeGridData()51 	public GridData makeGridData(){
52 		GridData data = new GridData(SWT.FILL,SWT.FILL,true,true);
53 		data.minimumWidth = 180;
54 		return data;
55 	}
56 
init()57 	public void init(){
58 		Composite composite = new Composite(this,SWT.NONE);
59 		composite.setLayout(new GridLayout());
60 		composite.setLayoutData(new GridData(SWT.FILL,SWT.FILL,true,true));
61 
62 		this.proposalParameters = new ArrayList();
63 
64 		this.proposalList = new List(composite,SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
65 		this.proposalList.setLayoutData(new GridData(SWT.FILL,SWT.FILL,true,true));
66 		this.proposalList.addSelectionListener(new SelectionAdapter() {
67 			public void widgetSelected(SelectionEvent e) {
68 				if(getDialog().getEditor() != null){
69 					showChord(getProposalList().getSelectionIndex());
70 				}
71 			}
72 		});
73 
74 	}
75 
76 	/** sets the current chord to be selected proposal */
showChord(int index)77 	protected void showChord(int index) {
78 		int[] params = (int[])this.proposalParameters.get(index);
79 		this.dialog.getSelector().adjustWidgets(params[TONIC_INDEX],
80 		                                        params[CHORD_INDEX],
81 		                                        params[ALTERATION_INDEX],
82 		                                        params[BASS_INDEX],
83 		                                        params[PLUSMINUS_INDEX],
84 		                                        params[ADDCHK_INDEX],
85 		                                        params[I5_INDEX],
86 		                                        params[I9_INDEX],
87 		                                        params[I11_INDEX]);
88 		String chordName = this.proposalList.getItem(index);
89 		chordName = chordName.substring(0, chordName.indexOf('(')-1);
90 		this.dialog.getEditor().getChordName().setText(chordName);
91 		this.dialog.getEditor().redraw();
92 	}
93 
94 	/**
95 	 * - Recognizes the chord string
96 	 * - Fills the component's list with alternative names
97 	 * - Sets all the ChordSelector fields into recognized chord (tonic, bass, chord, alterations)
98 	 * - Makes the alternatives and puts them into ChordList
99 	 * - Writes the chord formula into appropriate label
100 	 *  @param chord chord structure (frets, strings)
101 	 *  @param redecorate is the Chord Editor in editing mode, or it is just changed by ChordSelector
102 	 */
103 
recognize(final TGChord chord,final boolean redecorate,final boolean setChordName)104 	public void recognize(final TGChord chord,final boolean redecorate,final boolean setChordName) {
105 
106 		final long processId = (++ this.runningProcess);
107 		final boolean sharp = this.dialog.getSelector().getSharpButton().getSelection();
108 
109 		this.clearProposals();
110 
111 		new Thread( new Runnable() {
112 			public void run() {
113 				if(!getDialog().isDisposed() && isValidProcess(processId)){
114 
115 					final int params[] = makeProposals(processId, chord,sharp);
116 
117 					if (params == null) { // could not recognize anything!?
118 						if (isValidProcess(processId) && setChordName) {
119 							try {
120 								TGSynchronizer.instance().addRunnable(new TGSynchronizer.TGRunnable() {
121 									public void run() {
122 										if(!getDialog().isDisposed() && isValidProcess(processId)){
123 											getDialog().getEditor().setChordName("");
124 										}
125 									}
126 								});
127 							} catch (Throwable e) {
128 								e.printStackTrace();
129 							}
130 						}
131 						return;
132 					}
133 
134 					final String chordName = getChordName(params,sharp);
135 
136 					// Sets all the ChordSelector fields into recognized chord (tonic, bass, chord, alterations)
137 					if (isValidProcess(processId) && redecorate) {
138 						try {
139 							TGSynchronizer.instance().addRunnable(new TGSynchronizer.TGRunnable() {
140 								public void run() {
141 									if(!getDialog().isDisposed()){
142 										redecorate(params);
143 									}
144 								}
145 							});
146 						} catch (Throwable e) {
147 							e.printStackTrace();
148 						}
149 					}
150 
151 					if (isValidProcess(processId) && setChordName) {
152 						try {
153 							TGSynchronizer.instance().addRunnable(new TGSynchronizer.TGRunnable() {
154 								public void run() {
155 									if(!getDialog().isDisposed()){
156 										getDialog().getEditor().setChordName( (chordName != null ? chordName : "" ) );
157 									}
158 								}
159 							});
160 						} catch (Throwable e) {
161 							e.printStackTrace();
162 						}
163 					}
164 				}
165 			}
166 		} ).start();
167 	}
168 
169 	/** Fills the component's list with alternative names
170 	 * @param chord TGChord to be recognized
171 	 * @return parameters for adjustWidgets and getChordName methods
172 	 */
makeProposals(final long processId, TGChord chord,final boolean sharp)173 	protected int[] makeProposals(final long processId, TGChord chord,final boolean sharp) {
174 
175 		int[] tuning = this.dialog.getSelector().getTuning();
176 		java.util.List notesInside = new ArrayList();
177 
178 		// find and put in all the distinct notes
179 		for (int i=0; i<tuning.length; i++) {
180 			int fret = chord.getStrings()[i];
181 			if (fret!=-1) {
182 				Integer note = new Integer((tuning[tuning.length-1-i] + fret) % 12);
183 				Iterator it = notesInside.iterator();
184 				boolean found=false;
185 				while (it.hasNext())
186 					if (it.next().equals(note))
187 						found=true;
188 				if (!found)
189 					notesInside.add(note);
190 			}
191 		}
192 
193 		// Now search:
194 		// go through all the possible tonics
195 		// it is required because tonic isn't mandatory in a chord
196 		java.util.List allProposals = new ArrayList(10);
197 
198 		for (int tonic=0; tonic<12; tonic++) {
199 
200 			Proposal currentProp = null;
201 
202 			// first check for the basic chord tones
203 			for (int chordIndex = 0; chordIndex < ChordDatabase.length(); chordIndex ++) {
204 				ChordDatabase.ChordInfo info = ChordDatabase.get(chordIndex);
205 
206 				currentProp = new Proposal(notesInside);
207 				// it is more unusual the more we go down the index
208 				// except chords "5" and "m", they are quite usual :)
209 				currentProp.unusualGrade-=(chordIndex!=ChordDatabase.length() && chordIndex!=4 ? 2*chordIndex : 0);
210 
211 				//ChordDatabase.ChordInfo info = (ChordDatabase.ChordInfo)chordItr.next();
212 				boolean foundNote = false;
213 				for (int i=0; i<info.getRequiredNotes().length; i++) { // go through all the requred notes
214 					Iterator nit = notesInside.iterator();
215 					while (nit.hasNext()) // go through all the needed notes
216 						if (((Integer)nit.next()).intValue() == (tonic+info.getRequiredNotes()[i]-1)%12) {
217 							foundNote=true;
218 							if (tonic+info.getRequiredNotes()[i]-1 == tonic)
219 								currentProp.dontHaveGrade+=15; // this means penalty for not having tonic is -65
220 							currentProp.foundNote(tonic+info.getRequiredNotes()[i]-1); // found a note in a chord
221 						}
222 
223 				}
224 				// if something found, add it into a proposal if it's worth
225 				if (foundNote) {
226 					currentProp.params[TONIC_INDEX] = tonic;
227 					currentProp.params[CHORD_INDEX] = chordIndex;//possibleChords.indexOf(info);
228 					int foundNotesCount = currentProp.missingNotes.length-currentProp.missingCount;
229 
230 					// it is worth if it is missing 1 essential note and/or fifth
231 					if (!info.getName().startsWith("dim") && !info.getName().startsWith("aug"))
232 						if (!currentProp.isFound(tonic+8-1)) {
233 
234 							// hmmm. maybe it's altered fifth? Create a branch for it.
235 							if (currentProp.isNeeded(tonic+7-1) || currentProp.isNeeded(tonic+9-1)) {
236 								Proposal branchProp = (Proposal)currentProp.clone();
237 								if (branchProp.isNeeded(tonic+9-1)) {
238 									branchProp.params[I5_INDEX] = 1;
239 									branchProp.foundNote(tonic+8);
240 								}
241 								else {
242 									branchProp.params[I5_INDEX] = 2;
243 									branchProp.foundNote(tonic+6);
244 								}
245 								branchProp.unusualGrade-=35;
246 								if (foundNotesCount+1>=info.getRequiredNotes().length-1) {
247 									branchProp.dontHaveGrade-=(info.getRequiredNotes().length-(foundNotesCount+1))*50;
248 									allProposals.add(branchProp);
249 								}
250 
251 							}
252 							else {
253 								currentProp.params[I5_INDEX] = 0;
254 								currentProp.dontHaveGrade+=30;
255 							}
256 						}
257 
258 					currentProp.params[I5_INDEX] = 0;
259 					if (foundNotesCount>=info.getRequiredNotes().length-1 ) {
260 							currentProp.dontHaveGrade-=(info.getRequiredNotes().length-foundNotesCount)*50;
261 							allProposals.add(currentProp);
262 					}
263 				}
264 				currentProp=null;
265 			}
266 		}
267 
268 		Iterator props = allProposals.iterator();
269 		java.util.List unsortedProposals = new ArrayList(5);
270 		while (props.hasNext()) {
271 			// place the still missing alterations notes accordingly... bass also
272 			///////////////////////////////////////////////////////////////
273 
274 			final Proposal current = (Proposal)props.next();
275 
276 			boolean bassIsOnlyInBass = true;
277 			// ---------------- bass tone ----------------
278 			for (int i=chord.getStrings().length-1; i>=0; i--) {
279 				if (chord.getStrings()[i]!=-1) {
280 					if (current.params[BASS_INDEX]==-1) {// if we still didn't determine bass
281 						current.params[BASS_INDEX] = (tuning[tuning.length-1-i] + chord.getStrings()[i]) % 12;
282 						if (current.params[BASS_INDEX]!=current.params[TONIC_INDEX])
283 							current.unusualGrade-=20;
284 					}
285 					if (current.params[BASS_INDEX]==(tuning[tuning.length-1-i] + chord.getStrings()[i]) % 12 )
286 						bassIsOnlyInBass=false; // if we stumbled upon bass tone again
287 				}
288 			}
289 
290 			if (current.isNeeded(current.params[BASS_INDEX]) && bassIsOnlyInBass) {
291 				   // do not mark as FOUND if bass is somewhere other than in bass only
292 					current.foundNote(current.params[BASS_INDEX]);
293 					current.unusualGrade-=20;
294 			}
295 			// <=11 means "not DIM or AUG or 5"
296 			if (current.missingCount>0 && current.params[CHORD_INDEX]<=11) {
297 				// ---------------- alteration tones ----------------
298 				// determine seventh -->> 2 is HARDCODED!
299 				int seventh;
300 				if (current.params[CHORD_INDEX] == 2) seventh=current.params[TONIC_INDEX]+12-1; // plain 7
301 						else seventh=current.params[TONIC_INDEX]+11-1; // b7
302 				if (current.isExisting(seventh)) {
303 					if (!current.isFound(seventh)) {
304 						current.filled[3]=true;
305 						current.foundNote(seventh);
306 					}
307 				}
308 				for (int plusminus=0; plusminus<=2; plusminus++) {
309 					for (int i=2; i>=0; i--)  // 13, 11, 9
310 							if (current.isNeeded(current.params[TONIC_INDEX]+getAddNote(i, plusminus)) && !current.filled[i]) {
311 								current.filled[i]=true;
312 								current.plusminusValue[i]=plusminus;
313 								if (plusminus!=0)
314 									current.unusualGrade-=15;
315 								current.foundNote(current.params[TONIC_INDEX]+getAddNote(i, plusminus));
316 							}
317 
318 				}
319 			}
320 
321 			// fill in the list
322 			///////////////////////////////////////////////////////////////
323 			if (!(current.filled[3] && !(current.filled[0] || current.filled[1] || current.filled[2])) &&  // if just found seventh, cancel it
324 					current.missingCount==0 && // we don't tollerate notes in chord that are not used in the ChordName
325 					current.dontHaveGrade>-51) {
326 						findChordLogic(current);
327 						unsortedProposals.add(current);
328 				}
329 
330 		}
331 		// first, sort by DontHaveGrade
332 		shellsort(unsortedProposals,1);
333 
334 		int cut=-1;
335 		int howManyIncomplete = ChordSettings.instance().getIncompleteChords();
336 
337 		for (int i=0; i<unsortedProposals.size() && cut==-1; i++) {
338 			int prior = ((Proposal)unsortedProposals.get(i)).dontHaveGrade;
339 			if (prior<0)
340 				cut=i+howManyIncomplete;
341 		}
342 		// cut the search
343 		unsortedProposals=unsortedProposals.subList(0, (cut>0 && cut<unsortedProposals.size() ? cut : unsortedProposals.size()));
344 		// sort by unusualGrade
345 		shellsort(unsortedProposals,2);
346 
347 		int firstNegative = 0;
348 		for (int i=0; i<unsortedProposals.size(); i++) {
349 			final Proposal current = (Proposal)unsortedProposals.get(i);
350 			if (firstNegative==0 && current.unusualGrade<0)
351 				firstNegative=current.unusualGrade;
352 
353 			if (current.unusualGrade > (firstNegative>=0 ? 0 : firstNegative)-60){
354 				try {
355 					TGSynchronizer.instance().addRunnable(new TGSynchronizer.TGRunnable() {
356 						public void run() {
357 							if(!getDialog().isDisposed() && isValidProcess(processId)){
358 								addProposal(current.params, getChordName(current.params,sharp)+" ("+Math.round(100+current.dontHaveGrade*7/10)+"%)" );
359 							}
360 						}
361 					});
362 				} catch (Throwable e) {
363 					e.printStackTrace();
364 				}
365 			}
366 		}
367 		if (this.proposalParameters.size()==0)
368 			return null;
369 		return (int[])this.proposalParameters.get(0);
370 	}
371 
372 	/** adjusts widgets on the Recognizer combo */
redecorate(int params[])373 	protected void redecorate(int params[]){
374 		this.dialog.getSelector().adjustWidgets(params[TONIC_INDEX],
375 		                                        params[CHORD_INDEX],
376 		                                        params[ALTERATION_INDEX],
377 		                                        params[BASS_INDEX],
378 		                                        params[PLUSMINUS_INDEX],
379 		                                        params[ADDCHK_INDEX],
380 		                                        params[I5_INDEX],
381 		                                        params[I9_INDEX],
382 		                                        params[I11_INDEX]);
383 	}
384 
385 	/** Assembles chord name according to ChordNamingConvention */
getChordName(int[] param, boolean sharp)386 	protected String getChordName(int[] param, boolean sharp) {
387 		return new ChordNamingConvention().createChordName(param[TONIC_INDEX],
388 		                                                   param[CHORD_INDEX],
389 		                                                   param[ALTERATION_INDEX],
390 		                                                   param[PLUSMINUS_INDEX],
391 		                                                   param[ADDCHK_INDEX] != 0,
392 		                                                   param[I5_INDEX],
393 		                                                   param[I9_INDEX],
394 		                                                   param[I11_INDEX],
395 		                                                   param[BASS_INDEX],
396 		                                                   sharp);
397 	}
398 
399 	/** Return required interval in semitones for add type and +- modificator
400 	 * @param type 0=add9, 1=add11, 2=add13
401 	 * @param selectionIndex 0=usual, 1="+", 2="-"
402 	 */
getAddNote(int type, int selectionIndex)403 	protected int getAddNote(int type, int selectionIndex) {
404 
405 		int wantedNote = 0;
406 
407 		switch (type) {
408 			case 0:
409 				wantedNote = 3; // add9
410 				break;
411 			case 1:
412 				wantedNote = 6; // add11
413 				break;
414 			case 2:
415 				wantedNote = 10; // add13
416 				break;
417 		}
418 
419 		switch (selectionIndex) {
420 			case 1:
421 				wantedNote++;
422 				break;
423 			case 2:
424 				wantedNote--;
425 				break;
426 			default:
427 				break;
428 		}
429 
430 		return --wantedNote;
431 
432 	}
433 
findChordLogic(Proposal current)434 	void findChordLogic(Proposal current) {
435 		boolean[] found = current.filled;
436 		int[] plusMinus = current.plusminusValue;
437 		/*if (!found[3])
438 			current.unusualGrade-=50;*/
439 		current.params[ALTERATION_INDEX]=0;
440 		current.params[I9_INDEX]=plusMinus[0];
441 		current.params[I11_INDEX]=plusMinus[1];
442 		current.params[ADDCHK_INDEX]=0;
443 		current.params[PLUSMINUS_INDEX]=0;
444 
445 		if (found[2]) { // -------------- 13
446 			current.params[ALTERATION_INDEX]=3;
447 			current.params[PLUSMINUS_INDEX]=plusMinus[2];
448 			if (!found[1] || !found[0] || !found[3]) { // b7 or 9 or 11 not inside
449 				current.unusualGrade-=10;
450 				if (!found[1] && !found[0] && !found[3])
451 					current.params[ADDCHK_INDEX]=1;
452 				else { // just penalty if something's missing
453 					if (!found[3]) // don't-have penalty if seventh is missing
454 						current.dontHaveGrade-=25;
455 					if (!found[1]) { // if 9 or 11 is missing, it is more unusual
456 						current.unusualGrade-=30;
457 						current.dontHaveGrade-=10;
458 					}
459 					if (!found[0]) {
460 						current.unusualGrade-=30;
461 						current.dontHaveGrade-=10;
462 					}
463 				}
464 			}
465 		}
466 		else
467 			if (found[1]) { // -------------- 11
468 				current.params[ALTERATION_INDEX]=2;
469 				current.params[PLUSMINUS_INDEX]=plusMinus[1];
470 				current.params[I11_INDEX]=0;
471 				current.unusualGrade-=10;
472 
473 				if (!found[0] || !found[3]) { // b7 or 9 not inside
474 					if (!found[0] && !found[3])
475 						current.params[ADDCHK_INDEX]=1;
476 					else{
477 						if (!found[3])
478 							current.dontHaveGrade-=25;
479 						if (!found[0]) {
480 							current.unusualGrade-=30;
481 							current.dontHaveGrade-=10;
482 						}
483 					}
484 				}
485 			}
486 			else
487 				if (found[0]) { // 9
488 					current.params[ALTERATION_INDEX]=1;
489 					current.params[I9_INDEX]=0;
490 					current.params[I11_INDEX]=0;
491 					current.params[PLUSMINUS_INDEX]=plusMinus[0];
492 					current.unusualGrade-=10;
493 					if (!found[3])
494 						current.params[ADDCHK_INDEX]=1;
495 
496 				}
497 	}
498 
499 	/**
500 	 * Shellsort, using a sequence suggested by Gonnet.
501 	 * -- a little adopted
502 	 * @param a List of Proposals, unsorted
503 	 * @param sortIndex 1 to sort by don'tHaveGrade, 2 to sort by unusualGrade
504 	 * @return sorted list by selected criteria
505 	 */
shellsort( java.util.List a, int sortIndex )506 	public void shellsort( java.util.List a, int sortIndex ){
507 		int length = a.size();
508 		for( int gap = length / 2; gap > 0;
509 					 gap = gap == 2 ? 1 : (int) ( gap / 2.2 ) )
510 			for( int i = gap; i < length; i++ ){
511 				Proposal tmp = (Proposal)a.get(i);
512 				int j = i;
513 
514 				for( ; j >= gap &&
515 				(  sortIndex == 1 ?
516 				tmp.dontHaveGrade > ((Proposal)a.get(j - gap)).dontHaveGrade :
517 				tmp.unusualGrade > ((Proposal)a.get(j - gap)).unusualGrade  )
518 				;
519 				j -= gap )
520 					a.set(j, a.get(j - gap));
521 				a.set( j , tmp);
522 			}
523 	}
524 
addProposal(int[] params, String name)525 	protected void addProposal(int[] params, String name){
526 		this.proposalParameters.add(params);
527 		this.proposalList.add(name);
528 	}
529 
clearProposals()530 	protected void clearProposals(){
531 		this.proposalList.removeAll();
532 		this.proposalParameters.clear();
533 	}
534 
getDialog()535 	protected ChordDialog getDialog(){
536 		return this.dialog;
537 	}
538 
getProposalList()539 	protected List getProposalList(){
540 		return this.proposalList;
541 	}
542 
isValidProcess(long processId)543 	protected boolean isValidProcess(long processId){
544 		return (this.runningProcess == processId);
545 	}
546 
547 	protected class Proposal implements Cloneable{
548 		int[] params;
549 
550 		/** grade for chord "unusualness" - Cm is less unusual than E7/9+/C */
551 		int unusualGrade = 0;
552 		/** penalty for notes that chord doesn't have */
553 		int dontHaveGrade = -15;
554 
555 		/** counts the notes that are in chord but still not recognized */
556 		int missingCount;
557 		int[] missingNotes;
558 
559 		boolean filled[]={false,false,false,false};
560 		int plusminusValue[]={0,0,0};
561 
Proposal()562 		private Proposal() {
563 			super();
564 			this.params = new int[9];
565 			for (int i=0; i<9; i++)
566 				this.params[i]=-1;
567 		}
568 
569 		/** initialize with needed notes */
Proposal(java.util.List notes)570 		public Proposal(java.util.List notes) {
571 			this.params = new int[9];
572 			for (int i=0; i<9; i++)
573 				this.params[i]=-1;
574 
575 			int length = notes.size();
576 			this.missingNotes = new int[length];
577 			for (int i = 0; i< length; i++){ // deep copy, because of clone() method
578 				this.missingNotes[i] = ((Integer)notes.get(i)).intValue();
579 			}
580 			this.missingCount = length;
581 		}
582 
583 		/** if note is found, mark it as found in the Missing array*/
foundNote(int value)584 		void foundNote(int value) {
585 			int note = (value % 12);
586 			if (this.missingCount!=0)
587 				for (int i=0; i<this.missingCount; i++)
588 					if (this.missingNotes[i] == note) {
589 						// put the found one on the end, switch positions
590 						this.missingCount--;
591 						int temp = this.missingNotes[i];
592 						this.missingNotes[i]=this.missingNotes[this.missingCount];
593 						this.missingNotes[this.missingCount]=temp;
594 						return;
595 					}
596 		}
597 
598 		/** is note already found? */
isFound(int value)599 		boolean isFound(int value) {
600 			int note = (value % 12);
601 			for (int i=this.missingCount; i<this.missingNotes.length; i++)
602 				if (this.missingNotes[i] == note)
603 					return true;
604 			return false;
605 		}
606 
607 		/** is note required to be found? */
isNeeded(int value)608 		boolean isNeeded(int value) {
609 			int note = (value % 12);
610 			if (this.missingCount!=0)
611 				for (int i=0; i<this.missingCount; i++)
612 					if (this.missingNotes[i] == note)
613 						return true;
614 			return false;
615 		}
616 
617 		/** does note exist in a chord? (found or not found) */
isExisting(int value)618 		boolean isExisting(int value) {
619 			int note = (value % 12);
620 			for (int i=0; i<this.missingNotes.length; i++)
621 				if (this.missingNotes[i] == note)
622 					return true;
623 			return false;
624 		}
625 
626 		/** calls the Object.clone() method, since it is private (?!!??) */
clone()627 		public Object clone() {
628 			Proposal proposal = new Proposal();
629 			for (int i=0; i<9; i++)
630 				proposal.params[i] = this.params[i];
631 			proposal.unusualGrade = this.unusualGrade;
632 			proposal.dontHaveGrade = this.dontHaveGrade;
633 			proposal.missingCount = this.missingCount;
634 			proposal.missingNotes = new int[this.missingNotes.length];
635 			for(int i = 0; i < proposal.missingNotes.length; i ++){
636 				proposal.missingNotes[i] = this.missingNotes[i];
637 			}
638 			proposal.filled = new boolean[this.filled.length];
639 			for (int i=0; i<proposal.filled.length; i++)
640 				proposal.filled[i] = this.filled[i];
641 
642 			proposal.plusminusValue = new int[this.plusminusValue.length];
643 			for (int i=0; i<proposal.plusminusValue.length; i++)
644 				proposal.plusminusValue[i] = this.plusminusValue[i];
645 
646 			return proposal;
647 		}
648 
equals(Object o)649 		public boolean equals(Object o) {
650 			Proposal another = (Proposal)o;
651 			for (int i=0; i<9; i++)
652 				if (this.params[i]!=another.params[i])
653 					return false;
654 			// not all attributes, but the rest is not needed YET!
655 			return true;
656 		}
657 	}
658 }