OSDN Git Service

Chordクラスのイミュータブル化
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Sat, 31 Dec 2016 06:03:38 +0000 (15:03 +0900)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Sat, 31 Dec 2016 06:03:38 +0000 (15:03 +0900)
src/camidion/chordhelper/ChordHelperApplet.java
src/camidion/chordhelper/chordmatrix/ChordMatrix.java
src/camidion/chordhelper/music/Chord.java
src/camidion/chordhelper/music/ChordProgression.java
src/camidion/chordhelper/music/Music.java
src/camidion/chordhelper/music/NoteSymbol.java

index 72e5f9f..bc0644a 100644 (file)
@@ -283,7 +283,7 @@ public class ChordHelperApplet extends JApplet {
         */
        public static class VersionInfo {
                public static final String      NAME = "MIDI Chord Helper";
-               public static final String      VERSION = "Ver.20161230.1";
+               public static final String      VERSION = "Ver.20161231.1";
                public static final String      COPYRIGHT = "Copyright (C) 2004-2016";
                public static final String      AUTHER = "@きよし - Akiyoshi Kamide";
                public static final String      URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
@@ -796,7 +796,7 @@ public class ChordHelperApplet extends JApplet {
                }
                // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
                Key originalKey = chordMatrix.getKeySignatureCapo();
-               Chord originalChord = playChord.transposedChord(
+               Chord originalChord = playChord.transposedNewChord(
                        chordMatrix.capoSelecter.getCapo(),
                        chordMatrix.getKeySignature()
                );
@@ -862,7 +862,7 @@ public class ChordHelperApplet extends JApplet {
                if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
                        diagramChord = playChord;
                else
-                       diagramChord = originalChord.transposedChord(-chordDiagramCapo, originalKey);
+                       diagramChord = originalChord.transposedNewChord(-chordDiagramCapo, originalKey);
                chordDiagram.setChord(diagramChord);
                if( chordDiagram.recordTextButton.isSelected() )
                        lyricDisplay.appendChord(diagramChord);
index fe9fc91..ed560c3 100644 (file)
@@ -22,7 +22,6 @@ import java.awt.event.MouseMotionListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 import javax.swing.JComponent;
@@ -539,32 +538,32 @@ public class ChordMatrix extends JPanel
                Component obj = e.getComponent();
                if( obj instanceof ChordLabel ) {
                        ChordLabel cl = (ChordLabel)obj;
-                       Chord chord = cl.chord.clone();
+                       List<Chord.Interval> intervals = new ArrayList<>(cl.chord.intervals());
                        if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
                                if( e.isShiftDown() )
-                                       chord.set(Chord.Interval.MAJOR_SEVENTH);
+                                       intervals.add(Chord.Interval.MAJOR_SEVENTH);
                                else
-                                       chord.set(Chord.Interval.SEVENTH);
+                                       intervals.add(Chord.Interval.SEVENTH);
                        }
                        else if( e.isShiftDown() )
-                               chord.set(Chord.Interval.SIXTH);
+                               intervals.add(Chord.Interval.SIXTH);
                        if( e.isControlDown() )
-                               chord.set(Chord.Interval.NINTH);
+                               intervals.add(Chord.Interval.NINTH);
                        else
-                               chord.clear(Chord.Interval.NINTH);
+                               intervals.remove(Chord.Interval.NINTH);
 
                        if( e.isAltDown() ) {
                                if( cl.isSus4 ) {
-                                       chord.set(Chord.Interval.MAJOR); // To cancel sus4
-                                       chord.set(Chord.Interval.SHARP5);
+                                       intervals.add(Chord.Interval.MAJOR); // To cancel sus4
+                                       intervals.add(Chord.Interval.SHARP5);
                                }
-                               else chord.set(Chord.Interval.FLAT5);
+                               else intervals.add(Chord.Interval.FLAT5);
                        }
                        if( selectedChordLabel != null ) {
                                selectedChordLabel.setSelection(false);
                        }
                        (selectedChordLabel = cl).setSelection(true);
-                       setSelectedChord(chord);
+                       setSelectedChord(new Chord(cl.chord, intervals));
                }
                else if( obj instanceof KeySignatureLabel ) {
                        int v = ((KeySignatureLabel)obj).co5Value;
@@ -603,19 +602,19 @@ public class ChordMatrix extends JPanel
                                destinationChordLabel = null; return;
                        }
                        if( destinationChordLabel != null ) return;
-                       Chord chord = labelDraggedFrom.chord.clone();
+                       List<Chord.Interval> intervals = new ArrayList<>(labelDraggedFrom.chord.intervals());
                        if( labelDraggedFrom.isMinor ) {
                                if( labelDraggedTo == null ) { // Out of chord buttons
                                        // mM7
-                                       chord.set(Chord.Interval.MAJOR_SEVENTH);
+                                       intervals.add(Chord.Interval.MAJOR_SEVENTH);
                                }
                                else if( labelDraggedFrom.co5Value < labelDraggedTo.co5Value ) { // Right
                                        // m6
-                                       chord.set(Chord.Interval.SIXTH);
+                                       intervals.add(Chord.Interval.SIXTH);
                                }
                                else { // Left or up from minor to major
                                        // m7
-                                       chord.set(Chord.Interval.SEVENTH);
+                                       intervals.add(Chord.Interval.SEVENTH);
                                }
                        }
                        else if( labelDraggedFrom.isSus4 ) {
@@ -623,14 +622,14 @@ public class ChordMatrix extends JPanel
                                        return;
                                }
                                else if( ! labelDraggedTo.isSus4 ) { // Down from sus4 to major
-                                       chord.set(Chord.Interval.MAJOR);
+                                       intervals.add(Chord.Interval.MAJOR);
                                }
                                else if( labelDraggedFrom.co5Value < labelDraggedTo.co5Value ) { // Right
-                                       chord.set(Chord.Interval.NINTH);
+                                       intervals.add(Chord.Interval.NINTH);
                                }
                                else { // Left
                                        // 7sus4
-                                       chord.set(Chord.Interval.SEVENTH);
+                                       intervals.add(Chord.Interval.SEVENTH);
                                }
                        }
                        else {
@@ -638,50 +637,50 @@ public class ChordMatrix extends JPanel
                                        return;
                                }
                                else if( labelDraggedTo.isSus4 ) { // Up from major to sus4
-                                       chord.set(Chord.Interval.NINTH);
+                                       intervals.add(Chord.Interval.NINTH);
                                }
                                else if( labelDraggedFrom.co5Value < labelDraggedTo.co5Value ) { // Right
                                        // M7
-                                       chord.set(Chord.Interval.MAJOR_SEVENTH);
+                                       intervals.add(Chord.Interval.MAJOR_SEVENTH);
                                }
                                else if( labelDraggedTo.isMinor ) { // Down from major to minor
                                        // 6
-                                       chord.set(Chord.Interval.SIXTH);
+                                       intervals.add(Chord.Interval.SIXTH);
                                }
                                else { // Left
                                        // 7
-                                       chord.set(Chord.Interval.SEVENTH);
+                                       intervals.add(Chord.Interval.SEVENTH);
                                }
                        }
-                       if( chord.isSet(Chord.Interval.NINTH) || (labelDraggedFrom.isSus4 && (labelDraggedTo == null || ! labelDraggedTo.isSus4) ) ) {
+                       if( intervals.contains(Chord.Interval.NINTH) || (labelDraggedFrom.isSus4 && (labelDraggedTo == null || ! labelDraggedTo.isSus4) ) ) {
                                if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
                                        if( e.isShiftDown() ) {
-                                               chord.set(Chord.Interval.MAJOR_SEVENTH);
+                                               intervals.add(Chord.Interval.MAJOR_SEVENTH);
                                        }
                                        else {
-                                               chord.set(Chord.Interval.SEVENTH);
+                                               intervals.add(Chord.Interval.SEVENTH);
                                        }
                                }
                                else if( e.isShiftDown() ) {
-                                       chord.set(Chord.Interval.SIXTH);
+                                       intervals.add(Chord.Interval.SIXTH);
                                }
                        }
                        else {
                                if( e.isControlDown() )
-                                       chord.set(Chord.Interval.NINTH);
+                                       intervals.add(Chord.Interval.NINTH);
                                else
-                                       chord.clear(Chord.Interval.NINTH);
+                                       intervals.remove(Chord.Interval.NINTH);
                        }
                        if( e.isAltDown() ) {
                                if( labelDraggedFrom.isSus4 ) {
-                                       chord.set(Chord.Interval.MAJOR);
-                                       chord.set(Chord.Interval.SHARP5);
+                                       intervals.add(Chord.Interval.MAJOR);
+                                       intervals.add(Chord.Interval.SHARP5);
                                }
                                else {
-                                       chord.set(Chord.Interval.FLAT5);
+                                       intervals.add(Chord.Interval.FLAT5);
                                }
                        }
-                       setSelectedChord(chord);
+                       setSelectedChord(new Chord(labelDraggedFrom.chord, intervals));
                        destinationChordLabel = (labelDraggedTo == null ? labelDraggedFrom : labelDraggedTo ) ;
                }
                else if( draggedFrom instanceof KeySignatureLabel ) {
@@ -788,7 +787,7 @@ public class ChordMatrix extends JPanel
                if( i < 0 ) return; // No key char found
                if( iCol < 0 ) iCol += 12; else if( iCol > N_COLUMNS ) iCol -= 12;
                cl = chordLabels[iCol + N_COLUMNS * iRow];
-               List<Chord.Interval> intervals = new ArrayList<Chord.Interval>(Arrays.asList(cl.chord.intervals()));
+               List<Chord.Interval> intervals = new ArrayList<>(cl.chord.intervals());
                if( shiftPressed ) {
                        if( ! intervals.contains(Chord.Interval.SEVENTH) ) {
                                intervals.add(Chord.Interval.SEVENTH);
@@ -897,7 +896,7 @@ public class ChordMatrix extends JPanel
        protected void capoChanged(int newCapo) {
                if(capo == newCapo) return;
                capoKey = key.transposedKey(capo = newCapo);
-               selectedChordCapo = (selectedChord == null ? null : selectedChord.transposedChord(newCapo));
+               selectedChordCapo = (selectedChord == null ? null : selectedChord.transposedNewChord(newCapo));
                for( ChordLabel cl : chordLabels ) cl.keyChanged();
                fireKeySignatureChanged();
        }
@@ -966,14 +965,14 @@ public class ChordMatrix extends JPanel
        }
        public void setSelectedChordCapo( Chord chord ) {
                setNoteIndex(-1); // Cancel arpeggio mode
-               selectedChord = (chord == null ? null : chord.transposedChord(-capo,capoKey));
+               selectedChord = (chord == null ? null : chord.transposedNewChord(-capo,capoKey));
                selectedChordCapo = chord;
                fireChordChanged();
        }
        public void setSelectedChord( Chord chord ) {
                setNoteIndex(-1); // Cancel arpeggio mode
                selectedChord = chord;
-               selectedChordCapo = (chord == null ? null : chord.transposedChord(capo,key));
+               selectedChordCapo = (chord == null ? null : chord.transposedNewChord(capo,key));
                fireChordChanged();
        }
        /**
index d5267cf..5092825 100644 (file)
@@ -3,16 +3,19 @@ package camidion.chordhelper.music;
 import java.awt.Color;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Vector;
 
 import javax.swing.JLabel;
 
 /**
- * 和音(コード - musical chord)のクラス
+ * 和音(コード - musical chord)のクラス(値は不変)
  */
-public class Chord implements Cloneable {
+public class Chord {
        /** コード構成音の順序に対応する色 */
        public static final Color NOTE_INDEX_COLORS[] = {
                Color.red,
@@ -23,71 +26,71 @@ public class Chord implements Cloneable {
                Color.orange,
                Color.green
        };
-       /** 音程差の半音オフセットのインデックス */
-       private static enum OffsetIndex {
+       /** 音程差のグループ分け */
+       private static enum IntervalGroup {
                THIRD, FIFTH, SEVENTH, NINTH, ELEVENTH, THIRTEENTH
        }
        /** 音程差 */
        public static enum Interval {
 
                /** 長2度(major 2nd / sus2) */
-               SUS2(2, OffsetIndex.THIRD, "sus2", "suspended 2nd"),
+               SUS2(2, IntervalGroup.THIRD, "sus2", "suspended 2nd"),
                /** 短3度または増2度 */
-               MINOR(3, OffsetIndex.THIRD, "m", "minor"),
+               MINOR(3, IntervalGroup.THIRD, "m", "minor"),
                /** 長3度 */
-               MAJOR(4, OffsetIndex.THIRD, "", "major"),
+               MAJOR(4, IntervalGroup.THIRD, "", "major"),
                /** 完全4度(parfect 4th / sus4) */
-               SUS4(5, OffsetIndex.THIRD, "sus4", "suspended 4th"),
+               SUS4(5, IntervalGroup.THIRD, "sus4", "suspended 4th"),
 
                /** 減5度または増4度(トライトーン = 三全音 = 半オクターブ) */
-               FLAT5(6, OffsetIndex.FIFTH, "-5", "flatted 5th"),
+               FLAT5(6, IntervalGroup.FIFTH, "-5", "flatted 5th"),
                /** 完全5度 */
-               PARFECT5(7, OffsetIndex.FIFTH, "", "parfect 5th"),
+               PARFECT5(7, IntervalGroup.FIFTH, "", "parfect 5th"),
                /** 増5度または短6度 */
-               SHARP5(8, OffsetIndex.FIFTH, "+5", "sharped 5th"),
+               SHARP5(8, IntervalGroup.FIFTH, "+5", "sharped 5th"),
 
                /** 長6度または減7度 */
-               SIXTH(9, OffsetIndex.SEVENTH, "6", "6th"),
+               SIXTH(9, IntervalGroup.SEVENTH, "6", "6th"),
                /** 短7度 */
-               SEVENTH(10, OffsetIndex.SEVENTH, "7", "7th"),
+               SEVENTH(10, IntervalGroup.SEVENTH, "7", "7th"),
                /** 長7度 */
-               MAJOR_SEVENTH(11, OffsetIndex.SEVENTH, "M7", "major 7th"),
+               MAJOR_SEVENTH(11, IntervalGroup.SEVENTH, "M7", "major 7th"),
 
                /** 短9度(短2度の1オクターブ上) */
-               FLAT9(13, OffsetIndex.NINTH, "-9", "flatted 9th"),
+               FLAT9(13, IntervalGroup.NINTH, "-9", "flatted 9th"),
                /** 長9度(長2度の1オクターブ上) */
-               NINTH(14, OffsetIndex.NINTH, "9", "9th"),
+               NINTH(14, IntervalGroup.NINTH, "9", "9th"),
                /** 増9度(増2度の1オクターブ上) */
-               SHARP9(15, OffsetIndex.NINTH, "+9", "sharped 9th"),
+               SHARP9(15, IntervalGroup.NINTH, "+9", "sharped 9th"),
 
                /** 完全11度(完全4度の1オクターブ上) */
-               ELEVENTH(17, OffsetIndex.ELEVENTH, "11", "11th"),
+               ELEVENTH(17, IntervalGroup.ELEVENTH, "11", "11th"),
                /** 増11度(増4度の1オクターブ上) */
-               SHARP11(18, OffsetIndex.ELEVENTH, "+11", "sharped 11th"),
+               SHARP11(18, IntervalGroup.ELEVENTH, "+11", "sharped 11th"),
 
                /** 短13度(短6度の1オクターブ上) */
-               FLAT13(20, OffsetIndex.THIRTEENTH, "-13", "flatted 13th"),
+               FLAT13(20, IntervalGroup.THIRTEENTH, "-13", "flatted 13th"),
                /** 長13度(長6度の1オクターブ上) */
-               THIRTEENTH(21, OffsetIndex.THIRTEENTH, "13", "13th");
+               THIRTEENTH(21, IntervalGroup.THIRTEENTH, "13", "13th");
 
-               private Interval(int chromaticOffset, OffsetIndex offsetIndex, String symbol, String description) {
+               private Interval(int chromaticOffset, IntervalGroup offsetIndex, String symbol, String description) {
                        this.chromaticOffset = chromaticOffset;
-                       this.offsetIndex = offsetIndex;
+                       this.intervalGroup = offsetIndex;
                        this.symbol = symbol;
                        this.description = description;
                }
                /**
-                * 半音差を返します。
+                * 半音差を返します。これはルート音からの音程差を半音単位で表した値です。
                 * @return 半音差
                 */
                public int getChromaticOffset() { return chromaticOffset; }
                private int chromaticOffset;
                /**
-                * 対応するインデックスを返します。
-                * @return 対応するインデックス
+                * この音程が属しているグループを返します。
+                * @return この音程が属しているグループ
                 */
-               public OffsetIndex getChromaticOffsetIndex() { return offsetIndex; }
-               private OffsetIndex offsetIndex;
+               public IntervalGroup getIntervalGroup() { return intervalGroup; }
+               private IntervalGroup intervalGroup;
                /**
                 * コード名に使う略称を返します。
                 * @return 略称
@@ -107,72 +110,63 @@ public class Chord implements Cloneable {
         */
        public String symbolSuffix() {
                String suffix = "";
-               Interval itv3rd = offsets.get(OffsetIndex.THIRD);
-               Interval itv5th = offsets.get(OffsetIndex.FIFTH);
-               Interval itv7th = offsets.get(OffsetIndex.SEVENTH);
-               if( itv3rd == Interval.MINOR ) {
-                       suffix += itv3rd.getSymbol();
-               }
-               if( itv7th != null ) {
-                       suffix += itv7th.getSymbol();
-               }
-               if( Arrays.asList(Interval.SUS2, Interval.SUS4).contains(itv3rd) ) {
-                       suffix += itv3rd.getSymbol();
-               }
-               if( itv5th != Interval.PARFECT5 ) {
-                       suffix += itv5th.getSymbol();
-               }
+               Interval i3 = intervalMap.get(IntervalGroup.THIRD);
+               Interval i5 = intervalMap.get(IntervalGroup.FIFTH);
+               Interval i7 = intervalMap.get(IntervalGroup.SEVENTH);
+               if( Interval.MINOR == i3 ) suffix += i3.getSymbol();
+               if( i7 != null ) suffix += i7.getSymbol();
+               if( SUSPENDED.contains(i3) ) suffix += i3.getSymbol();
+               if( Interval.PARFECT5 != i5 ) suffix += i5.getSymbol();
                Vector<String> inParen = new Vector<String>();
-               for( OffsetIndex index : Arrays.asList(OffsetIndex.NINTH, OffsetIndex.ELEVENTH, OffsetIndex.THIRTEENTH) ) {
-                       Interval interval = offsets.get(index);
+               for( IntervalGroup index : EXTENDED ) {
+                       Interval interval = intervalMap.get(index);
                        if( interval != null ) inParen.add(interval.getSymbol());
                }
                if( ! inParen.isEmpty() ) suffix += "("+String.join(",",inParen)+")";
                String alias = symbolSuffixAliases.get(suffix);
                return alias == null ? suffix : alias;
        }
-       private static final Map<String, String> symbolSuffixAliases = new HashMap<String, String>() {
-               {
-                       put("m-5", "dim");
-                       put("+5", "aug");
-                       put("m6-5", "dim7");
-                       put("(9)", "add9");
-                       put("7(9)", "9");
-                       put("M7(9)", "M9");
-                       put("7+5", "aug7");
-                       put("m6-5(9)", "dim9");
-               }
-       };
        /**
         * コードの説明のうち、音名を除いた部分を組み立てて返します。
         * @return コード説明の音名を除いた部分
         */
        public String nameSuffix() {
                String suffix = "";
-               Interval itv3rd = offsets.get(OffsetIndex.THIRD);
-               Interval itv5th = offsets.get(OffsetIndex.FIFTH);
-               Interval itv7th = offsets.get(OffsetIndex.SEVENTH);
-               if( itv3rd == Interval.MINOR ) {
-                       suffix += " " + itv3rd.getDescription();
-               }
-               if( itv7th != null ) {
-                       suffix += " " + itv7th.getDescription();
-               }
-               if( Arrays.asList(Interval.SUS2, Interval.SUS4).contains(itv3rd) ) {
-                       suffix += " " + itv3rd.getDescription();
-               }
-               if( itv5th != Interval.PARFECT5 ) {
-                       suffix += " " + itv5th.getDescription();
-               }
+               Interval i3 = intervalMap.get(IntervalGroup.THIRD);
+               Interval i5 = intervalMap.get(IntervalGroup.FIFTH);
+               Interval i7 = intervalMap.get(IntervalGroup.SEVENTH);
+               if( Interval.MINOR == i3 ) suffix += " " + i3.getDescription();
+               if( i7 != null ) suffix += " " + i7.getDescription();
+               if( SUSPENDED.contains(i3) ) suffix += " " + i3.getDescription();
+               if( Interval.PARFECT5 != i5 ) suffix += " " + i5.getDescription();
                Vector<String> inParen = new Vector<String>();
-               for( OffsetIndex index : Arrays.asList(OffsetIndex.NINTH, OffsetIndex.ELEVENTH, OffsetIndex.THIRTEENTH) ) {
-                       Interval interval = offsets.get(index);
-                       if( interval != null ) inParen.add(interval.getDescription());
+               for( IntervalGroup index : EXTENDED ) {
+                       Interval i9 = intervalMap.get(index);
+                       if( i9 != null ) inParen.add(i9.getDescription());
                }
                if( ! inParen.isEmpty() ) suffix += "("+String.join(",",inParen)+")";
                String alias = nameSuffixAliases.get(suffix);
                return alias == null ? suffix : alias;
        }
+       private static final List<Interval> SUSPENDED = Arrays.asList(
+                       Interval.SUS2,
+                       Interval.SUS4);
+       private static final List<IntervalGroup> EXTENDED = Arrays.asList(
+                       IntervalGroup.NINTH,
+                       IntervalGroup.ELEVENTH,
+                       IntervalGroup.THIRTEENTH);
+       private static final Map<String, String> symbolSuffixAliases = new HashMap<String, String>() {
+               {
+                       put("m-5", "dim");
+                       put("+5", "aug");
+                       put("m6-5", "dim7");
+                       put("(9)", "add9");
+                       put("7(9)", "9");
+                       put("M7(9)", "M9");
+                       put("7+5", "aug7");
+                       put("m6-5(9)", "dim9");
+               }
+       };
        private static final Map<String, String> nameSuffixAliases = new HashMap<String, String>() {
                {
                        put("", " "+Interval.MAJOR.getDescription());
@@ -186,24 +180,51 @@ public class Chord implements Cloneable {
                        put(" minor 6th flatted 5th(9th)", " diminished 9th");
                }
        };
-
        /**
-        * 現在有効な構成音(ルート、ベースは除く)の音程
-        */
-       private Map<OffsetIndex, Interval> offsets;
-       /**
-        * このコードのルート音
+        * ルート音を返します。
+        * @return ルート音
         */
+       public NoteSymbol rootNoteSymbol() { return rootNoteSymbol; }
        private NoteSymbol rootNoteSymbol;
        /**
-        * このコードのベース音(ルート音と異なる場合は分数コードの分母)
+        * ベース音を返します。オンコードの場合はルート音と異なります。
+        * @return ベース音
         */
+       public NoteSymbol bassNoteSymbol() { return bassNoteSymbol; }
        private NoteSymbol bassNoteSymbol;
+       /**
+        * 指定した音程が設定されているか調べます。
+        * @param interval 音程
+        * @return 指定した音程が設定されていたらtrue
+        */
+       public boolean isSet(Interval interval) {
+               return interval.equals(intervalMap.get(interval.getIntervalGroup()));
+       }
+       /**
+        * このコードの構成音(ルート音自身やベース音は含まない)を、
+        * ルート音からの音程のコレクション(変更不可能なビュー)として返します。
+        * @return 音程のコレクション
+        */
+       public Collection<Interval> intervals() { return intervals; }
+       private Collection<Interval> intervals;
+       private void fixIntervals() {
+               intervals = Collections.unmodifiableCollection(intervalMap.values());
+       }
+       /** 現在有効な構成音(ルート、ベースは除く)の音程 */
+       private Map<IntervalGroup, Interval> intervalMap;
+       private void set(Interval interval) {
+               intervalMap.put(interval.getIntervalGroup(), interval);
+       }
+       private void set(Collection<Interval> intervals) {
+               for(Interval interval : intervals) if(interval != null) set(interval);
+       }
 
        /**
-        * 指定されたルート音と構成音を持つコードを構築します。
-        * @param root ルート音(ベース音としても使う)
-        * @param intervals その他の構成音の音程(長三度、完全五度は指定しなくてもデフォルトで設定されます)
+        * 指定されたルート音、構成音を持つコードを構築します。
+        * @param root ルート音
+        * @param intervals その他の構成音の音程
+        * (メジャーコードの構成音である長三度・完全五度は、指定しなくてもデフォルトで設定されます)
+        * @throws NullPointerException ルート音にnullが指定された場合
         */
        public Chord(NoteSymbol root, Interval... intervals) {
                this(root, root, intervals);
@@ -211,8 +232,10 @@ public class Chord implements Cloneable {
        /**
         * 指定されたルート音、ベース音、構成音を持つコードを構築します。
         * @param root ルート音
-        * @param bass ベース音
-        * @param intervals その他の構成音の音程(長三度、完全五度は指定しなくてもデフォルトで設定されます)
+        * @param bass ベース音(nullの場合はルート音が指定されたとみなされます)
+        * @param intervals その他の構成音の音程
+        * (メジャーコードの構成音である長三度・完全五度は、指定しなくてもデフォルトで設定されます)
+        * @throws NullPointerException ルート音にnullが指定された場合
         */
        public Chord(NoteSymbol root, NoteSymbol bass, Interval... intervals) {
                this(root, bass, Arrays.asList(intervals));
@@ -220,16 +243,19 @@ public class Chord implements Cloneable {
        /**
         * 指定されたルート音、ベース音、構成音を持つコードを構築します。
         * @param root ルート音
-        * @param bass ベース音
-        * @param intervals その他の構成音の音程(長三度、完全五度は指定しなくてもデフォルトで設定されます)
+        * @param bass ベース音(nullの場合はルート音が指定されたとみなされます)
+        * @param intervals その他の構成音の音程のコレクション
+        * (メジャーコードの構成音である長三度・完全五度は、指定しなくてもデフォルトで設定されます)
+        * @throws NullPointerException ルート音、または構成音コレクションにnullが指定された場合
         */
        public Chord(NoteSymbol root, NoteSymbol bass, Collection<Interval> intervals) {
-               rootNoteSymbol = root;
-               bassNoteSymbol = bass;
-               offsets = new HashMap<>();
+               rootNoteSymbol = Objects.requireNonNull(root);
+               bassNoteSymbol = (bass==null ? root : bass);
+               intervalMap = new HashMap<>();
                set(Interval.MAJOR);
                set(Interval.PARFECT5);
                set(intervals);
+               fixIntervals();
        }
        /**
         * 元のコードのルート音、ベース音以外の構成音の一部を変更した新しいコードを構築します。
@@ -243,14 +269,15 @@ public class Chord implements Cloneable {
        /**
         * 元のコードのルート音、ベース音以外の構成音の一部を変更した新しいコードを構築します。
         * @param chord 元のコード
-        * @param intervals 変更したい構成音の音程(ルート音、ベース音を除く)
-        * @throws NullPointerException å\85\83ã\81®ã\82³ã\83¼ã\83\89にnullが指定された場合
+        * @param intervals 変更したい構成音の音程(ルート音、ベース音を除く)のコレクション
+        * @throws NullPointerException å¼\95æ\95°にnullが指定された場合
         */
        public Chord(Chord original, Collection<Interval> intervals) {
                rootNoteSymbol = original.rootNoteSymbol;
                bassNoteSymbol = original.bassNoteSymbol;
-               offsets = new HashMap<>(original.offsets);
+               intervalMap = new HashMap<>(original.intervalMap);
                set(intervals);
+               fixIntervals();
        }
        /**
         * 指定された調と同名のコードを構築します。
@@ -258,11 +285,12 @@ public class Chord implements Cloneable {
         * @throws NullPointerException 調にnullが指定された場合
         */
        public Chord(Key key) {
-               offsets = new HashMap<>(); set(Interval.PARFECT5);
+               intervalMap = new HashMap<>(); set(Interval.PARFECT5);
                int keyCo5 = key.toCo5(); if( key.majorMinor() == Key.MajorMinor.MINOR ) {
                        keyCo5 += 3; set(Interval.MINOR);
                } else set(Interval.MAJOR);
                bassNoteSymbol = rootNoteSymbol = new NoteSymbol(keyCo5);
+               fixIntervals();
        }
        /**
         * コード名からコードを構築します。
@@ -270,7 +298,7 @@ public class Chord implements Cloneable {
         * @throws NullPointerException コード名にnullが指定された場合
         */
        public Chord(String chordSymbol) {
-               offsets = new HashMap<>();
+               intervalMap = new HashMap<>();
                set(Interval.MAJOR);
                set(Interval.PARFECT5);
                //
@@ -278,6 +306,7 @@ public class Chord implements Cloneable {
                String rootOnBass[] = chordSymbol.trim().split("(/|on)");
                if( rootOnBass.length == 0 ) {
                        bassNoteSymbol = rootNoteSymbol = new NoteSymbol();
+                       fixIntervals();
                        return;
                }
                rootNoteSymbol = new NoteSymbol(rootOnBass[0]);
@@ -292,7 +321,10 @@ public class Chord implements Cloneable {
                //
                // ()の中と外に分ける
                String suffixWithParen[] = suffix.split("[\\(\\)]");
-               if( suffixWithParen.length == 0 ) return;
+               if( suffixWithParen.length == 0 ) {
+                       fixIntervals();
+                       return;
+               }
                String suffixParen = "";
                if( suffixWithParen.length > 1 ) {
                        suffixParen = suffixWithParen[1];
@@ -336,32 +368,7 @@ public class Chord implements Cloneable {
                                else if( p.matches("(\\+5|#5)") ) set(Interval.SHARP5);
                        }
                }
-       }
-       /**
-        * ルート音を返します。
-        * @return ルート音
-        */
-       public NoteSymbol rootNoteSymbol() { return rootNoteSymbol; }
-       /**
-        * ベース音を返します。分数コードの場合はルート音と異なります。
-        * @return ベース音
-        */
-       public NoteSymbol bassNoteSymbol() { return bassNoteSymbol; }
-       /**
-        * このコードの構成音を、ルート音からの音程の配列として返します。
-        * ルート音自身やベース音は含まれません。
-        * @return 音程の配列
-        */
-       public Interval[] intervals() {
-               return offsets.values().toArray(new Interval[offsets.size()]);
-       }
-       /**
-        * 指定した音程が設定されているか調べます。
-        * @param itv 音程
-        * @return 指定した音程が設定されていたらtrue
-        */
-       public boolean isSet(Interval itv) {
-               return itv.equals(offsets.get(itv.getChromaticOffsetIndex()));
+               fixIntervals();
        }
        /**
         * コードの同一性を判定します。ルート音、ベース音の異名同音は異なるものとみなされます。
@@ -376,7 +383,7 @@ public class Chord implements Cloneable {
                        Chord another = (Chord) anObject;
                        if( ! rootNoteSymbol.equals(another.rootNoteSymbol) ) return false;
                        if( ! bassNoteSymbol.equals(another.bassNoteSymbol) ) return false;
-                       return offsets.equals(another.offsets);
+                       return intervalMap.equals(another.intervalMap);
                }
                return false;
        }
@@ -393,13 +400,13 @@ public class Chord implements Cloneable {
                if( another == null ) return false;
                if( ! rootNoteSymbol.equalsEnharmonically(another.rootNoteSymbol) ) return false;
                if( ! bassNoteSymbol.equalsEnharmonically(another.bassNoteSymbol) ) return false;
-               return offsets.equals(another.offsets);
+               return intervalMap.equals(another.intervalMap);
        }
        /**
         * コード構成音の数を返します。ルート音は含まれますが、ベース音は含まれません。
         * @return コード構成音の数
         */
-       public int numberOfNotes() { return offsets.size() + 1; }
+       public int numberOfNotes() { return intervalMap.size() + 1; }
        /**
         * 指定された位置にある構成音のノート番号を返します。
         * @param index 位置(0をルート音とした構成音の順序)
@@ -410,8 +417,8 @@ public class Chord implements Cloneable {
                if( index == 0 ) return rootnote;
                Interval itv;
                int i=0;
-               for( OffsetIndex offsetIndex : OffsetIndex.values() )
-                       if( (itv = offsets.get(offsetIndex)) != null && ++i == index )
+               for( IntervalGroup offsetIndex : IntervalGroup.values() )
+                       if( (itv = intervalMap.get(offsetIndex)) != null && ++i == index )
                                return rootnote + itv.getChromaticOffset();
                return -1;
        }
@@ -428,8 +435,8 @@ public class Chord implements Cloneable {
                int i;
                ia[i=0] = rootnote;
                Interval itv;
-               for( OffsetIndex offsetIndex : OffsetIndex.values() )
-                       if( (itv = offsets.get(offsetIndex)) != null )
+               for( IntervalGroup offsetIndex : IntervalGroup.values() )
+                       if( (itv = intervalMap.get(offsetIndex)) != null )
                                ia[++i] = rootnote + itv.getChromaticOffset();
                if( range != null ) range.invertNotesOf(ia, key);
                return ia;
@@ -445,8 +452,8 @@ public class Chord implements Cloneable {
                if( Music.mod12(relativeNote) == 0 ) return 0;
                Interval itv;
                int i=0;
-               for( OffsetIndex offsetIndex : OffsetIndex.values() ) {
-                       if( (itv = offsets.get(offsetIndex)) != null ) {
+               for( IntervalGroup offsetIndex : IntervalGroup.values() ) {
+                       if( (itv = intervalMap.get(offsetIndex)) != null ) {
                                i++;
                                if( Music.mod12(relativeNote - itv.getChromaticOffset()) == 0 )
                                        return i;
@@ -458,41 +465,41 @@ public class Chord implements Cloneable {
         * 指定したキーのスケールを外れた構成音がないか調べます。
         * @param key 調べるキー
         * @return スケールを外れている構成音がなければtrue
+        * @throws NullPointerException キーにnullが指定された場合
         */
-       public boolean isOnScaleIn(Key key) { return isOnScaleInKey(key.toCo5()); }
-       private boolean isOnScaleInKey(int keyCo5) {
+       public boolean isOnScaleIn(Key key) {
+               int keyCo5 = key.toCo5();
                int rootnote = rootNoteSymbol.toNoteNumber();
                if( ! Music.isOnScale(rootnote, keyCo5) ) return false;
                Interval itv;
-               for( OffsetIndex offsetIndex : OffsetIndex.values() ) {
-                       if( (itv = offsets.get(offsetIndex)) == null ) continue;
+               for( IntervalGroup offsetIndex : IntervalGroup.values() ) {
+                       if( (itv = intervalMap.get(offsetIndex)) == null ) continue;
                        if( ! Music.isOnScale(rootnote + itv.getChromaticOffset(), keyCo5) ) return false;
                }
                return true;
        }
        /**
-        * C/Amの調に近いほうの♯、♭の表記で、移調したコードを返します。
+        * 指定された調に近いほうの♯、♭の表記で、移調したコードを返します。
         * @param chromaticOffset 移調幅(半音単位)
+        * @param originalKey 基準とする調
         * @return 移調した新しいコード(移調幅が0の場合は自分自身)
         */
-       public Chord transposedChord(int chromaticOffset) {
-               return transposedChord(chromaticOffset, 0);
+       public Chord transposedNewChord(int chromaticOffset, Key originalKey) {
+               return transposedNewChord(chromaticOffset, originalKey.toCo5());
        }
        /**
-        * 指定された調に近いほうの♯、♭の表記で、移調したコードを返します。
+        * C/Amの調に近いほうの♯、♭の表記で、移調したコードを返します。
         * @param chromaticOffset 移調幅(半音単位)
-        * @param originalKey 基準とする調
         * @return 移調した新しいコード(移調幅が0の場合は自分自身)
         */
-       public Chord transposedChord(int chromaticOffset, Key originalKey) {
-               return transposedChord(chromaticOffset, originalKey.toCo5());
+       public Chord transposedNewChord(int chromaticOffset) {
+               return transposedNewChord(chromaticOffset, 0);
        }
-       private Chord transposedChord(int chromaticOffset, int originalKeyCo5) {
+       private Chord transposedNewChord(int chromaticOffset, int originalKeyCo5) {
                if( chromaticOffset == 0 ) return this;
-               int offsetCo5 = Music.mod12(Music.reverseCo5(chromaticOffset));
+               int offsetCo5 = Music.mod12(Music.toggleCo5(chromaticOffset));
                if( offsetCo5 > 6 ) offsetCo5 -= 12;
                int keyCo5 = originalKeyCo5 + offsetCo5;
-               //
                int newRootCo5 = rootNoteSymbol.toCo5() + offsetCo5;
                int newBassCo5 = bassNoteSymbol.toCo5() + offsetCo5;
                if( keyCo5 > 6 ) {
@@ -503,7 +510,9 @@ public class Chord implements Cloneable {
                        newRootCo5 += 12;
                        newBassCo5 += 12;
                }
-               return new Chord(new NoteSymbol(newRootCo5), new NoteSymbol(newBassCo5), intervals());
+               NoteSymbol root = new NoteSymbol(newRootCo5);
+               NoteSymbol bass = (newBassCo5 == newRootCo5 ? root : new NoteSymbol(newBassCo5));
+               return new Chord(root, bass, intervals);
        }
 
        /**
@@ -563,35 +572,4 @@ public class Chord implements Cloneable {
                }
                return name;
        }
-
-       /**
-        * このコードのクローンを作成します。
-        */
-       @Override
-       public Chord clone() {
-               Chord newChord = new Chord(rootNoteSymbol, bassNoteSymbol);
-               newChord.offsets = new HashMap<>(offsets);
-               return newChord;
-       }
-       /**
-        * 指定した音程の構成音を設定します。
-        * @param interval 設定する音程
-        */
-       public void set(Interval interval) {
-               offsets.put(interval.getChromaticOffsetIndex(), interval);
-       }
-       /**
-        * 指定した複数の音程の構成音を設定します。
-        * @param intervals 設定する音程
-        */
-       protected void set(Collection<Interval> intervals) {
-               for(Interval itv : intervals) if(itv != null) set(itv);
-       }
-       /**
-        * 指定した音程の構成音をクリアします。
-        * @param itv クリアする音程
-        */
-       public void clear(Interval itv) {
-               offsets.remove(itv.getChromaticOffsetIndex());
-       }
 }
\ No newline at end of file
index 9ebca40..b2652e6 100644 (file)
@@ -364,7 +364,7 @@ public class ChordProgression {
                                                // キーが未設定のときは、最初のコードから推測して設定
                                                if( key == null ) key = new Key(cs.chord);
                                                //
-                                               Chord newChord = cs.chord.transposedChord(chromaticOffset, key);
+                                               Chord newChord = cs.chord.transposedNewChord(chromaticOffset, key);
                                                measure.set(i, new ChordStroke(newChord, cs.beatLength));
                                        }
                                }
index edd260f..3bce8ac 100644 (file)
@@ -5,7 +5,7 @@ package camidion.chordhelper.music;
  *
  * Circle-Of-Fifth based music theory functions
  *
- * @author Copyright (C) 2004-2014 @きよし - Akiyoshi Kamide
+ * @author Copyright (C) 2004-2016 @きよし - Akiyoshi Kamide
  */
 public class Music {
        /**
@@ -13,28 +13,22 @@ public class Music {
         */
        public static final int SEMITONES_PER_OCTAVE = 12;
        /**
-        * MIDIノート番号と、
-        * メジャーキー基準の五度圏インデックス値との間の変換を行います。
+        * MIDIノート番号と、メジャーキー基準の五度圏インデックス値との間の変換を行います。
         *
         * <p>双方向の変換に対応しています。
-        * 内部的には、元の値が奇数のときに6(半オクターブ)を足し、
-        * 偶数のときにそのまま返しているだけです。
-        * 値は0~11であるとは限りません。その範囲に補正したい場合は
-        *  {@link #mod12(int)} を併用します。
+        * 内部的には、元の値が奇数のときに6(半オクターブ)を足し、偶数のときにそのまま返しているだけです。
+        * 値は0~11であるとは限りません。その範囲に補正したい場合は {@link #mod12(int)} を併用します。
         * </p>
         *
         * @param n 元の値
         * @return 変換結果
         */
-       public static int reverseCo5(int n) {
-               return (n & 1) == 0 ? n : n+6 ;
-       }
+       public static int toggleCo5(int n) { return (n & 1) == 0 ? n : n+6 ; }
        /**
         * ノート番号からオクターブ成分を抜きます。
-        * <p>n % 12 と似ていますが、Java の % 演算子では、
-        * 左辺に負数を与えると答えも負数になってしまうため、n % 12 で計算しても
-        * 0~11 の範囲を外れてしまうことがあります。そこで、
-        * 負数の場合に12を足すことにより 0~11 の範囲に入るよう補正します。
+        * <p>n % 12 と似ていますが、Java の % 演算子では、左辺に負数を与えると答えも負数になってしまうため、
+        * n % 12 で計算しても 0~11 の範囲を外れてしまうことがあります。
+        * そこで、負数の場合に12を足すことにより 0~11 の範囲に入るよう補正します。
         * </p>
         * @param n 元のノート番号
         * @return オクターブ成分を抜いたノート番号(0~11)
@@ -44,9 +38,7 @@ public class Music {
                return qn < 0 ? qn + 12 : qn ;
        }
        /**
-        * 指定されたMIDIノート番号の音の周波数を返します。
-        * チューニングは A=440Hz とします。
-        *
+        * 指定されたMIDIノート番号の音の周波数(A=440Hzチューニング時)を返します。
         * @param noteNumber MIDIノート番号
         * @return 音の周波数[Hz]
         */
@@ -54,8 +46,7 @@ public class Music {
                return 55 * Math.pow( 2, (double)(noteNumber - 33)/12 );
        }
        /**
-        * MIDIノート番号の示す音階が、
-        * 指定された調(五度圏インデックス値)におけるメジャースケールまたは
+        * MIDIノート番号の示す音階が、指定された調(五度圏インデックス値)におけるメジャースケールまたは
         * ナチュラルマイナースケールの構成音に該当するか調べます。
         *
         * <p>調の五度圏インデックス値に0(ハ長調またはイ短調)を指定すると、
@@ -67,7 +58,7 @@ public class Music {
         * @return スケール構成音のときtrue、スケールを外れている場合false
         */
        public static boolean isOnScale(int noteNumber, int keyCo5) {
-               return mod12(reverseCo5(noteNumber) - keyCo5 + 1) < 7 ;
+               return mod12(toggleCo5(noteNumber) - keyCo5 + 1) < 7 ;
        }
        /**
         * 五度圏インデックス値で表された音階を、
@@ -83,7 +74,7 @@ public class Music {
         */
        public static int transposeCo5(int co5, int chromaticOffset) {
                if( chromaticOffset == 0 ) return co5;
-               int transposedCo5 = mod12( co5 + reverseCo5(chromaticOffset) );
+               int transposedCo5 = mod12( co5 + toggleCo5(chromaticOffset) );
                if( transposedCo5 > 6 ) transposedCo5 -= Music.SEMITONES_PER_OCTAVE;
                return transposedCo5;
        }
@@ -92,8 +83,6 @@ public class Music {
         * @param co5 五度圏インデックス値
         * @return 真裏の五度圏インデックス値
         */
-       public static int oppositeCo5(int co5) {
-               return co5 > 0 ? co5 - 6 : co5 + 6;
-       }
+       public static int oppositeCo5(int co5) { return co5 > 0 ? co5 - 6 : co5 + 6; }
 }
 
index 4309153..a9d5e46 100644 (file)
@@ -243,7 +243,7 @@ public class NoteSymbol {
         * @return MIDIノート番号が示す音名
         */
        public static String noteNumberToSymbol(int noteNumber, int maxChars) {
-               int co5 = Music.mod12(Music.reverseCo5(noteNumber));
+               int co5 = Music.mod12(Music.toggleCo5(noteNumber));
                if( co5 == 11 ) co5 -= Music.SEMITONES_PER_OCTAVE; // E# -> F
                if( co5 < 6 ) return (new NoteSymbol(co5)).toString(); // F C G D A E B
 
@@ -276,6 +276,6 @@ public class NoteSymbol {
         * @return ノート番号(0~11)
         */
        public static int majorCo5ToNoteNumber(int majorCo5) {
-               return Music.mod12(Music.reverseCo5(majorCo5));
+               return Music.mod12(Music.toggleCo5(majorCo5));
        }
 }
\ No newline at end of file