1 package camidion.chordhelper.music;
5 * 調(キー)を表すクラスです。値は不変です。
7 * <p>内部的には次の値を持っています。</p>
9 * <li>五度圏インデックス。この値は調号の♯の数(♭の数は負数)と同じで、
10 * MIDIメタメッセージの調号パラメータと互換性があります。</li>
11 * <li>メジャー/マイナーの区別、区別なしの3値({@link MajorMinor}で定義)</li>
18 public static final int MAX_SHARPS_OR_FLATS = 7;
20 * 調号が空(C/Am ハ長調またはイ短調)で、メジャー・マイナーの区別のない調
22 public static final Key C_MAJOR_OR_A_MINOR = new Key();
27 public enum MajorMinor {
33 public MajorMinor opposite() { return MAJOR; }
40 public boolean includes(MajorMinor mm) { return true; }
47 public MajorMinor opposite() { return MINOR; }
50 private MajorMinor(int index) { this.index = index; }
51 /** インデックス値(マイナー:-1、区別なし:0、メジャー:1)を返します。 */
52 public int index() { return index; }
54 public MajorMinor opposite() { return this; }
55 /** この値が、引数で指定されたメジャー/マイナーを含んだ意味であるときにtrueを返します。 */
56 public boolean includes(MajorMinor mm) { return equals(mm); }
65 private MajorMinor majorMinor = MajorMinor.MAJOR_OR_MINOR;
67 * 指定の五度圏インデックスを持つ、メジャー・マイナーの区別のない調を構築します。
69 * @param co5 五度圏インデックス
71 public Key(int co5) { this.co5 = co5; normalize(); }
73 * 指定の五度圏インデックスを持つ、メジャー・マイナーの区別を指定した調を構築します。
75 * @param co5 五度圏インデックス(Index based Circle Of 5th)
76 * @param majorMinor メジャー・マイナーの区別
78 public Key(int co5, MajorMinor majorMinor) {
80 this.majorMinor = majorMinor;
84 * MIDIの調データ(メタメッセージ2byteの配列)から調を構築します。
86 * <li>長さ0の配列が与えられた場合は{@link #Key() 引数のないコンストラクタ}と同じ動作になります。</li>
87 * <li>メジャー・マイナーの区別を表す有効な値が見つからなかった場合、区別なしとして構築されます。</li>
90 * @param midiData MIDIの調データ
92 public Key(byte midiData[]) {
93 if( midiData.length == 0 ) return;
95 if( midiData.length > 1 ) {
97 case 0 : majorMinor = MajorMinor.MAJOR; break;
98 case 1 : majorMinor = MajorMinor.MINOR; break;
104 * C、Am のような文字列から調を構築します。
105 * mがあればマイナー、なければメジャーとなります(区別のない状態にはなりません)。
107 * @param keySymbol 調を表す文字列
108 * @throw IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
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;
117 * 指定されたコードと同名、または最も近い調を構築します。
119 * <li>コード構成音に短3度があればマイナー、なければメジャーとなります(区別なしになることはありません)。</li>
120 * <li>調として存在しないコード(例:A♯)が来た場合、存在する異名同音の調(例:B♭)に置き換えられます。</li>
122 * @param chord コード(和音)
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;
131 * この調を正規化します。調が7♭~7♯の範囲に入っていない場合、その範囲に入るよう調整されます。
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;
141 public int toCo5() { return co5; }
144 * @return メジャー・マイナーの区別
146 public MajorMinor majorMinor() { return majorMinor; }
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();
157 public int hashCode() { return 0x4000 * majorMinor.index() + co5 ; }
159 * この調の文字列表現を C、Am のような形式で返します。
160 * メジャー・マイナーの区別がない場合、調号を先頭に付加して返します。
164 public String toString() {
165 String s = toStringIn(Note.Language.SYMBOL);
166 if( majorMinor == MajorMinor.MAJOR_OR_MINOR ) s = signature() + " : " + s;
170 * この調の文字列表現を、指定された言語モードで返します。
171 * @param language 言語モード
174 public String toStringIn(Note.Language language) {
175 return language.stringOf(this);
179 * 正規化された状態において最大2文字になるよう調整されます。
183 public String signature() {
189 case -2: return "bb";
191 if( co5 >= 3 && co5 <= 7 ) return co5 + "#" ;
192 else if( co5 <= -3 && co5 >= -7 ) return (-co5) + "b" ;
200 public String signatureDescription() {
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" ;
209 * MIDIの調データ(メタメッセージ2byte)を生成して返します。
212 public byte[] getBytes() {
213 return new byte[] {(byte) co5, (byte) ((majorMinor == MajorMinor.MINOR) ? 1 : 0)};
217 * @return 相対ドの音階(0~11)
219 public int relativeDo() { return Note.mod12(Note.toggleCo5(co5)); }
221 * この調のルート音を表すノート番号(オクターブ抜き)を返します。
222 * メジャーキーの場合は相対ド、マイナーキーの場合は相対ラの音階です。
224 * @return キーのルート音(0~11)
226 public int rootNoteNumber() {
227 return majorMinor==MajorMinor.MINOR ? Note.mod12(Note.toggleCo5(co5) - 3) : relativeDo();
230 * 指定されたMIDIノート番号の示す音階が、この調のメジャースケールまたは
231 * ナチュラルマイナースケールの構成音に該当するか調べます。
233 * キーがハ長調またはイ短調の場合、白鍵のときにtrue、黒鍵のときにfalseを返します。
235 * @param noteNumber ノート番号
236 * @return 指定されたノート番号がこのキーのスケールの構成音ならtrue
238 public boolean isOnScale(int noteNumber) {
239 return Note.mod12(Note.toggleCo5(noteNumber) - co5 + 1) < 7 ;
243 * これは元の調と同じ調号を持つ、メジャーとマイナーが異なる調です。
244 * メジャーとマイナーの区別が不明な場合、この調自身を返します。
247 public Key relativeKey() {
248 MajorMinor mmo = majorMinor.opposite();
249 return mmo.equals(majorMinor) ? this : new Key(co5, mmo);
253 * これは元の調とルート音が同じで、メジャーとマイナーが異なる調です。
254 * メジャーとマイナーの区別が不明な場合、この調自身を返します。
258 public Key parallelKey() {
259 MajorMinor mmo = majorMinor.opposite();
260 return mmo.equals(majorMinor) ? this : new Key(co5 - 3 * majorMinor.index(), mmo);
263 * この調に異名同音の調があれば、それを返します。
264 * <p>例えば、♭5個(D♭メジャー)の場合、異名同音の調は♯7個(C♯メジャー)となります。
265 * 異名同音の調が存在しない調(4♯~4♭)に対してこのメソッドを呼び出した場合、
270 public Key enharmonicKey() {
272 if( newCo5 > 4 ) newCo5 -= 12; else if( newCo5 < -4 ) newCo5 += 12;
273 return newCo5 == co5 ? this : new Key(newCo5, majorMinor);
276 * 指定された半音オフセット値だけ移調した調を返します。
277 * 半音オフセット値が 0 の場合、この調自身を返します。
279 * @param chromaticOffset 半音オフセット値
282 public Key transposedKey(int chromaticOffset) {
283 return chromaticOffset == 0 ? this : new Key(Note.transposeCo5(co5, chromaticOffset), majorMinor);
287 * @return 五度圏で真裏にあたる調
289 public Key createOppositeKey() {
290 return new Key(Note.oppositeCo5(co5), majorMinor);