Source code: com/virtuosotechnologies/asaph/notationmanager/StandardNotationFactory.java
1 /*
2 ================================================================================
3
4 FILE: StandardNotationFactoryImpl.java
5
6 PROJECT:
7
8 Asaph
9
10 CONTENTS:
11
12 Standard implementation of NotationFactory
13
14 PROGRAMMERS:
15
16 Daniel Azuma (DA) <dazuma@kagi.com>
17
18 COPYRIGHT:
19
20 Copyright (C) 2003 Daniel Azuma (dazuma@kagi.com)
21
22 This program is free software; you can redistribute it and/or
23 modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation; either version 2
25 of the License, or (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public
33 License along with this program; if not, write to
34 Free Software Foundation, Inc.
35 59 Temple Place, Suite 330
36 Boston, MA 02111-1307 USA
37
38 ================================================================================
39 */
40
41
42 package com.virtuosotechnologies.asaph.notationmanager;
43
44
45 import java.util.Locale;
46 import java.util.List;
47 import java.util.ArrayList;
48 import java.util.LinkedHashMap;
49 import java.util.Iterator;
50
51 import com.virtuosotechnologies.lib.util.LocalizedStringBuilder;
52
53 import com.virtuosotechnologies.asaph.model.notation.NotationFactory;
54 import com.virtuosotechnologies.asaph.model.notation.Note;
55 import com.virtuosotechnologies.asaph.model.notation.NoteModifier;
56 import com.virtuosotechnologies.asaph.model.notation.Interval;
57 import com.virtuosotechnologies.asaph.model.notation.Chord;
58
59
60 /**
61 * Standard implementation of NotationFactory
62 */
63 /*package*/ class StandardNotationFactory
64 implements
65 NotationFactory
66 {
67 private boolean isGerman_;
68
69 private StandardNoteModifier naturalModifier_;
70 private StandardNoteModifier flatModifier_;
71 private StandardNoteModifier sharpModifier_;
72 private StandardNote defaultNote_;
73 private StandardChord emptyChord_;
74
75 private StandardNote[] guiBaseNotes_;
76 private StandardNote[] parseBaseNotes_;
77
78 private static final LocalizedStringBuilder strings_ =
79 new LocalizedStringBuilder(StandardNotationFactory.class, "StandardNotationStringsRB");
80
81 private static final String STR_NoteModifier_Natural =
82 strings_.buildString("NoteModifier_Natural");
83 private static final String STR_NoteModifier_Sharp =
84 strings_.buildString("NoteModifier_Sharp");
85 private static final String STR_NoteModifier_Flat =
86 strings_.buildString("NoteModifier_Flat");
87 private static final String STR_NoteModifier_DoubleSharp =
88 strings_.buildString("NoteModifier_DoubleSharp");
89 private static final String STR_NoteModifier_DoubleFlat =
90 strings_.buildString("NoteModifier_DoubleFlat");
91 private static final String STR_Interval_Unknown =
92 strings_.buildString("Interval_Unknown");
93
94 private LinkedHashMap longIntervalStrings_;
95
96
97 /*package*/ StandardNotationFactory(
98 boolean isGerman)
99 {
100 isGerman_ = isGerman;
101
102 naturalModifier_ = new StandardNoteModifier(this, 0);
103 flatModifier_ = new StandardNoteModifier(this, -1);
104 sharpModifier_ = new StandardNoteModifier(this, 1);
105 defaultNote_ = new StandardNote(this, 0, 0, naturalModifier_, true);
106 emptyChord_ = new StandardChord(this, null, "", null, "");
107
108 if (isGerman_)
109 {
110 guiBaseNotes_ = new StandardNote[8];
111 guiBaseNotes_[0] = new StandardNote(this, 0, 0, naturalModifier_, true);
112 guiBaseNotes_[1] = new StandardNote(this, 1, 1, naturalModifier_, true);
113 guiBaseNotes_[2] = new StandardNote(this, 1, 2, naturalModifier_, true);
114 guiBaseNotes_[3] = new StandardNote(this, 2, 3, naturalModifier_, true);
115 guiBaseNotes_[4] = new StandardNote(this, 3, 5, naturalModifier_, true);
116 guiBaseNotes_[5] = new StandardNote(this, 4, 7, naturalModifier_, true);
117 guiBaseNotes_[6] = new StandardNote(this, 5, 8, naturalModifier_, true);
118 guiBaseNotes_[7] = new StandardNote(this, 6, 10, naturalModifier_, true);
119 parseBaseNotes_ = new StandardNote[8];
120 parseBaseNotes_[0] = guiBaseNotes_[0];
121 parseBaseNotes_[1] = guiBaseNotes_[1];
122 parseBaseNotes_[2] = guiBaseNotes_[3];
123 parseBaseNotes_[3] = guiBaseNotes_[4];
124 parseBaseNotes_[4] = guiBaseNotes_[5];
125 parseBaseNotes_[5] = guiBaseNotes_[6];
126 parseBaseNotes_[6] = guiBaseNotes_[7];
127 parseBaseNotes_[7] = guiBaseNotes_[2];
128 }
129 else
130 {
131 guiBaseNotes_ = new StandardNote[7];
132 guiBaseNotes_[0] = new StandardNote(this, 0, 0, naturalModifier_, true);
133 guiBaseNotes_[1] = new StandardNote(this, 1, 2, naturalModifier_, true);
134 guiBaseNotes_[2] = new StandardNote(this, 2, 3, naturalModifier_, true);
135 guiBaseNotes_[3] = new StandardNote(this, 3, 5, naturalModifier_, true);
136 guiBaseNotes_[4] = new StandardNote(this, 4, 7, naturalModifier_, true);
137 guiBaseNotes_[5] = new StandardNote(this, 5, 8, naturalModifier_, true);
138 guiBaseNotes_[6] = new StandardNote(this, 6, 10, naturalModifier_, true);
139 parseBaseNotes_ = new StandardNote[7];
140 parseBaseNotes_[0] = guiBaseNotes_[0];
141 parseBaseNotes_[1] = guiBaseNotes_[1];
142 parseBaseNotes_[2] = guiBaseNotes_[2];
143 parseBaseNotes_[3] = guiBaseNotes_[3];
144 parseBaseNotes_[4] = guiBaseNotes_[4];
145 parseBaseNotes_[5] = guiBaseNotes_[5];
146 parseBaseNotes_[6] = guiBaseNotes_[6];
147 }
148
149 longIntervalStrings_ = new LinkedHashMap();
150 longIntervalStrings_.put(new StandardInterval(this, 0, -1),
151 strings_.buildString("Interval_DimUnison"));
152 longIntervalStrings_.put(new StandardInterval(this, 0, 0),
153 strings_.buildString("Interval_PerfUnison"));
154 longIntervalStrings_.put(new StandardInterval(this, 0, 1),
155 strings_.buildString("Interval_AugUnison"));
156 longIntervalStrings_.put(new StandardInterval(this, 1, 0),
157 strings_.buildString("Interval_DimSecond"));
158 longIntervalStrings_.put(new StandardInterval(this, 1, 1),
159 strings_.buildString("Interval_MinSecond"));
160 longIntervalStrings_.put(new StandardInterval(this, 1, 2),
161 strings_.buildString("Interval_MajSecond"));
162 longIntervalStrings_.put(new StandardInterval(this, 1, 3),
163 strings_.buildString("Interval_AugSecond"));
164 longIntervalStrings_.put(new StandardInterval(this, 2, 2),
165 strings_.buildString("Interval_DimThird"));
166 longIntervalStrings_.put(new StandardInterval(this, 2, 3),
167 strings_.buildString("Interval_MinThird"));
168 longIntervalStrings_.put(new StandardInterval(this, 2, 4),
169 strings_.buildString("Interval_MajThird"));
170 longIntervalStrings_.put(new StandardInterval(this, 2, 5),
171 strings_.buildString("Interval_AugThird"));
172 longIntervalStrings_.put(new StandardInterval(this, 3, 4),
173 strings_.buildString("Interval_DimFourth"));
174 longIntervalStrings_.put(new StandardInterval(this, 3, 5),
175 strings_.buildString("Interval_PerfFourth"));
176 longIntervalStrings_.put(new StandardInterval(this, 3, 6),
177 strings_.buildString("Interval_AugFourth"));
178 longIntervalStrings_.put(new StandardInterval(this, 4, 6),
179 strings_.buildString("Interval_DimFifth"));
180 longIntervalStrings_.put(new StandardInterval(this, 4, 7),
181 strings_.buildString("Interval_PerfFifth"));
182 longIntervalStrings_.put(new StandardInterval(this, 4, 8),
183 strings_.buildString("Interval_AugFifth"));
184 longIntervalStrings_.put(new StandardInterval(this, 5, 7),
185 strings_.buildString("Interval_DimSixth"));
186 longIntervalStrings_.put(new StandardInterval(this, 5, 8),
187 strings_.buildString("Interval_MinSixth"));
188 longIntervalStrings_.put(new StandardInterval(this, 5, 9),
189 strings_.buildString("Interval_MajSixth"));
190 longIntervalStrings_.put(new StandardInterval(this, 5, 10),
191 strings_.buildString("Interval_AugSixth"));
192 longIntervalStrings_.put(new StandardInterval(this, 6, 9),
193 strings_.buildString("Interval_DimSeventh"));
194 longIntervalStrings_.put(new StandardInterval(this, 6, 10),
195 strings_.buildString("Interval_MinSeventh"));
196 longIntervalStrings_.put(new StandardInterval(this, 6, 11),
197 strings_.buildString("Interval_MajSeventh"));
198 longIntervalStrings_.put(new StandardInterval(this, 6, 12),
199 strings_.buildString("Interval_AugSeventh"));
200 }
201
202
203 /*package*/ String getLongStringForNoteModifierValue(
204 int value)
205 {
206 if (value == 0) return STR_NoteModifier_Natural;
207 if (value == 1) return STR_NoteModifier_Sharp;
208 if (value == -1) return STR_NoteModifier_Flat;
209 if (value == 2) return STR_NoteModifier_DoubleSharp;
210 if (value == -2) return STR_NoteModifier_DoubleFlat;
211
212 if (value > 0)
213 {
214 return strings_.buildString("NoteModifier_MultiSharps", new Integer(value));
215 }
216 else
217 {
218 return strings_.buildString("NoteModifier_MultiFlats", new Integer(-value));
219 }
220 }
221
222
223 /*package*/ String getShortStringForNoteModifierValue(
224 int value)
225 {
226 if (value == 0) return "";
227 if (value == 1) return "#";
228 if (value == -1) return "b";
229 if (value == 2) return "x";
230 if (value == -2) return "bb";
231
232 StringBuffer sb = new StringBuffer();
233 if (value > 0)
234 {
235 for (int i=0; i<value; ++i)
236 {
237 sb.append('#');
238 }
239 }
240 else
241 {
242 for (int i=0; i>value; --i)
243 {
244 sb.append('b');
245 }
246 }
247 return new String(sb);
248 }
249
250
251 /*package*/ String getStringForNote(
252 int letterValue,
253 int pitchValue,
254 StandardNoteModifier modifier,
255 boolean isCaps)
256 {
257 char ch;
258 if (isGerman_ && letterValue == 1 && pitchValue >= 2)
259 {
260 ch = 'H';
261 }
262 else
263 {
264 ch = (char)('A'+letterValue);
265 }
266 if (!isCaps)
267 {
268 ch = (char)(ch+'a'-'A');
269 }
270 StringBuffer sb = new StringBuffer();
271 sb.append(ch);
272 sb.append(getShortStringForNoteModifierValue(modifier.getValue()));
273 return new String(sb);
274 }
275
276
277 /*package*/ StandardNote rawCreateNote(
278 int letterValue,
279 int basePitchValue,
280 int modifierValue,
281 boolean isCaps)
282 {
283 int nPitchValue = basePitchValue+modifierValue;
284 if (isGerman_ && letterValue == 1)
285 {
286 if (basePitchValue == 2 && nPitchValue <= 1)
287 {
288 // Flattening an "H" and turning it into a "B"
289 ++modifierValue;
290 }
291 else if (basePitchValue == 1 && nPitchValue >= 2)
292 {
293 // Sharpening a "B" and turning it into an "H"
294 --modifierValue;
295 }
296 }
297 return new StandardNote(this, letterValue, nPitchValue,
298 new StandardNoteModifier(this, modifierValue), isCaps);
299 }
300
301
302 /*package*/ String getLongStringForInterval(
303 StandardInterval interval)
304 {
305 String str = (String)longIntervalStrings_.get(interval);
306 if (str == null)
307 {
308 str = STR_Interval_Unknown;
309 }
310 return str;
311 }
312
313
314 /*package*/ String getStringForChord(
315 StandardNote mainNote,
316 String mainType,
317 StandardNote bassNote,
318 String bassType)
319 {
320 String mainNoteStr = "";
321 if (mainNote != null)
322 {
323 mainNoteStr = mainNote.generateString();
324 if (bassNote != null)
325 {
326 return mainNoteStr+mainType+'/'+bassNote.generateString()+bassType;
327 }
328 }
329 return mainNoteStr+mainType;
330 }
331
332
333 /*package*/ StandardNote parseNoteFromScanner(
334 StringScanner str)
335 {
336 char ch = str.nextChar();
337 if (ch == 0)
338 {
339 return null;
340 }
341 boolean isCaps = true;
342 if (ch >= 'a' && ch <= 'h')
343 {
344 ch = (char)(ch+'A'-'a');
345 isCaps = false;
346 }
347 if (ch < 'A' || ch > 'H' || (!isGerman_ && ch == 'H'))
348 {
349 str.pushBack();
350 return null;
351 }
352 StandardNote note = parseBaseNotes_[ch-'A'];
353 ch = str.nextChar();
354 if (ch == 0)
355 {
356 return new StandardNote(this, note.getLetterValue(), note.getPitchValue(),
357 (StandardNoteModifier)note.getNoteModifier(), isCaps);
358 }
359 else if (ch == 'X' || ch == 'x')
360 {
361 return rawCreateNote(note.getLetterValue(), note.getPitchValue(), 2, isCaps);
362 }
363 else if (ch == 'b' || ch == 'B')
364 {
365 int mod = -1;
366 while (true)
367 {
368 ch = str.nextChar();
369 if (ch == 0)
370 {
371 return rawCreateNote(note.getLetterValue(), note.getPitchValue(),
372 mod, isCaps);
373 }
374 else if (ch != 'b' && ch != 'B')
375 {
376 str.pushBack();
377 return rawCreateNote(note.getLetterValue(), note.getPitchValue(),
378 mod, isCaps);
379 }
380 --mod;
381 }
382 }
383 else if (ch == '#')
384 {
385 int mod = 1;
386 while (true)
387 {
388 ch = str.nextChar();
389 if (ch == 0)
390 {
391 return rawCreateNote(note.getLetterValue(), note.getPitchValue(),
392 mod, isCaps);
393 }
394 else if (ch != '#')
395 {
396 str.pushBack();
397 return rawCreateNote(note.getLetterValue(), note.getPitchValue(),
398 mod, isCaps);
399 }
400 ++mod;
401 }
402 }
403 else
404 {
405 str.pushBack();
406 return new StandardNote(this, note.getLetterValue(), note.getPitchValue(),
407 (StandardNoteModifier)note.getNoteModifier(), isCaps);
408 }
409 }
410
411
412 /*package*/ StandardChord parseChordFromScanner(
413 StringScanner scan)
414 {
415 StandardNote note = parseNoteFromScanner(scan);
416 StandardNote bass = null;
417 StringBuffer typebuf = new StringBuffer();
418 StringBuffer type2buf = new StringBuffer();
419 while (true)
420 {
421 char ch = scan.nextChar();
422 if (ch == 0)
423 {
424 break;
425 }
426 else if (ch == ' ' || ch == '\t')
427 {
428 scan.pushBack();
429 break;
430 }
431 else if (ch == '/' && bass == null)
432 {
433 bass = parseNoteFromScanner(scan);
434 if (bass == null)
435 {
436 typebuf.append(ch);
437 }
438 }
439 else if (bass == null)
440 {
441 typebuf.append(ch);
442 }
443 else
444 {
445 type2buf.append(ch);
446 }
447 }
448 return new StandardChord(this, note, new String(typebuf), bass, new String(type2buf));
449 }
450
451
452 /*package*/ StandardNote addIntervalToNote(
453 int letterValue,
454 int pitchValue,
455 boolean isCaps,
456 int letterDiff,
457 int pitchDiff)
458 {
459 letterValue += letterDiff;
460 int jumps = letterValue / 7;
461 letterValue %= 7;
462 if (letterValue < 0)
463 {
464 letterValue += 7;
465 --jumps;
466 }
467 pitchValue += pitchDiff - jumps*12;
468 StandardNote baseNote = parseBaseNotes_[letterValue];
469 int basePitch = baseNote.getPitchValue();
470 if (isGerman_ && letterValue == 1 && basePitch < pitchValue)
471 {
472 baseNote = parseBaseNotes_[7];
473 basePitch = baseNote.getPitchValue();
474 }
475 return new StandardNote(this, letterValue, pitchValue,
476 new StandardNoteModifier(this, pitchValue-basePitch), isCaps);
477 }
478
479
480 // Gui builder methods
481
482 /**
483 * Get an array of the common intervals.
484 * This is used to construct choosers for intervals.
485 *
486 * @return array of Interval
487 */
488 public Interval[] getCommonIntervals()
489 {
490 Interval[] ret = new Interval[longIntervalStrings_.size()];
491 int index = 0;
492 for (Iterator iter = longIntervalStrings_.keySet().iterator(); iter.hasNext(); )
493 {
494 ret[index++] = (Interval)iter.next();
495 }
496 return ret;
497 }
498
499
500 /**
501 * Get an array of the base notes-- that is, with a default modifier (usually natural).
502 * This is used to construct choosers for notes.
503 *
504 * @return array of Note
505 */
506 public Note[] getBaseNotes()
507 {
508 Note[] ret = new Note[guiBaseNotes_.length];
509 System.arraycopy(guiBaseNotes_, 0, ret, 0, guiBaseNotes_.length);
510 return ret;
511 }
512
513
514 /**
515 * Get an array of common modifiers for the given base note.
516 * This is used to construct choosers for notes.
517 *
518 * @param note Note to modify
519 * @return array of common modifiers for the given note.
520 */
521 public NoteModifier[] getCommonModifiersForNote(
522 Note note)
523 {
524 StandardNote snote = (StandardNote)note;
525 if (isGerman_ && snote.getLetterValue() == 1)
526 {
527 if (snote.getPitchValue() >= 2)
528 {
529 NoteModifier[] ret = new NoteModifier[2];
530 ret[0] = naturalModifier_;
531 ret[1] = sharpModifier_;
532 return ret;
533 }
534 else
535 {
536 NoteModifier[] ret = new NoteModifier[1];
537 ret[0] = naturalModifier_;
538 return ret;
539 }
540 }
541 else
542 {
543 NoteModifier[] ret = new NoteModifier[3];
544 ret[0] = naturalModifier_;
545 ret[1] = sharpModifier_;
546 ret[2] = flatModifier_;
547 return ret;
548 }
549 }
550
551
552 // Other note modifier methods methods
553
554 /**
555 * Is the given modifier allowed for the given note?
556 *
557 * @param n the note
558 * @return default NoteModifier
559 */
560 public boolean isModifierAllowedForNote(
561 Note note,
562 NoteModifier mod)
563 {
564 StandardNote snote = (StandardNote)note;
565 StandardNoteModifier smod = (StandardNoteModifier)mod;
566 if (isGerman_ && snote.getLetterValue() == 1)
567 {
568 if (snote.getPitchValue() >= 2 && smod.getValue() < 0)
569 {
570 return false;
571 }
572 if (snote.getPitchValue() <= 1 && smod.getValue() > 0)
573 {
574 return false;
575 }
576 }
577 return true;
578 }
579
580
581 /**
582 * Get the default NoteModifier (typically natural) for the given note.
583 *
584 * @param n the note
585 * @return default NoteModifier
586 */
587 public NoteModifier getDefaultNoteModifierFor(
588 Note n)
589 {
590 return naturalModifier_;
591 }
592
593
594 // Parsing methods
595
596 /**
597 * Parse a Note from a String
598 *
599 * @param str source String
600 * @return resulting Note
601 */
602 public Note parseNote(
603 String str)
604 {
605 StringScanner scan = new StringScanner(str);
606 StandardNote note = parseNoteFromScanner(scan);
607 if (scan.nextChar() == 0)
608 {
609 return note;
610 }
611 else
612 {
613 return null;
614 }
615 }
616
617
618 /**
619 * Parse a Note array from a String
620 *
621 * @param str source String
622 * @return resulting Note array
623 */
624 public Note[] parseNoteArray(
625 String str)
626 {
627 List list = new ArrayList();
628 StringScanner scan = new StringScanner(str);
629 while (true)
630 {
631 StandardNote note = parseNoteFromScanner(scan);
632 if (note == null)
633 {
634 break;
635 }
636 list.add(note);
637 if (scan.nextChar() != ' ')
638 {
639 break;
640 }
641 }
642 Note[] ret = new Note[list.size()];
643 list.toArray(ret);
644 return ret;
645 }
646
647
648 /**
649 * Parse a Chord from a String
650 *
651 * @param str source String
652 * @return resulting Chord
653 */
654 public Chord parseChord(
655 String str)
656 {
657 StringScanner scan = new StringScanner(str);
658 StandardChord chord = parseChordFromScanner(scan);
659 if (scan.nextChar() == 0)
660 {
661 return chord;
662 }
663 else
664 {
665 return null;
666 }
667 }
668
669
670 /**
671 * Parse a Chord array from a String
672 *
673 * @param str source String
674 * @return resulting Chord array
675 */
676 public Chord[] parseChordArray(
677 String str)
678 {
679 List list = new ArrayList();
680 StringScanner scan = new StringScanner(str);
681
682 boolean isAllEmpty = true;
683 while (true)
684 {
685 StandardChord chord = parseChordFromScanner(scan);
686 if (isAllEmpty && !chord.isEmpty())
687 {
688 isAllEmpty = false;
689 }
690 list.add(chord);
691 char ch = scan.nextChar();
692 if (ch == 0)
693 {
694 break;
695 }
696 }
697
698 if (isAllEmpty)
699 {
700 list.remove(list.size()-1);
701 }
702 Chord[] ret = new Chord[list.size()];
703 list.toArray(ret);
704 return ret;
705 }
706
707
708 /**
709 * Generate a string for a chord array
710 *
711 * @param chords array of Chords
712 * @return String
713 */
714 public String unparseChordArray(
715 Chord[] chords)
716 {
717 StringBuffer sb = new StringBuffer();
718 boolean firstChord = true;
719 boolean isAllEmpty = true;
720 for (int i=0; i<chords.length; ++i)
721 {
722 if (firstChord)
723 {
724 firstChord = false;
725 }
726 else
727 {
728 sb.append(' ');
729 }
730 sb.append(chords[i].generateString());
731 if (isAllEmpty && !chords[i].isEmpty())
732 {
733 isAllEmpty = false;
734 }
735 }
736 if (isAllEmpty && chords.length > 0)
737 {
738 sb.append(' ');
739 }
740 return new String(sb);
741 }
742
743
744 // Convenience constructors
745
746 /**
747 * Construct an empty Chord
748 *
749 * @return new empty Chord
750 */
751 public Chord getEmptyChord()
752 {
753 return emptyChord_;
754 }
755
756
757 /**
758 * Construct a default Note
759 *
760 * @return new Note
761 */
762 public Note getDefaultNote()
763 {
764 return defaultNote_;
765 }
766 }