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