OSDN Git Service

リファクタリング(Chordクラス周りを中心に)
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / music / Key.java
1 package camidion.chordhelper.music;
2
3
4 /**
5  * 調(キー)を表すクラスです。値は不変です。
6  *
7  * <p>内部的には次の値を持っています。</p>
8  * <ul>
9  * <li>五度圏インデックス値。これは調号の♯の数(♭の数は負数)と同じです。</li>
10  * <li>メジャー/マイナーの区別、区別なしの3値({@link MajorMinor}で定義)</li>
11  * </ul>
12  * <p>これらの値はMIDIのメタメッセージにある調号のパラメータに対応します。
13  * </p>
14  */
15 public class Key {
16         /**
17          * 調号の♯または♭の最大数
18          */
19         public static final int MAX_SHARPS_OR_FLATS = 7;
20         /**
21          * キー指定(メジャー/マイナー/両方)
22          */
23         public enum MajorMinor {
24                 /**
25                  * マイナーキー(短調)
26                  */
27                 MINOR(-1) {
28                         @Override
29                         public MajorMinor opposite() { return MAJOR; }
30                 },
31                 /**
32                  * メジャーまたはマイナー(区別なし)
33                  */
34                 MAJOR_OR_MINOR(0) {
35                         @Override
36                         public boolean includes(MajorMinor mm) { return true; }
37                 },
38                 /**
39                  * メジャーキー(長調)
40                  */
41                 MAJOR(1) {
42                         @Override
43                         public MajorMinor opposite() { return MINOR; }
44                 };
45                 private int index;
46                 private MajorMinor(int index) { this.index = index; }
47                 /** インデックス値(マイナー:-1、区別なし:0、メジャー:1)を返します。 */
48                 public int index() { return index; }
49                 /** 反対の調を返します。 */
50                 public MajorMinor opposite() { return this; }
51                 /** この値が、引数で指定されたメジャー/マイナーを含んだ意味であるときにtrueを返します。 */
52                 public boolean includes(MajorMinor mm) { return equals(mm); }
53         }
54         /**
55          * この調の五度圏インデックス値
56          */
57         private int co5;
58         /**
59          * メジャー・マイナーの区別
60          */
61         private MajorMinor majorMinor = MajorMinor.MAJOR_OR_MINOR;
62         /**
63          * 調号が空(C/Am ハ長調またはイ短調)で、メジャー・マイナーの区別のない調を構築します。
64          */
65         public Key() { }
66         /**
67          * 指定の五度圏インデックス値を持つ、メジャー・マイナーの区別のない調を構築します。
68          *
69          * @param co5 五度圏インデックス値
70          */
71         public Key(int co5) { this.co5 = co5; normalize(); }
72         /**
73          * 指定の五度圏インデックス値を持つ、メジャー・マイナーの区別を指定した調を構築します。
74          *
75          * @param co5 五度圏インデックス値
76          * @param majorMinor メジャー・マイナーの区別
77          */
78         public Key(int co5, MajorMinor majorMinor) {
79                 this.co5 = co5;
80                 this.majorMinor = majorMinor;
81                 normalize();
82         }
83         /**
84          * MIDIの調データ(メタメッセージ2byteの配列)から調を構築します。
85          * <ul>
86          * <li>長さ0の配列が与えられた場合は{@link #Key() 引数のないコンストラクタ}と同じ動作になります。</li>
87          * <li>メジャー・マイナーの区別を表す有効な値が見つからなかった場合、区別なしとして構築されます。</li>
88          * </ul>
89          *
90          * @param midiData MIDIの調データ
91          */
92         public Key(byte midiData[]) {
93                 if( midiData.length == 0 ) return;
94                 co5 = midiData[0];
95                 if( midiData.length > 1 ) {
96                         switch(midiData[1]) {
97                         case 0 : majorMinor = MajorMinor.MAJOR; break;
98                         case 1 : majorMinor = MajorMinor.MINOR; break;
99                         }
100                 }
101                 normalize();
102         }
103         /**
104          * C、Am のような文字列から調を構築します。
105          * mがあればマイナー、なければメジャーとなります(区別のない状態にはなりません)。
106          *
107          * @param keySymbol 調を表す文字列
108          * @throw IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
109          */
110         public Key(String keySymbol) throws IllegalArgumentException {
111                 co5 = NoteSymbol.co5OfSymbol(keySymbol);
112                 if( keySymbol.matches(".*m") ) { majorMinor = MajorMinor.MINOR; co5 -= 3; }
113                 else majorMinor = MajorMinor.MAJOR;
114                 normalize();
115         }
116         /**
117          * 指定されたコードと同名、または最も近い調を構築します。
118          * <ul>
119          * <li>コード構成音に短3度があればマイナー、なければメジャーとなります(区別なしになることはありません)。</li>
120          * <li>調として存在しないコード(例:A♯)が来た場合、存在する異名同音の調(例:B♭)に置き換えられます。</li>
121          * </ul>
122          * @param chord コード(和音)
123          */
124         public Key(Chord chord) {
125                 co5 = chord.rootNoteSymbol().toCo5();
126                 if( chord.isSet(Chord.Interval.MINOR) ) { majorMinor = MajorMinor.MINOR; co5 -= 3; }
127                 else majorMinor = MajorMinor.MAJOR;
128                 normalize();
129         }
130         /**
131          * この調を正規化します。
132          * 調が7♭~7♯の範囲に入っていない場合、
133          * その範囲に入るよう調整されます。
134          */
135         private void normalize() {
136                 if( co5 >= -MAX_SHARPS_OR_FLATS && co5 <= MAX_SHARPS_OR_FLATS ) return;
137                 if( (co5 = Music.mod12(co5)) > 6 ) co5 -= Music.SEMITONES_PER_OCTAVE;
138         }
139         /**
140          * 五度圏インデックス値を返します。
141          * @return 五度圏インデックス値
142          */
143         public int toCo5() { return co5; }
144         /**
145          * メジャー・マイナーの区別を返します。
146          * @return メジャー・マイナーの区別
147          */
148         public MajorMinor majorMinor() { return majorMinor; }
149         @Override
150         public boolean equals(Object anObject) {
151                 if( this == anObject ) return true;
152                 if( anObject instanceof Key ) {
153                         Key another = (Key) anObject;
154                         return co5 == another.toCo5() && majorMinor == another.majorMinor();
155                 }
156                 return false;
157         }
158         @Override
159         public int hashCode() { return 0x4000 * majorMinor.index() + co5 ; }
160         /**
161          * この調の文字列表現を C、Am のような形式で返します。
162          * メジャー・マイナーの区別がない場合、調号を先頭に付加して返します。
163          * @return この調の文字列表現
164          */
165         @Override
166         public String toString() {
167                 String s = toStringIn(NoteSymbol.Language.SYMBOL);
168                 if(majorMinor == MajorMinor.MAJOR_OR_MINOR) s = signature() + " : " + s;
169                 return s;
170         }
171         /**
172          * この調の文字列表現を、指定された形式で返します。
173          * @return この調の文字列表現
174          */
175         public String toStringIn(NoteSymbol.Language language) {
176                 return language.stringOf(this);
177         }
178         /**
179          * 調号を表す半角文字列を返します。
180          * 正規化された状態において最大2文字になるよう調整されます。
181          *
182          * @return 調号を表す半角文字列
183          */
184         public String signature() {
185                 switch(co5) {
186                 case  0: return "==";
187                 case  1: return "#";
188                 case -1: return "b";
189                 case  2: return "##";
190                 case -2: return "bb";
191                 default:
192                         if( co5 >= 3 && co5 <= 7 ) return co5 + "#" ;
193                         else if( co5 <= -3 && co5 >= -7 ) return (-co5) + "b" ;
194                         return "";
195                 }
196         }
197         /**
198          * 調号の説明(英語)を返します。
199          * @return 調号の説明
200          */
201         public String signatureDescription() {
202                 switch(co5) {
203                 case  0: return "no sharps or flats";
204                 case  1: return "1 sharp";
205                 case -1: return "1 flat";
206                 default: return co5 < 0 ? (-co5) + " flats" : co5 + " sharps" ;
207                 }
208         }
209         /**
210          * MIDIの調データ(メタメッセージ2byte)を生成して返します。
211          * @return  MIDIの調データ
212          */
213         public byte[] getBytes() {
214                 return new byte[] {(byte) co5, (byte) ((majorMinor == MajorMinor.MINOR) ? 1 : 0)};
215         }
216         /**
217          * 相対ドの音階を返します。
218          * @return 相対ドの音階(0~11)
219          */
220         public int relativeDo() { return NoteSymbol.majorCo5ToNoteNumber(co5); }
221         /**
222          * この調のルート音を表すノート番号(オクターブ抜き)を返します。
223          * メジャーキーの場合は相対ド、
224          * マイナーキーの場合は相対ラの音階です。
225          *
226          * @return キーのルート音(0~11)
227          */
228         public int rootNoteNumber() {
229                 int n = relativeDo();
230                 return majorMinor==MajorMinor.MINOR ? Music.mod12(n-3) : n;
231         }
232         /**
233          * 指定されたノート番号の音が、この調のスケールの構成音か調べます。
234          * メジャーキーの場合はメジャースケール、
235          * マイナーキーの場合はナチュラルマイナースケールとして判断されます。
236          *
237          * @param noteNumber ノート番号
238          * @return 指定されたノート番号がこのキーのスケールの構成音ならtrue
239          */
240         public boolean isOnScale(int noteNumber) { return Music.isOnScale(noteNumber, co5); }
241         /**
242          * この調に対する平行調を返します。
243          * これは元の調と同じ調号を持つ、メジャーとマイナーが異なる調です。
244          * メジャーとマイナーの区別が不明な場合、この調自身を返します。
245          * @return 平行調
246          */
247         public Key relativeKey() {
248                 MajorMinor mmo = majorMinor.opposite();
249                 return mmo.equals(majorMinor) ? this : new Key(co5, mmo);
250         }
251         /**
252          * この調に対する同主調を返します。
253          * これは元の調とルート音が同じで、メジャーとマイナーが異なる調です。
254          * メジャーとマイナーの区別が不明な場合、この調自身を返します。
255          *
256          * @return 同主調
257          */
258         public Key parallelKey() {
259                 MajorMinor mmo = majorMinor.opposite();
260                 return mmo.equals(majorMinor) ? this : new Key(co5 - 3 * majorMinor.index(), mmo);
261         }
262         /**
263          * この調に異名同音の調があれば、それを返します。
264          * <p>例えば、♭5個(D♭メジャー)の場合、異名同音の調は♯7個(C♯メジャー)となります。
265          * 異名同音の調が存在しない調(4♯~4♭)に対してこのメソッドを呼び出した場合、
266          * この調自身を返します。
267          * </p>
268          * @return 異名同音の調
269          */
270         public Key enharmonicKey() {
271                 int newCo5 = co5;
272                 if( newCo5 > 4 ) newCo5 -= 12; else if( newCo5 < -4 ) newCo5 += 12;
273                 return newCo5 == co5 ? this : new Key(newCo5, majorMinor);
274         }
275         /**
276          * 指定された半音オフセット値だけ移調した調を返します。
277          * 半音オフセット値が 0 の場合、この調自身を返します。
278          *
279          * @param chromaticOffset 半音オフセット値
280          * @return 移調した調
281          */
282         public Key transposedKey(int chromaticOffset) {
283                 return chromaticOffset == 0 ? this : new Key(Music.transposeCo5(co5, chromaticOffset), majorMinor);
284         }
285         /**
286          * 五度圏で真裏にあたる調を返します。
287          * @return 五度圏で真裏にあたる調
288          */
289         public Key createOppositeKey() {
290                 return new Key(Music.oppositeCo5(co5), majorMinor);
291         }
292 }