Melody.java
1    import java.util.ArrayList;
2    import java.util.Collections;
3    
4    public class Melody {
5    
6        private boolean[] rhythm;
7        private String[] melody;
8        private final String[] allNotes = new String[]{"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
9        private final int[][] scales = new int[][]{new int[]{2,2,1,2,2,2}, new int[]{2,1,2,2,2,1}, new int[]{1,2,2,2,1,2}, new int[]{2,2,2,1,2,2}, new int[]{2,2,1,2,2,1}, new int[]{2,1,2,2,1,2}, new int[]{1,2,2,1,2,2}, new int[]{2,1,2,2,1,3}, new int[]{2,1,2,2,2,2}}; // ionian, dorian, phrygian, lydian, mixolydian, aeolian, locrian, harmonic minor, melodic minor
10       private String[] keyNotes, extraNotes; // TODO: the extraNotes aren't referenced anywhere so they could be removed (might be useful though)
11       private int length, changeIndex;
12       private double pausePercentage, iPercentage, iiPercentage, iiiPercentage, ivPercentage, vPercentage, viPercentage;
13       private boolean changeIndexFound;
14   
15       public Melody(int length){
16           setKeyNotes(new String[7]);
17           setExtraNotes(new String[5]);
18           setLength(length);
19           setPausePercentage(0.75);
20           // This percentage distribution lays emphasis on I and V -> can always be manipulated for different results
21           // TODO: change this for every mode to emphasize every mode's individual specialty
22           setiPercentage(0.25);
23           setiiPercentage(getiPercentage() + 0.1);
24           setiiiPercentage(getiiPercentage() + 0.1);
25           setivPercentage(getiiiPercentage() + 0.1);
26           setvPercentage(getivPercentage() + 0.25);
27           setviPercentage(getvPercentage() + 0.1);
28       }
29   
30   
31       // Getters & Setters
32   
33       public boolean[] getRhythm(){
34           return this.rhythm;
35       }
36   
37       public void setRhythm(boolean[] newRhythm){
38           if(newRhythm != null && newRhythm.length > 0){
39               this.rhythm = newRhythm;
40           } else {
41               System.out.println("The given rhythm does not contain any content!");
42           }
43       }
44   
45       public String[] getMelody(){
46           return this.melody;
47       }
48   
49       public void setMelody(String[] newMelody){
50           if(newMelody != null && newMelody.length != 0){
51               this.melody = newMelody;
52           } else {
53               System.out.println("The given melody does not contain any content!");
54           }
55       }
56   
57       public String[] getAllNotes(){
58           return this.allNotes;
59       }
60   
61       public String[] getKeyNotes(){
62           return this.keyNotes;
63       }
64   
65       public void setKeyNotes(String[] newKeyNotes) {
66           this.keyNotes = newKeyNotes;
67       }
68   
69       public String[] getExtraNotes() {
70           return this.extraNotes;
71       }
72   
73       public void setExtraNotes(String[] extraNotes) {
74           this.extraNotes = extraNotes;
75       }
76   
77       public int getLength(){
78           return this.length;
79       }
80   
81       public void setLength(int newLength){
82           if(newLength != 0){
83               this.length = newLength;
84           } else {
85               System.out.println("The given length is too short!");
86           }
87       }
88   
89       public int getChangeIndex(){
90           return this.changeIndex;
91       }
92   
93       public void setChangeIndex(int changeIndex){
94           this.changeIndex = changeIndex;
95       }
96   
97       private double getPausePercentage(){
98           return this.pausePercentage;
99       }
100  
101      private void setPausePercentage(double newPercentage){
102          if(newPercentage >= 0 && newPercentage <= 1){
103              this.pausePercentage = newPercentage;
104          } else {
105              System.out.println("The given pausePercentage is out of bounds!");
106          }
107      }
108  
109      private double getiPercentage(){
110          return this.iPercentage;
111      }
112  
113      private void setiPercentage(double newPercentage){
114          if(newPercentage >= 0 && newPercentage <= 1){
115              this.iPercentage = newPercentage;
116          } else {
117              System.out.println("The given iPercentage is out of bounds!");
118          }
119      }
120  
121      private double getiiPercentage(){
122          return this.iiPercentage;
123      }
124  
125      private void setiiPercentage(double newPercentage){
126          if(newPercentage >= 0 && newPercentage <= 1){
127              this.iiPercentage = newPercentage;
128          } else {
129              System.out.println("The given iiPercentage is out of bounds!");
130          }
131      }
132  
133      private double getiiiPercentage(){
134          return this.iiiPercentage;
135      }
136  
137      private void setiiiPercentage(double newPercentage){
138          if(newPercentage >= 0 && newPercentage <= 1){
139              this.iiiPercentage = newPercentage;
140          } else {
141              System.out.println("The given iiiPercentage is out of bounds!");
142          }
143      }
144  
145      private double getivPercentage(){
146          return this.ivPercentage;
147      }
148  
149      private void setivPercentage(double newPercentage){
150          if(newPercentage >= 0 && newPercentage <= 1){
151              this.ivPercentage = newPercentage;
152          } else {
153              System.out.println("The given ivPercentage is out of bounds!");
154          }
155      }
156  
157      private double getvPercentage(){
158          return this.vPercentage;
159      }
160  
161      private void setvPercentage(double newPercentage){
162          if(newPercentage >= 0 && newPercentage <= 1){
163              this.vPercentage = newPercentage;
164          } else {
165              System.out.println("The given vPercentage is out of bounds!");
166          }
167      }
168  
169      private double getviPercentage(){
170          return this.viPercentage;
171      }
172  
173      private void setviPercentage(double newPercentage){
174          if(newPercentage >= 0 && newPercentage <= 1){
175              this.viPercentage = newPercentage;
176          } else {
177              System.out.println("The given viPercentage is out of bounds!");
178          }
179      }
180  
181      public boolean getChangeIndexFound(){
182          return this.changeIndexFound;
183      }
184  
185      public void setChangeIndexFound(boolean changeIndexFound){
186          this.changeIndexFound = changeIndexFound;
187      }
188  
189      public int[][] getScales() {
190          return scales;
191      }
192  
193  
194      // Actual Methods
195  
196      public void createRhythm(){
197          setRhythm(new boolean[getLength()]);
198          for(int i = 0; i < getLength(); i++){
199              if(Math.random() < getPausePercentage()){
200                  getRhythm()[i] = false;
201                  setPausePercentage(getPausePercentage() * 0.9); // "notePercentage" increases (pausePercentage decreases) the longer the pause has been
202              } else {
203                  getRhythm()[i] = true;
204                  setPausePercentage(0.75);
205              }
206          }
207      }
208  
209      public void createKeyNotes(String tonic, int scale){
210          getKeyNotes()[0] = tonic;
211          setChangeIndexFound(false);
212          int currentIndex = -1;
213          // Find index of tonic
214          for(int i = 0; i < getAllNotes().length; i++) {
215              if(getAllNotes()[i].equals(tonic)){
216                  currentIndex = i;
217                  break;
218              }
219          }
220          // Create rest of notes based on index of tonic
221          if(currentIndex != -1){
222              int i = 1;
223              for(int next : getScales()[scale]){
224                  if(currentIndex + next < getAllNotes().length){
225                      currentIndex = currentIndex + next;
226                  } else {
227                      currentIndex = currentIndex + next - getAllNotes().length;
228                      if(!getChangeIndexFound()){
229                          setChangeIndex(i);
230                          setChangeIndexFound(true);
231                      }
232                  }
233                  getKeyNotes()[i] = getAllNotes()[currentIndex];
234                  i++;
235              }
236          } else {
237              System.out.println("The given tonic is not part of the Western note system!");
238          }
239          createExtraNotes();
240      }
241  
242      private void createExtraNotes(){
243          int j = 0;
244          for(int i = 0; i < getAllNotes().length; i++){
245             if(findKeyNoteIndex(getAllNotes()[i]) == -1){
246                 getExtraNotes()[j] = getAllNotes()[i];
247                 j++;
248             }
249         }
250      }
251  
252      public int findKeyNoteIndex(String keyNote){
253          for(int i = 0; i < getKeyNotes().length; i++){
254              if(getKeyNotes()[i].equals(keyNote)){
255                  return i;
256              }
257          }
258          return -1;
259      }
260  
261      public int findAllNotesIndex(String note){
262          for(int i = 0; i < getAllNotes().length; i++){
263              if(getAllNotes()[i].equals(note)){
264                  return i;
265              }
266          }
267          return -1;
268      }
269  
270      public void createMelody(){
271          setMelody(new String[getLength()]);
272          double randDouble;
273          for(int i = 0; i < getMelody().length; i++){
274              if(getRhythm()[i]){
275                  randDouble = Math.random();
276                  setMelodyNote(randDouble, i);
277              }
278          }
279      }
280  
281      public void createChordsMelody(Chord[] chords){
282          setMelody(new String[getLength()]);
283          // declare variables outside of loop in order to save storage space
284          double randDouble;
285          String[] arpeggiateNotes;
286          String[] noMinor2ndIntervalsNotes;
287          double p;
288          // create melody
289          for(int i = 0; i < getMelody().length; i++){ // chordsArray should have the same length as the melodyArray
290              if(getRhythm()[i]){
291                  randDouble = Math.random();
292                  if(chords[i] == null){ // no chord entered -> normal melodyNote creation
293                      setMelodyNote(randDouble, i);
294                  } else { // chord is entered
295                      if(chords[i].getArpeggiate()){ // arpeggiate chord
296                          arpeggiateNotes = extractPossibleArpeggiateNotes(chords[i].getKeyChordNotes(), chords[i].getExtraChordNotes());
297                          p = 1.0/arpeggiateNotes.length; // give each possible note an equal probability
298                          for(int j = 0; j < arpeggiateNotes.length; j++){
299                              if(p*j < randDouble && randDouble < p*(j+1)){ // in order to understand this, compare it to the setMelodyNote() method -> it's the same thing just in a for loop because the equal probability allows it
300                                  getMelody()[i] = arpeggiateNotes[j];
301                                  break;
302                              }
303                          }
304                      } else { // no minor 2nd interval chord
305                          noMinor2ndIntervalsNotes = extractPossibleNoMinor2ndIntervalsNotes(chords[i].getKeyChordNotes(), chords[i].getExtraChordNotes(), chords[i]);
306                          p = 1.0/noMinor2ndIntervalsNotes.length; // give each possible note an equal probability
307                          for(int j = 0; j < noMinor2ndIntervalsNotes.length; j++){
308                              if(p*j < randDouble && randDouble < p*(j+1)){ // in order to understand this, compare it to the setMelodyNote() method -> it's the same thing just in a for loop because the equal probability allows it
309                                  getMelody()[i] = noMinor2ndIntervalsNotes[j];
310                                  break;
311                              }
312                          }
313                      }
314                  }
315              }
316          }
317      }
318  
319      private void setMelodyNote(double randDouble, int index){
320          if(randDouble < getiPercentage()){ // I
321              getMelody()[index] = getKeyNotes()[0];
322          } else if(getiPercentage() < randDouble && randDouble < getiiPercentage()){ // II
323              getMelody()[index] = getKeyNotes()[1];
324          } else if(getiiPercentage() < randDouble && randDouble < getiiiPercentage()){ // III
325              getMelody()[index] = getKeyNotes()[2];
326          } else if(getiiiPercentage() < randDouble && randDouble < getivPercentage()){ // IV
327              getMelody()[index] = getKeyNotes()[3];
328          }
329          else if(getivPercentage() < randDouble && randDouble < getvPercentage()){ // V
330              getMelody()[index] = getKeyNotes()[4];
331          }
332          else if(getvPercentage() < randDouble && randDouble < getviPercentage()){ // VI
333              getMelody()[index] = getKeyNotes()[5];
334          }
335          else { // VII
336              getMelody()[index] = getKeyNotes()[6];
337          }
338      }
339  
340      public static String extractActualNoteName(String note){
341          int index = 0;
342          String actualNoteName = "";
343          while(index < note.length() && note.charAt(index) != ' '){ // (index < note.length) is not necessary for the names of the CheckBoxNotes; could be used for chordBaseNoteCB if roman numbers in brackets (C (I); C#; D (II) ...) are added for chord function in key
344              actualNoteName += note.charAt(index);
345              index++;
346          }
347          return actualNoteName;
348      }
349  
350      private String[] extractPossibleArpeggiateNotes(String[] keyChordNotes, String[] extraChordNotes){
351          ArrayList<String> possibleNotes = new ArrayList<>();
352          for(String note : keyChordNotes){
353              if(note != null){
354                  possibleNotes.add(note);
355              }
356          }
357          for(String note : extraChordNotes){
358              if(note != null){
359                  possibleNotes.add(note);
360              }
361          }
362          return convertToStringArray(possibleNotes);
363      }
364  
365      private String[] extractPossibleNoMinor2ndIntervalsNotes(String[] keyChordNotes, String[] extraChordNotes, Chord chord){
366          ArrayList<Integer> noteIndicesArrayList = new ArrayList<>(); // store the indices of the chord notes
367          int indexBuffer; // this variable is declared here so that it won't be declared in every iteration of the for loop (to save storage space)
368  
369          // store all chord notes in an array like getAllNotes() and order them by placing them at their respective index -> one wants to be able to compare allNotesArray with getAllNotes()
370          String[] allNotesArray = new String[12];
371          for(String keyChordNote : keyChordNotes){
372              if(keyChordNote != null){
373                  indexBuffer = findAllNotesIndex(keyChordNote); // this variable shortens the runtime by 'preventing' the findAllNotesIndex() method from running twice
374                  allNotesArray[indexBuffer] = keyChordNote;
375                  noteIndicesArrayList.add(indexBuffer);
376              }
377          }
378          for(String extraChordNote : extraChordNotes){
379              if(extraChordNote != null){
380                  indexBuffer = findAllNotesIndex(extraChordNote); // this variable shortens the runtime by 'preventing' the findAllNotesIndex() method from running twice
381                  allNotesArray[indexBuffer] = extraChordNote;
382                  noteIndicesArrayList.add(indexBuffer);
383              }
384          }
385          Collections.sort(noteIndicesArrayList);
386  
387          // find out which keyNotes could be added to the allNotesArray because they don't have a minor 2nd interval with any of the chord notes
388          for(int i = 0; i < noteIndicesArrayList.size()-1; i++){
389              // the "if(noteIndicesArrayList.get(i+1) - noteIndicesArrayList.get(i) > 3)" part could be removed because the for-loop already indirectly includes this if-statement -> it's still in there because it makes the method easier to understand (I hope)
390              if(noteIndicesArrayList.get(i+1) - noteIndicesArrayList.get(i) > 3){ // check whether the 'space' between two notes allows for a note (in between those notes) that doesn't have a minor 2nd interval with both of the surrounding notes
391                  for(int j = noteIndicesArrayList.get(i)+2; j <= noteIndicesArrayList.get(i+1)-2; j++){ // check whether any of the notes in between the two surrounding notes (that doesn't have a minor 2nd interval with the surrounding notes -> hence the +2/-2) is a keyNote and could therefore be added to the allNotesArray
392                      if(findKeyNoteIndex(getAllNotes()[j]) != -1){ // note == keyNote
393                          allNotesArray[j] = getAllNotes()[j];
394                      }
395                  }
396              }
397          }
398          // the "if(getAllNotes().length - (noteIndicesArrayList.get(noteIndicesArrayList.size()-1) - noteIndicesArrayList.get(0)) > 3)" part could be removed because the for-loop already indirectly includes this if-statement -> it's still in there because it makes the method easier to understand (I hope)
399          if(getAllNotes().length - (noteIndicesArrayList.get(noteIndicesArrayList.size()-1) - noteIndicesArrayList.get(0)) > 3){ // check the space between the last and the first note in the array -> this always starts somewhere "at the end" of the allNotesArray and ends somewhere "at the beginning" -> hence the getAllNotes().length - ()
400              // check the "end part" of the array
401              for(int i = noteIndicesArrayList.get(noteIndicesArrayList.size()-1)+2; i < getAllNotes().length; i++){ // check whether any of the notes in between the two surrounding notes (that doesn't have a minor 2nd interval with the surrounding notes -> hence the +2/-2) is a keyNote and could therefore be added to the allNotesArray
402                  if(findKeyNoteIndex(getAllNotes()[i]) != -1){ // note == keyNote
403                      allNotesArray[i] = getAllNotes()[i];
404                  }
405              }
406              // check the "beginning part" of the array
407              for(int i = 0; i <= noteIndicesArrayList.get(0)-2; i++){ // check whether any of the notes in between the two surrounding notes (that doesn't have a minor 2nd interval with the surrounding notes -> hence the +2/-2) is a keyNote and could therefore be added to the allNotesArray
408                  if(findKeyNoteIndex(getAllNotes()[i]) != -1){ // note == keyNote
409                      allNotesArray[i] = getAllNotes()[i];
410                  }
411              }
412          }
413  
414          // remove notes with null value from allNotesArray
415          ArrayList<String> possibleNotes = new ArrayList<>();
416          for(String note : allNotesArray){
417              if(note != null){
418                  possibleNotes.add(note);
419              }
420          }
421          return convertToStringArray(possibleNotes);
422      }
423  
424      private String[] convertToStringArray(ArrayList<String> arrayList){
425          String[] array = new String[arrayList.size()];
426          for (int i = 0; i < array.length; i++){
427              array[i] = arrayList.get(i);
428          }
429          return array;
430      }
431  }
432