source: trunk/src/org/apollo/meldex/RogTrack.java

Last change on this file was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

  • Property svn:executable set to *
File size: 14.1 KB
Line 
1package org.apollo.meldex;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileOutputStream;
6import java.io.IOException;
7import java.util.ArrayList;
8
9import org.expeditee.gio.EcosystemManager;
10
11public class RogTrack
12{
13 // The number of semitones from the key centre
14 final int nameOffset[] = { 0, 0, 1, 2, 2, 3, 3, 4, 4, 5, 6, 6 };
15
16 // Data about the track (loaded from the file)
17 private String databaseName = "";
18 private String songName = "";
19 private int wholeNoteDivisions = 16;
20
21 // Data representing the track events
22 ArrayList<RogTrackEvent> trackData = new ArrayList<RogTrackEvent>();
23
24
25 // ---------------------------------------
26 // WavToRog specific functions
27 // ---------------------------------------
28 public RogTrack()
29 {
30 }
31
32
33 public void addTimeSignature(int numer, int denom)
34 {
35 trackData.add(new RogTrackEvent(RogTrackEvent.TIME_SIG_EVENT, numer, denom));
36 }
37
38
39 public void addNote(int noteLength, int notePitch, int key)
40 {
41 int noteName = getNoteName(key, notePitch);
42 trackData.add(new RogTrackEvent(noteLength, noteName, notePitch));
43 }
44
45
46 public void addRest(int restLength)
47 {
48 if (restLength > 0) {
49 trackData.add(new RogTrackEvent(restLength, 0, 0));
50 }
51 }
52
53
54 public void addKeySignature(int numSharpsFlats, int isMinor)
55 {
56 trackData.add(0, new RogTrackEvent(RogTrackEvent.KEY_SIG_EVENT, isMinor, numSharpsFlats));
57 }
58
59
60 public int calculateBestKey()
61 {
62 int accidental;
63 int minAccidental = trackData.size();
64 int minKey = 0;
65
66 for (int numSharpFlat = 0; numSharpFlat < 8; numSharpFlat++) {
67 accidental = checkAccidental(numSharpFlat);
68 if (accidental < minAccidental) {
69 minAccidental = accidental;
70 minKey = numSharpFlat;
71 }
72
73 accidental = checkAccidental(-numSharpFlat);
74 if (accidental < minAccidental) {
75 minAccidental = accidental;
76 minKey = -numSharpFlat;
77 }
78 }
79
80 return minKey;
81 }
82
83
84 public int checkAccidental(int key)
85 {
86 int sharpsFlats[] = { 0, 0, 0, 0, 0, 0, 0 };
87 int nameValue[] = { 0, 2, 4, 5, 7, 9, 11 };
88
89 // Calculate the sharps and flats for this key
90 if (key < 0) {
91 for (int flatNum = 1; flatNum <= (-key); flatNum++) {
92 sharpsFlats[((8 + flatNum) * 3) % 7]--;
93 }
94 }
95 else {
96 for (int sharpNum = 1; sharpNum <= key; sharpNum++) {
97 sharpsFlats[((sharpNum * 4) + 6) % 7]++;
98 }
99 }
100
101 // Iterate through the track
102 int sumAccidental = 0;
103 for (int i = 0; i < trackData.size(); i++) {
104 RogTrackEvent te = (RogTrackEvent) trackData.get(i);
105
106 // Have we found a note??
107 if (te.type <= RogTrackEvent.LAST_NOTE_EVENT) {
108 int semitone = te.param2;
109
110 // Calculate the accidental for this key
111 int noteName = getNoteName(key, semitone);
112 int numSharpsFlats = (semitone % 12) - nameValue[noteName];
113
114 if (numSharpsFlats < -6) {
115 numSharpsFlats += 12;
116 }
117 if (numSharpsFlats > 6) {
118 numSharpsFlats -= 12;
119 }
120
121 if (numSharpsFlats != sharpsFlats[noteName]) {
122 sumAccidental++;
123 }
124 }
125 }
126
127 return sumAccidental;
128 }
129
130
131 public void setNoteNames(int numSharpsFlats)
132 {
133 for (int i = 0; i < trackData.size(); i++) {
134 RogTrackEvent te = (RogTrackEvent) trackData.get(i);
135 if (te.type <= RogTrackEvent.LAST_NOTE_EVENT && te.param2 > 0) {
136 te.param1 = getNoteName(numSharpsFlats, te.param2);
137 }
138 }
139 }
140
141
142 public int getNoteName(int key, int notePitch)
143 {
144 // Calculate the name of the key
145 int keyName = (((7 - key) * 3) % 7);
146
147 // Calculate the semitone start position
148 int keySemitoneStart = (((12 - key) * 5) % 12);
149
150 // Calculate the note name
151 int noteName = (keyName + nameOffset[(notePitch + 12 - keySemitoneStart) % 12]) % 7;
152 return noteName;
153 }
154
155
156 public String toString()
157 {
158 String str = "song \"" + databaseName + "\" \"" + songName + "\" " + wholeNoteDivisions + "\n";
159
160 // Display each of the TrackEvents in turn
161 for (int i = 0; i < trackData.size(); i++) {
162 RogTrackEvent te = (RogTrackEvent) trackData.get(i);
163
164 // Is this a note??
165 if (te.type <= RogTrackEvent.LAST_NOTE_EVENT) {
166 char noteName;
167 if (te.param1 < 5) {
168 noteName = (char) (te.param1 + 'C');
169 }
170 else {
171 noteName = (char) (te.param1 - 5 + 'A');
172 }
173 str = str + te.type + " " + noteName + " " + te.param2 + "\n";
174 }
175
176 // Is this a time signature??
177 else if (te.type == RogTrackEvent.TIME_SIG_EVENT) {
178 str = str + "timesig " + te.param1 + " " + te.param2 + "\n";
179 }
180
181 // Is this a key signature??
182 else if (te.type == RogTrackEvent.KEY_SIG_EVENT) {
183 str = str + "keysig " + te.param1 + " " + te.param2 + "\n";
184 }
185
186 // Is this a fermata event??
187 else if (te.type == RogTrackEvent.FERMATA_EVENT) {
188 str = str + "fermata\n";
189 }
190
191 // Is this a tempo event??
192 else if (te.type == RogTrackEvent.TEMPO_EVENT) {
193 str = str + "tempo " + te.param1 + "\n";
194 }
195 }
196
197 // Return the finished string
198 return str;
199 }
200
201 public void fromString(String rog_text)
202 {
203 int rog_text_len = rog_text.length();
204 byte[] rog_bytes = rog_text.getBytes();
205
206 parseFile(rog_bytes, rog_text_len);
207 }
208
209
210 /**
211 *
212 * @return
213 * The converted melody. Null if was unable to convert
214 */
215 public Melody toMelody()
216 {
217 int track_len = trackData.size();
218
219 ArrayList<RogTrackEvent> filtered_td = new ArrayList<RogTrackEvent>();
220
221 for (int i = 0; i < track_len; i++) {
222 RogTrackEvent te = (RogTrackEvent) trackData.get(i);
223
224 // Is this a note??
225 if (te.type <= RogTrackEvent.LAST_NOTE_EVENT) {
226 filtered_td.add(te);
227 }
228 }
229
230 int filtered_track_len = filtered_td.size();
231
232 if (filtered_track_len<1) {
233 //System.err.println("Insufficient track data for form relative pitch melody");
234 return null;
235 }
236
237 ArrayList<MelodyEvent> me_list = new ArrayList<MelodyEvent>();
238
239 for (int i = 0; i < filtered_track_len-1; i++) {
240 RogTrackEvent te = (RogTrackEvent) filtered_td.get(i);
241 RogTrackEvent next_te = (RogTrackEvent) filtered_td.get(i+1);
242
243 int duration = te.type;
244
245 if (next_te.param2 == 0) {
246 // is a rest
247 MelodyEvent me = new MelodyEvent(MelodyEvent.REST_EVENT,duration);
248 me_list.add(me);
249
250 // skip ahead one further position
251 i++;
252 continue;
253 }
254
255 int rel_pitch = next_te.param2 - te.param2;
256
257 MelodyEvent me = new MelodyEvent(rel_pitch,duration);
258 me_list.add(me);
259 }
260
261 int me_list_len = me_list.size();
262
263 MelodyEvent[] me_array = new MelodyEvent[me_list_len];
264 for (int i=0; i<me_list_len; i++) {
265 me_array[i] = (MelodyEvent)me_list.get(i);
266 }
267
268 Melody me = new Melody(Melody.RELATIVE_PITCH,me_array);
269
270 return me;
271 }
272
273
274 // ---------------------------------------
275 // RogToGif specific functions
276 // ---------------------------------------
277
278 // ----------------------------------------------------------------------------------
279 // Method : loadFromFile
280 // Returns : false - if the track could not be successfully loaded
281 // true - if the track was successfully loaded
282 // ----------------------------------------------------------------------------------
283 public boolean loadFromFile(File rogFile)
284 {
285 boolean success = true;
286 FileInputStream fileIn = null;
287 try {
288 // Open the file with a FileInputStream, ready for parsing
289 fileIn = new FileInputStream(rogFile);
290
291 // Get the number of bytes in the file
292 int rogLength = fileIn.available();
293
294 // Create a buffer to store the file, and read the file into it
295 byte[] rogData = new byte[rogLength];
296 fileIn.read(rogData, 0, rogLength);
297
298 // Parse the .rog file
299 parseFile(rogData, rogLength);
300 } catch (Exception ex) {
301 EcosystemManager.getGraphicsManager().showDialog("Exception", "Exception occurred reading file.\n\n" + ex);
302 success = false;
303 } finally {
304 if (fileIn != null) {
305 try {
306 fileIn.close();
307 } catch (IOException e) {
308 // If an IO exception occurs when closing the file, just sweep it under the rug
309 }
310 }
311 }
312
313 // File loaded successfully
314 return success;
315 }
316
317
318 private void parseFile(byte[] rogData, int rogLength)
319 {
320 // Break the file up into lines and parse each one seperately
321 int lineStart = 0;
322 for (int pos = 0; pos < rogLength; pos++) {
323 if (rogData[pos] == '\n') {
324 parseLine(new String(rogData, lineStart, (pos - lineStart)));
325 lineStart = pos + 1;
326 }
327 }
328 // If the last line is not newline-terminated then we have to parse it now
329 if (lineStart != rogLength) {
330 parseLine(new String(rogData, lineStart, (rogLength - lineStart)));
331 }
332 }
333
334
335 private void parseLine(String line)
336 {
337 String sent = null;
338 String word = nextWord(line, 0);
339 int parsed = (word.length() + 1);
340
341 if (word.equals("") || word.equals("metadata")) {
342 // do nothing => ignore it!
343 }
344 else if (word.equals("song")) {
345 // Parse the first parameter - databaseName
346 sent = nextSentence(line, parsed + 1);
347 parsed += (sent.length() + 3);
348 databaseName = sent;
349
350 // Parse the second parameter - songName
351 sent = nextSentence(line, parsed + 1);
352 parsed += (sent.length() + 3);
353 songName = sent;
354
355 // Parse the third parameter - wholeDivision
356 word = nextWord(line, parsed);
357 parsed += (word.length() + 1);
358 wholeNoteDivisions = (int) Double.parseDouble(word);
359 }
360
361 else if (word.equals("fermata")) {
362 // Add a fermata event to the track (no parameters)
363 trackData.add(new RogTrackEvent(RogTrackEvent.FERMATA_EVENT, 0, 0));
364 }
365
366 else if (word.equals("tempo")) {
367 // Parse the first parameter - tempoBPM
368 word = nextWord(line, parsed);
369 parsed += (word.length() + 1);
370 int tempoBPM = (int) Double.parseDouble(word);
371
372 // Add a tempo event to the track
373 trackData.add(new RogTrackEvent(RogTrackEvent.TEMPO_EVENT, tempoBPM, 0));
374 }
375
376 else if (word.equals("keysig")) {
377 // Parse the first parameter - isMinor
378 word = nextWord(line, parsed);
379 parsed += (word.length() + 1);
380 int isMinor = (int) Double.parseDouble(word);
381
382 // Parse the second parameter - numSharpsFlats
383 word = nextWord(line, parsed);
384 parsed += (word.length() + 1);
385 int numSharpsFlats = (int) Double.parseDouble(word);
386
387 // Add this key signature event to the track
388 trackData.add(0, new RogTrackEvent(RogTrackEvent.KEY_SIG_EVENT, isMinor, numSharpsFlats));
389 }
390
391 else if (word.equals("timesig")) {
392 // Parse the first parameter - numerator
393 word = nextWord(line, parsed);
394 parsed += (word.length() + 1);
395 int numerator = (int) Double.parseDouble(word);
396
397 // Parse the second parameter - denominator
398 word = nextWord(line, parsed);
399 parsed += (word.length() + 1);
400 int denominator = (int) Double.parseDouble(word);
401
402 // Add this time signature event to the track
403 trackData.add(new RogTrackEvent(RogTrackEvent.TIME_SIG_EVENT, numerator, denominator));
404 }
405
406 else {
407 // This must be a note line, so parse the duration
408 int duration = (int) Double.parseDouble(word);
409
410 // Parse the first parameter - noteName
411 word = nextWord(line, parsed);
412 parsed += (word.length() + 1);
413 char noteName = word.charAt(0);
414
415 // Check that the noteName is valid
416 if (noteName < 'A' || noteName > 'G') {
417 EcosystemManager.getGraphicsManager().showDialog("Exception", "Parsing error - check that the Rog file is valid.");
418 return;
419 }
420
421 // Convert the noteName character into it's MIDI integer equivalent
422 int noteNum;
423 if (noteName >= 'C') {
424 noteNum = noteName - 'C';
425 }
426 else {
427 noteNum = noteName - 'A' + 5;
428 }
429
430 // Parse the second parameter - pitch
431 word = nextWord(line, parsed);
432 parsed += (word.length() + 1);
433 int pitch = (int) Double.parseDouble(word);
434
435 // Add this note event to the track
436 trackData.add(new RogTrackEvent(duration, noteNum, pitch));
437 }
438 }
439
440
441 private String nextSentence(String line, int sentStart)
442 {
443 for (int pos = sentStart; pos < line.length(); pos++) {
444 // We have found a quote mark, so return the sentence found
445 if (line.charAt(pos) == '"') {
446 return (new String(line.substring(sentStart, pos)));
447 }
448 }
449 // We have reached the end of the string, so return the remainder
450 return (new String(line.substring(sentStart, line.length())));
451 }
452
453
454 private String nextWord(String line, int wordStart)
455 {
456 for (int pos = wordStart; pos < line.length(); pos++) {
457 // We have found a space, so return the word found
458 if (line.charAt(pos) == ' ') {
459 return (new String(line.substring(wordStart, pos)));
460 }
461 }
462 // We have reached the end of the string, so return the remainder
463 return (new String(line.substring(wordStart, line.length())));
464 }
465
466
467 public String getDatabaseName()
468 {
469 return databaseName;
470 }
471
472
473 public String getSongName()
474 {
475 return songName;
476 }
477
478
479 public int getWholeNoteDivisions()
480 {
481 return wholeNoteDivisions;
482 }
483
484
485 // ---------------------------------------
486 // SymInput specific functions
487 // ---------------------------------------
488 public void setWholeNoteDivisions(int wnd)
489 {
490 wholeNoteDivisions = wnd;
491 }
492
493
494 // ----------------------------------------------------------------------------------
495 // Method : saveToFile
496 // Returns : false - if the track could not be successfully saved
497 // true - if the track was successfully saved
498 // ----------------------------------------------------------------------------------
499 public boolean saveToFile(File outFile)
500 {
501 // Convert the track to a string and save it to the specified file
502 try {
503 FileOutputStream fileOut = new FileOutputStream(outFile);
504 fileOut.write(toString().getBytes());
505 fileOut.close();
506 }
507 catch (Exception ex) {
508 EcosystemManager.getGraphicsManager().showDialog("Exception", "Exception occurred writing to file.\n\n" + ex);
509 return false;
510 }
511
512 // File saved successfully
513 return true;
514 }
515}
Note: See TracBrowser for help on using the repository browser.