OSDN Git Service

・コードダイアグラム周りの改善
[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>五度圏インデックス。この値は調号の♯の数(♭の数は負数)と同じで、
10  * MIDIメタメッセージの調号パラメータと互換性があります。</li>
11  * <li>メジャー/マイナーの区別、区別なしの3値({@link MajorMinor}で定義)</li>
12  * </ul>
13  */
14 public class Key {
15         /**
16          * 調号の♯または♭の最大数
17          */
18         public static final int MAX_SHARPS_OR_FLATS = 7;
19         /**
20          * 調号が空(C/Am ハ長調またはイ短調)で、メジャー・マイナーの区別のない調
21          */
22         public static final Key C_MAJOR_OR_A_MINOR = new Key();
23         private Key() { }
24         /**
25          * キー指定(メジャー/マイナー/両方)
26          */
27         public enum MajorMinor {
28                 /**
29                  * マイナーキー(短調)
30                  */
31                 MINOR(-1) {
32                         @Override
33                         public MajorMinor opposite() { return MAJOR; }
34                 },
35                 /**
36                  * メジャーまたはマイナー(区別なし)
37                  */
38                 MAJOR_OR_MINOR(0) {
39                         @Override
40                         public boolean includes(MajorMinor mm) { return true; }
41                 },
42                 /**
43                  * メジャーキー(長調)
44                  */
45                 MAJOR(1) {
46                         @Override
47                         public MajorMinor opposite() { return MINOR; }
48                 };
49                 private int index;
50                 private MajorMinor(int index) { this.index = index; }
51                 /** インデックス値(マイナー:-1、区別なし:0、メジャー:1)を返します。 */
52                 public int index() { return index; }
53                 /** 反対の調を返します。 */
54                 public MajorMinor opposite() { return this; }
55                 /** この値が、引数で指定されたメジャー/マイナーを含んだ意味であるときにtrueを返します。 */
56                 public boolean includes(MajorMinor mm) { return equals(mm); }
57         }
58         /**
59          * この調の五度圏インデックス
60          */
61         private int co5;
62         /**
63          * メジャー・マイナーの区別
64          */
65         private MajorMinor majorMinor = MajorMinor.MAJOR_OR_MINOR;
66         /**
67          * 指定の五度圏インデックスを持つ、メジャー・マイナーの区別のない調を構築します。
68          *
69          * @param co5 五度圏インデックス
70          */
71         public Key(int co5) { this.co5 = co5; normalize(); }
72         /**
73          * 指定の五度圏インデックスを持つ、メジャー・マイナーの区別を指定した調を構築します。
74          *
75          * @param co5 五度圏インデックス(Index based Circle Of 5th)
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 = Note.co5Of(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          * この調を正規化します。調が7♭~7♯の範囲に入っていない場合、その範囲に入るよう調整されます。
132          */
133         private void normalize() {
134                 if( co5 >= -MAX_SHARPS_OR_FLATS && co5 <= MAX_SHARPS_OR_FLATS ) return;
135                 if( (co5 = Note.mod12(co5)) > 6 ) co5 -= Note.SEMITONES_PER_OCTAVE;
136         }
137         /**
138          * 五度圏インデックスを返します。
139          * @return 五度圏インデックス
140          */
141         public int toCo5() { return co5; }
142         /**
143          * メジャー・マイナーの区別を返します。
144          * @return メジャー・マイナーの区別
145          */
146         public MajorMinor majorMinor() { return majorMinor; }
147         @Override
148         public boolean equals(Object anObject) {
149                 if( this == anObject ) return true;
150                 if( anObject instanceof Key ) {
151                         Key another = (Key) anObject;
152                         return co5 == another.toCo5() && majorMinor == another.majorMinor();
153                 }
154                 return false;
155         }
156         @Override
157         public int hashCode() { return 0x4000 * majorMinor.index() + co5 ; }
158         /**
159          * この調の文字列表現を C、Am のような形式で返します。
160          * メジャー・マイナーの区別がない場合、調号を先頭に付加して返します。
161          * @return この調の文字列表現
162          */
163         @Override
164         public String toString() {
165                 String s = toStringIn(Note.Language.SYMBOL);
166                 if( majorMinor == MajorMinor.MAJOR_OR_MINOR ) s = signature() + " : " + s;
167                 return s;
168         }
169         /**
170          * この調の文字列表現を、指定された言語モードで返します。
171          * @param language 言語モード
172          * @return この調の文字列表現
173          */
174         public String toStringIn(Note.Language language) {
175                 return language.stringOf(this);
176         }
177         /**
178          * 調号を表す半角文字列を返します。
179          * 正規化された状態において最大2文字になるよう調整されます。
180          *
181          * @return 調号を表す半角文字列
182          */
183         public String signature() {
184                 switch(co5) {
185                 case  0: return "==";
186                 case  1: return "#";
187                 case -1: return "b";
188                 case  2: return "##";
189                 case -2: return "bb";
190                 default:
191                         if( co5 >= 3 && co5 <= 7 ) return co5 + "#" ;
192                         else if( co5 <= -3 && co5 >= -7 ) return (-co5) + "b" ;
193                         return "";
194                 }
195         }
196         /**
197          * 調号の説明(英語)を返します。
198          * @return 調号の説明
199          */
200         public String signatureDescription() {
201                 switch(co5) {
202                 case  0: return "no sharps or flats";
203                 case  1: return "1 sharp";
204                 case -1: return "1 flat";
205                 default: return co5 < 0 ? (-co5) + " flats" : co5 + " sharps" ;
206                 }
207         }
208         /**
209          * MIDIの調データ(メタメッセージ2byte)を生成して返します。
210          * @return  MIDIの調データ
211          */
212         public byte[] getBytes() {
213                 return new byte[] {(byte) co5, (byte) ((majorMinor == MajorMinor.MINOR) ? 1 : 0)};
214         }
215         /**
216          * この調の相対ドの音階を返します。
217          * @return 相対ドの音階(0~11)
218          */
219         public int relativeDo() { return Note.mod12(Note.toggleCo5(co5)); }
220         /**
221          * この調のルート音を表すノート番号(オクターブ抜き)を返します。
222          * メジャーキーの場合は相対ド、マイナーキーの場合は相対ラの音階です。
223          *
224          * @return キーのルート音(0~11)
225          */
226         public int rootNoteNumber() {
227                 return majorMinor==MajorMinor.MINOR ? Note.mod12(Note.toggleCo5(co5) - 3) : relativeDo();
228         }
229         /**
230          * 指定されたMIDIノート番号の示す音階が、この調のメジャースケールまたは
231          * ナチュラルマイナースケールの構成音に該当するか調べます。
232          *
233          * キーがハ長調またはイ短調の場合、白鍵のときにtrue、黒鍵のときにfalseを返します。
234          *
235          * @param noteNumber ノート番号
236          * @return 指定されたノート番号がこのキーのスケールの構成音ならtrue
237          */
238         public boolean isOnScale(int noteNumber) {
239                 return Note.mod12(Note.toggleCo5(noteNumber) - co5 + 1) < 7 ;
240         }
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(Note.transposeCo5(co5, chromaticOffset), majorMinor);
284         }
285         /**
286          * 五度圏で真裏にあたる調を返します。
287          * @return 五度圏で真裏にあたる調
288          */
289         public Key createOppositeKey() {
290                 return new Key(Note.oppositeCo5(co5), majorMinor);
291         }
292 }