*/
public static class VersionInfo {
public static final String NAME = "MIDI Chord Helper";
- public static final String VERSION = "Ver.20161211.1";
+ public static final String VERSION = "Ver.20161218.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/";
addChordMatrixListener(new ChordMatrixListener(){
public void keySignatureChanged() {
Key capoKey = getKeySignatureCapo();
- keyboardPanel.keySelecter.setKey(capoKey);
+ keyboardPanel.keySelecter.setSelectedKey(capoKey);
keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
}
public void chordChanged() { chordOn(); }
keySelecter.keysigCombobox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- Key key = keySelecter.getKey();
+ Key key = keySelecter.getSelectedKey();
key.transpose( - chordMatrix.capoSelecter.getCapo() );
chordMatrix.setKeySignature(key);
}
import camidion.chordhelper.music.Key;
import camidion.chordhelper.music.Music;
import camidion.chordhelper.music.NoteSymbol;
-import camidion.chordhelper.music.NoteSymbolLanguage;
/**
* MIDI Chord Helper 用のコードボタンマトリクス
}
else {
tip += key.signatureDescription() + " " +
- key.toStringIn(NoteSymbolLanguage.IN_JAPANESE);
+ key.toStringIn(NoteSymbol.Language.IN_JAPANESE);
if( v == 0 ) {
setIcon(new ButtonIcon(ButtonIcon.NATURAL_ICON));
}
import javax.swing.JLabel;
import camidion.chordhelper.music.Key;
-import camidion.chordhelper.music.NoteSymbolLanguage;
+import camidion.chordhelper.music.NoteSymbol;
/**
* 調表示ラベル
}
setText( "key:" + key.toString() );
setToolTipText(
- "Key: " + key.toStringIn(NoteSymbolLanguage.NAME)
- + " " + key.toStringIn(NoteSymbolLanguage.IN_JAPANESE)
+ "Key: " + key.toStringIn(NoteSymbol.Language.NAME)
+ + " " + key.toStringIn(NoteSymbol.Language.IN_JAPANESE)
+ " (" + key.signatureDescription() + ")"
);
setEnabled(true);
import javax.swing.JPanel;
import camidion.chordhelper.music.Key;
-import camidion.chordhelper.music.NoteSymbolLanguage;
+import camidion.chordhelper.music.NoteSymbol;
/**
* 調性選択
Key key;
for( int i = -7 ; i <= 7 ; i++ ) {
str = (key = new Key(i)).toString();
- if( i != 0 ) {
- str = key.signature() + " : " + str ;
- }
+ if( i != 0 ) str = key.signature() + " : " + str ;
addItem(str);
}
setMaximumRowCount(15);
};
JCheckBox minorCheckbox = null;
- public KeySignatureSelecter() { this(true); }
- public KeySignatureSelecter(boolean useMinorCheckbox) {
+ public KeySignatureSelecter() { this(null); }
+ public KeySignatureSelecter(Key key) {
add(new JLabel("Key:"));
add(keysigCombobox);
- if(useMinorCheckbox) {
+ if(key != null && key.majorMinor() != Key.MajorMinor.MAJOR_OR_MINOR) {
add( minorCheckbox = new JCheckBox("minor") );
minorCheckbox.addActionListener(this);
}
keysigCombobox.addActionListener(this);
- clear();
+ setSelectedKey(key);
}
+
public void actionPerformed(ActionEvent e) { updateToolTipText(); }
private void updateToolTipText() {
- Key key = getKey();
+ Key key = getSelectedKey();
keysigCombobox.setToolTipText(
- "Key: " + key.toStringIn( NoteSymbolLanguage.NAME )
- + " " + key.toStringIn( NoteSymbolLanguage.IN_JAPANESE )
+ "Key: " + key.toStringIn( NoteSymbol.Language.NAME )
+ + " " + key.toStringIn( NoteSymbol.Language.IN_JAPANESE )
+ " (" + key.signatureDescription() + ")"
);
}
- public void clear() { setKey(new Key("C")); }
- public void setKey( Key key ) {
- if( key == null ) { clear(); return; }
- keysigCombobox.setSelectedIndex( key.toCo5() + 7 );
- if( minorCheckbox == null ) return;
- minorCheckbox.setSelected(key.majorMinor() == Key.MajorMinor.MINOR);
+
+ public void setSelectedKey(Key key) {
+ if( key == null ) key = new Key("C");
+ keysigCombobox.setSelectedIndex(key.toCo5() + 7);
+ setMajorMinor(key.majorMinor());
}
- public Key getKey() {
- Key.MajorMinor majorMinor = (
- minorCheckbox == null ? Key.MajorMinor.MAJOR_OR_MINOR :
- isMinor() ? Key.MajorMinor.MINOR :
- Key.MajorMinor.MAJOR
- );
- return new Key(getKeyCo5(),majorMinor);
+ public Key getSelectedKey() {
+ return new Key(keysigCombobox.getSelectedIndex() - 7, getMajorMinor());
}
- public int getKeyCo5() {
- return keysigCombobox.getSelectedIndex() - 7;
+
+ private void setMajorMinor(Key.MajorMinor majorMinor) {
+ if( minorCheckbox == null || majorMinor == Key.MajorMinor.MAJOR_OR_MINOR ) return;
+ minorCheckbox.setSelected(majorMinor == Key.MajorMinor.MINOR);
}
- public boolean isMinor() {
- return minorCheckbox != null && minorCheckbox.isSelected();
+ private Key.MajorMinor getMajorMinor() {
+ return minorCheckbox == null ? Key.MajorMinor.MAJOR_OR_MINOR :
+ minorCheckbox.isSelected() ? Key.MajorMinor.MINOR : Key.MajorMinor.MAJOR;
}
}
\ No newline at end of file
/**
* 調号選択
*/
- private KeySignatureSelecter keysigSelecter = new KeySignatureSelecter() {
+ private KeySignatureSelecter keysigSelecter = new KeySignatureSelecter(new Key("C")) {
{
keysigCombobox.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
- dataText.setValue(getKey().getBytes());
+ dataText.setValue(getSelectedKey().getBytes());
}
}
);
minorCheckbox.addItemListener(
new ItemListener() {
public void itemStateChanged(ItemEvent e) {
- dataText.setValue(getKey().getBytes());
+ dataText.setValue(getSelectedKey().getBytes());
}
}
);
switch( data1 ) { // Data type -> Selecter
case 0x51: dataText.setValue(tempoSelecter.getTempoByteArray()); break;
case 0x58: dataText.setValue(timesigSelecter.getByteArray()); break;
- case 0x59: dataText.setValue(keysigSelecter.getKey().getBytes()); break;
+ case 0x59: dataText.setValue(keysigSelecter.getSelectedKey().getBytes()); break;
default: break;
}
}
byte data[] = dataText.getBytes();
if( data == null ) return null;
try {
- msg.setMessage(
- (int)(msgStatus & 0xFF), data, data.length
- );
+ msg.setMessage((int)(msgStatus & 0xFF), data, data.length);
} catch( InvalidMidiDataException e ) {
e.printStackTrace();
return null;
return (MidiMessage)msg;
}
ShortMessage msg = new ShortMessage();
- int msg_data1 = data1Text.getValue();
- if( msg_data1 < 0 ) msg_data1 = 0;
- int msg_data2 = data2Text.getValue();
- if( msg_data2 < 0 ) msg_data2 = 0;
+ int msgData1 = data1Text.getValue();
+ if( msgData1 < 0 ) msgData1 = 0;
+ int msgData2 = data2Text.getValue();
+ if( msgData2 < 0 ) msgData2 = 0;
try {
- if( MIDISpec.isChannelMessage( msgStatus ) ) {
- msg.setMessage(
- (msgStatus & 0xF0),
- channelText.getSelectedChannel(),
- msg_data1, msg_data2
- );
+ if( MIDISpec.isChannelMessage(msgStatus) ) {
+ msg.setMessage((msgStatus & 0xF0), channelText.getSelectedChannel(), msgData1, msgData2);
}
else {
- msg.setMessage( msgStatus, msg_data1, msg_data2 );
+ msg.setMessage(msgStatus, msgData1, msgData2);
}
} catch( InvalidMidiDataException e ) {
e.printStackTrace();
switch(msgType) {
case 0x51: tempoSelecter.setTempo(data); break;
case 0x58: timesigSelecter.setValue(data[0], data[1]); break;
- case 0x59: keysigSelecter.setKey(new Key(data)); break;
+ case 0x59: keysigSelecter.setSelectedKey(new Key(data)); break;
default: break;
}
if( MIDISpec.hasMetaMessageText(msgType) ) {
* @return コードの説明(英語)
*/
public String toName() {
- String name = rootNoteSymbol.toStringIn(NoteSymbolLanguage.NAME) + nameSuffix() ;
+ String name = rootNoteSymbol.toStringIn(NoteSymbol.Language.NAME) + nameSuffix() ;
if( ! rootNoteSymbol.equals(bassNoteSymbol) ) {
- name += " on " + bassNoteSymbol.toStringIn(NoteSymbolLanguage.NAME);
+ name += " on " + bassNoteSymbol.toStringIn(NoteSymbol.Language.NAME);
}
return name;
}
// Major/minor 切り替え
public void toggleKeyMajorMinor() {
- key = key.relativeKey();
+ key = key.createRelativeKey();
}
// コード進行の移調
Object element = measure.get(i);
if( element instanceof ChordStroke ) {
ChordStroke cs = (ChordStroke)element;
- Chord new_chord = cs.chord.clone();
+ Chord newChord = cs.chord.clone();
//
// キーが未設定のときは、最初のコードから推測して設定
- if( key == null ) key = new Key( new_chord );
+ if( key == null ) key = new Key(newChord);
//
- new_chord.transpose( chromaticOffset, key );
- measure.set( i, new ChordStroke( new_chord, cs.beat_length ) );
+ newChord.transpose( chromaticOffset, key );
+ measure.set(i, new ChordStroke(newChord, cs.beat_length));
}
}
}
* <p>内部的には次の値を持っています。</p>
* <ul>
* <li>五度圏インデックス値。これは調号の♯の数(♭の数は負数)と同じです。</li>
- * <li>メジャー/マイナーの区別(無指定ありの3値)</li>
+ * <li>メジャー/マイナーの区別、区別なしの3値({@link MajorMinor}で定義)</li>
* </ul>
* <p>これらの値はMIDIのメタメッセージにある調号のパラメータに対応します。
* </p>
*/
public class Key implements Cloneable {
/**
- * ã\83¡ã\82¸ã\83£ã\83¼ã\81¨ã\83\9eã\82¤ã\83\8aã\83¼ã\81®ã\81©ã\81¡ã\82\89ã\81\8bã\81\82ã\82\8bã\81\84ã\81¯ä¸¡æ\96¹ã\81§ã\81\82ã\82\8bã\81\93ã\81¨ã\82\92示ã\81\99å\9e\8b
+ * ã\82ã\83¼æ\8c\87å®\9aï¼\88ã\83¡ã\82¸ã\83£ã\83¼ï¼\8fã\83\9eã\82¤ã\83\8aã\83¼ï¼\8f両æ\96¹ï¼\89
*/
public enum MajorMinor {
+ /**
+ * メジャーまたはマイナー(区別なし)
+ */
+ MAJOR_OR_MINOR {
+ @Override
+ public boolean includes(MajorMinor mm) { return true; }
+ },
/** メジャーキー(長調) */
MAJOR {
@Override
MINOR {
@Override
public MajorMinor opposite() { return MAJOR; }
- },
- /** メジャーまたはマイナー */
- MAJOR_OR_MINOR;
-
+ };
/** 反対の調を返します。 */
public MajorMinor opposite() { return this; }
+ /**
+ * この値が、引数で指定されたメジャー/マイナーを含んだ意味であるときにtrueを返します。
+ * */
+ public boolean includes(MajorMinor mm) { return equals(mm); }
}
/**
* この調の五度圏インデックス値
*/
private int co5;
/**
- * メジャー・マイナーの種別
+ * メジャー・マイナーの種別(null不可:コンストラクタでの初期化必須)
*/
private MajorMinor majorMinor;
/**
- * 調号が空のキー(ハ長調またはイ短調)を構築します。
- */
- public Key() { }
- /**
- * 指定の五度圏インデックス値を持つ調を、
- * メジャーとマイナーを指定せずに構築します。
- *
- * @param co5 五度圏インデックス値
+ * 調号が空(C/Am ハ長調またはイ短調)のキーを、メジャーとマイナーを指定せずに構築します。
*/
- public Key(int co5) { setKey(co5, MajorMinor.MAJOR_OR_MINOR); }
+ public Key() {
+ majorMinor = MajorMinor.MAJOR_OR_MINOR;
+ }
/**
- * 指定の五度圏インデックス値を持つ、
- * メジャー/マイナーを指定した調を構築します。
+ * 指定の五度圏インデックス値を持つ調を、メジャーとマイナーを指定せずに構築します。
*
* @param co5 五度圏インデックス値
- * @param majorMinor {@link #MAJOR}、{@link #MINOR}、{@link #MAJOR_OR_MINOR} のいずれか
*/
- public Key(int co5, MajorMinor majorMinor) { setKey(co5, majorMinor); }
+ public Key(int co5) {
+ this.co5 = co5;
+ majorMinor = MajorMinor.MAJOR_OR_MINOR;
+ normalize();
+ }
/**
- * 指定の五度圏インデックス値を持つ、
- * メジャー/マイナーの明確な調を構築します。
+ * 指定の五度圏インデックス値を持つ、メジャー/マイナーを指定した調を構築します。
*
* @param co5 五度圏インデックス値
- * @param isMinor true:マイナー、false:メジャー
+ * @param majorMinor メジャー/マイナー指定
*/
- public Key(int co5, boolean isMinor) { setKey(co5, isMinor); }
+ public Key(int co5, MajorMinor majorMinor) {
+ this.co5 = co5;
+ this.majorMinor = majorMinor;
+ normalize();
+ }
/**
* MIDIの調データ(メタメッセージ2byte)から調を構築します。
* @param midiData MIDIの調データ
* @throw IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
*/
public Key(String keySymbol) throws IllegalArgumentException {
- boolean isMinor = keySymbol.matches(".*m");
- setKey((new NoteSymbol(keySymbol)).toCo5ForKey(isMinor), isMinor);
+ co5 = NoteSymbol.co5OfSymbol(keySymbol);
+ if( keySymbol.matches(".*m") ) { majorMinor = MajorMinor.MINOR; co5 -= 3; }
+ else majorMinor = MajorMinor.MAJOR;
+ normalize();
}
/**
* 指定されたコードと同名の調を構築します。
* @param chord コード(和音)
*/
public Key(Chord chord) {
- boolean isMinor = chord.isSet(Chord.Interval.MINOR);
- setKey(chord.rootNoteSymbol().toCo5ForKey(isMinor), isMinor);
+ co5 = chord.rootNoteSymbol().toCo5();
+ if( chord.isSet(Chord.Interval.MINOR) ) { majorMinor = MajorMinor.MINOR; co5 -= 3; }
+ else majorMinor = MajorMinor.MAJOR;
+ normalize();
}
@Override
public Key clone() { return new Key(co5, majorMinor); }
if( this == anObject ) return true;
if( anObject instanceof Key ) {
Key another = (Key) anObject;
- return
- co5 == another.toCo5() &&
- majorMinor == another.majorMinor() ;
+ return co5 == another.toCo5() && majorMinor == another.majorMinor();
}
return false;
}
@Override
- public int hashCode() { return majorMinor.ordinal() * 256 + co5 ; }
- private void setKey(int co5, boolean isMinor) {
- setKey( co5, isMinor ? MajorMinor.MINOR : MajorMinor.MAJOR );
- }
- private void setKey(int co5, MajorMinor majorMinor) {
- this.co5 = co5;
- this.majorMinor = majorMinor;
- normalize();
- }
+ public int hashCode() { return majorMinor.ordinal() * 64 + co5 ; }
/**
- * MIDIの調データ(メタメッセージ2byte)を設定します。
+ * この調にMIDIの調データ(メタメッセージ2byte)を設定し、{@link #normalize()}で正規化します。
* @param data MIDIの調データ
*/
- public void setBytes( byte[] data ) {
- byte sharpFlat = data.length > 0 ? data[0] : 0;
- byte isMinor = data.length > 1 ? data[1] : 0;
- setKey( (int)sharpFlat, isMinor==1 );
+ public void setBytes(byte[] data) {
+ co5 = data.length > 0 ? (int)data[0] : 0;
+ majorMinor = (data.length > 1 && data[1] == 1) ? MajorMinor.MINOR : MajorMinor.MAJOR ;
+ normalize();
}
/**
* MIDIの調データ(メタメッセージ2byte)を生成して返します。
*/
public int relativeDo() { return NoteSymbol.majorCo5ToNoteNumber(co5); }
/**
- * この調のルート音を返します。
+ * ã\81\93ã\81®èª¿ã\81®ã\83«ã\83¼ã\83\88é\9f³ã\82\92表ã\81\99ã\83\8eã\83¼ã\83\88ç\95ªå\8f·ï¼\88ã\82ªã\82¯ã\82¿ã\83¼ã\83\96æ\8a\9cã\81\8dï¼\89ã\82\92è¿\94ã\81\97ã\81¾ã\81\99ã\80\82
* メジャーキーの場合は相対ド、
* マイナーキーの場合は相対ラの音階です。
*
*/
public void normalize() {
if( co5 < -7 || co5 > 7 ) {
- co5 = Music.mod12( co5 );
+ co5 = Music.mod12(co5);
if( co5 > 6 ) co5 -= Music.SEMITONES_PER_OCTAVE;
}
}
/**
* 平行調を生成して返します。
* これは元の調と同じ調号を持つ、メジャーとマイナーが異なる調です。
- *
- * <p>メジャーとマイナーの区別が不明な場合、クローンを生成したのと同じことになります。
- * </p>
- *
+ * メジャーとマイナーの区別が不明な場合、クローンを生成したのと同じことになります。
* @return 平行調
*/
- public Key relativeKey() { return new Key(co5, majorMinor.opposite()); }
+ public Key createRelativeKey() { return new Key(co5, majorMinor.opposite()); }
/**
* 同主調を生成して返します。
* これは元の調とルート音が同じで、メジャーとマイナーが異なる調です。
- *
- * <p>メジャーとマイナーの区別が不明な場合、クローンを生成したのと同じことになります。
- * 元の調の♭、♯の数が5個以上の場合、
- * 7♭~7♯の範囲をはみ出すことがあります(正規化は行われません)。
- * </p>
- *
+ * メジャーとマイナーの区別が不明な場合、クローンを生成したのと同じことになります。
* @return 同主調
*/
- public Key parallelKey() {
+ public Key createParallelKey() {
+ int newCo5 = co5;
switch( majorMinor ) {
- case MAJOR: return new Key( co5-3, MajorMinor.MINOR );
- case MINOR: return new Key( co5+3, MajorMinor.MAJOR );
- default: return new Key(co5);
+ case MAJOR: newCo5 -= 3; break;
+ case MINOR: newCo5 += 3; break;
+ default: break;
}
+ return new Key(newCo5, majorMinor.opposite());
}
/**
* 五度圏で真裏にあたる調を生成して返します。
* @return 五度圏で真裏にあたる調
*/
- public Key oppositeKey() { return new Key(Music.oppositeCo5(co5), majorMinor); }
+ public Key createOppositeKey() { return new Key(Music.oppositeCo5(co5), majorMinor); }
/**
* この調の文字列表現を C、Am のような形式で返します。
* @return この調の文字列表現
*/
@Override
- public String toString() { return toStringIn(NoteSymbolLanguage.SYMBOL); }
+ public String toString() { return toStringIn(NoteSymbol.Language.SYMBOL); }
/**
* この調の文字列表現を、指定された形式で返します。
* @return この調の文字列表現
*/
- public String toStringIn(NoteSymbolLanguage language) {
- return language.keyStringOf(new NoteSymbol(co5), majorMinor);
- }
+ public String toStringIn(NoteSymbol.Language language) { return language.stringOf(this); }
/**
* 調号を表す半角文字列を返します。
* 正規化された状態において最大2文字になるよう調整されます。
if( msgdata.length == 2 ) {
Key key = new Key(msgdata);
str += ": " + key.signatureDescription();
- str += " (" + key.toStringIn(NoteSymbolLanguage.NAME) + ")";
+ str += " (" + key.toStringIn(NoteSymbol.Language.NAME) + ")";
break;
}
str += ": Size not 2 byte : data=(";
package camidion.chordhelper.music;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
/**
* 音名(オクターブ抜き)を表すクラスです。値は不変です。
*
* </p>
*/
public class NoteSymbol implements Cloneable {
- private static final int INDEX_OF_A = NoteSymbolLanguage.SYMBOL.indexOf("A");
- private static final int INDEX_OF_C = NoteSymbolLanguage.SYMBOL.indexOf("C");
+ private static final int INDEX_OF_A = Language.SYMBOL.indexOf("A");
+ private static final int INDEX_OF_C = Language.SYMBOL.indexOf("C");
+ /**
+ * 音階や調を表すシンボルの言語モードによる違いを定義します。
+ * <p>音名には、下記のような五度圏順のインデックス値(0~34)が割り当てられます。
+ * <pre>Fbb=0, Cbb=1, .. Bb=13, F=14, C=15, .. B=20, F#=21, C#=22, .. B#=27, Fx=28, .. Bx=34</pre>
+ */
+ public static enum Language {
+ /**
+ * 記号表記(Bb, F#)
+ */
+ SYMBOL(Arrays.asList("bb","b","","#","x"),"","m"),
+ /**
+ * 英名表記(B flat, F sharp)
+ */
+ NAME(Arrays.asList(" double flat"," flat",""," sharp"," double sharp")," major"," minor"),
+ /**
+ * 日本名表記(変ロ, 嬰ヘ)
+ */
+ IN_JAPANESE(Arrays.asList("重変","変","","嬰","重嬰"),"長調","短調");
+ //
+ private Language(List<String> sharpFlatList, String major, String minor) {
+ if( (this.sharpFlatList = sharpFlatList).contains("変") ) {
+ this.notes = "ヘハトニイホロ";
+ this.majorMinorDelimiter = "/";
+ } else {
+ this.notes = "FCGDAEB";
+ this.majorMinorDelimiter = " / ";
+ }
+ this.major = major;
+ this.minor = minor;
+ }
+ /**
+ * ♭や♯の表記を、半音下がる数が多いほうから順に並べたリスト
+ */
+ private List<String> sharpFlatList;
+ /**
+ * 引数の先頭にある、♭や♯などの変化記号のインデックス(0~4)を返します。
+ * <p>変化記号がない場合、2 を返します。それ以外は次の値を返します。</p>
+ * <ul>
+ * <li>ダブルシャープ:4</li>
+ * <li>シャープ:3</li>
+ * <li>フラット:1</li>
+ * <li>ダブルフラット:0</li>
+ * </ul>
+ * @param s 変化記号で始まる文字列
+ * @return インデックス
+ */
+ private int sharpFlatIndexOf(String s) {
+ int index = 0;
+ for( String sharpFlat : sharpFlatList ) {
+ if( ! sharpFlat.isEmpty() && s.startsWith(sharpFlat) ) return index;
+ index++;
+ }
+ return 2;
+ }
+ /**
+ * 音名を五度圏順で並べた7文字
+ */
+ private String notes;
+ /**
+ * インデックス値に該当する音名を返します。
+ * @param index インデックス値(定義は{@link Language}参照)
+ * @return 音名(例:Bb、B flat、変ロ)
+ * @throws IndexOutOfBoundsException インデックス値が範囲を外れている場合
+ */
+ private String stringOf(int index) {
+ int sharpFlatIndex = index / 7;
+ char note = notes.charAt(index - sharpFlatIndex * 7);
+ String sharpFlat = sharpFlatList.get(sharpFlatIndex);
+ return this == IN_JAPANESE ? sharpFlat + note : note + sharpFlat;
+ }
+ /**
+ * 音名に対するインデックス値を返します。
+ * 音名は通常、英大文字(ABCDEFG)ですが、英小文字(abcdefg)も認識します。
+ * 日本語名(イロハニホヘト)はサポートしていません。
+ *
+ * @param noteSymbol 音名で始まる文字列
+ * @return インデックス値(定義は{@link Language}参照)
+ * @throws UnsupportedOperationException このオブジェクトが {@link #IN_JAPANESE} の場合
+ * @throws NullPointerException 引数がnullの場合
+ * @throws IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
+ */
+ private int indexOf(String noteSymbol) {
+ if( this == IN_JAPANESE ) throw new UnsupportedOperationException();
+ Objects.requireNonNull(noteSymbol,"Musical note symbol must not be null");
+ String trimmed = noteSymbol.trim();
+ if( trimmed.isEmpty() ) throw new IllegalArgumentException("Empty musical note symbol");
+ int noteIndex = notes.indexOf(Character.toUpperCase(trimmed.charAt(0)));
+ if( noteIndex < 0 ) throw new IllegalArgumentException(
+ "Unknown musical note symbol ["+noteSymbol+"] not in ["+notes+"]");
+ return 7 * sharpFlatIndexOf(trimmed.substring(1)) + noteIndex;
+ }
+ /**
+ * メジャーを表す文字列
+ */
+ private String major;
+ /**
+ * マイナーを表す文字列
+ */
+ private String minor;
+ /**
+ * メジャーとマイナーを併記する場合の区切り文字
+ */
+ private String majorMinorDelimiter;
+ /**
+ * 調の文字列表現を返します。メジャー/マイナーの区別がない場合、両方の表現を返します。
+ * @param majorCo5 調の五度圏の値(0 == C/Am)
+ * @param majorMinor メジャー/マイナーの区別
+ * @return 調の文字列表現
+ */
+ public String stringOf(Key key) {
+ String s = "";
+ int co5 = key.toCo5();
+ Key.MajorMinor majorMinor = key.majorMinor();
+ if( majorMinor.includes(Key.MajorMinor.MAJOR) ) {
+ s = stringOf(co5 + INDEX_OF_C) + major;
+ if( ! majorMinor.includes(Key.MajorMinor.MINOR) ) return s;
+ s += majorMinorDelimiter;
+ }
+ return s + stringOf(co5 + INDEX_OF_A) + minor;
+ }
+ }
/** メジャーキー基準の五度圏インデックス値 */
private int majorCo5;
/** ノート番号(0~11) */
* @param noteSymbol 音名の文字列
* @throws IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
*/
- public NoteSymbol(String noteSymbol) {
- this(NoteSymbolLanguage.SYMBOL.indexOf(noteSymbol) - INDEX_OF_C);
- }
+ public NoteSymbol(String noteSymbol) { this(co5OfSymbol(noteSymbol)); }
@Override
protected NoteSymbol clone() { return new NoteSymbol(majorCo5); }
/**
*/
public int toCo5() { return majorCo5; }
/**
- * メジャー/マイナーキーに対応する五度圏インデックス値を返します。
- * <p>マイナーの場合、メジャー基準の五度圏インデックス値から3が差し引かれます。
- * 例えば、C major の場合は調号が0個なのに対し、C minor のときは調号の♭が3個に増えますが、
- * 3を差し引くことによってこのズレが補正されます。
- * </p>
- *
- * @param isMinor マイナーのときtrue
- * @return 五度圏インデックス値
- */
- public int toCo5ForKey(boolean isMinor) { return isMinor ? majorCo5 - 3 : majorCo5; }
- /**
* ノート番号(0~11)を返します。
* <p>これはMIDIノート番号からオクターブ情報を抜いた値と同じです。
* 五度圏インデックス値をノート番号に変換した場合、
* @return この音階の文字列表現
*/
@Override
- public String toString() { return toStringIn(NoteSymbolLanguage.SYMBOL); }
+ public String toString() { return toStringIn(Language.SYMBOL); }
/**
- * 指定した言語モードにおける文字列表現を返します。
+ * この音階の文字列表現を、引数で指定された言語モードで返します。
* @param language 言語モード
* @return 文字列表現
*/
- public String toStringIn(NoteSymbolLanguage language) {
+ public String toStringIn(Language language) {
return language.stringOf(majorCo5 + INDEX_OF_C);
}
/**
- * 指定した言語モードにおける、マイナーキー用の文字列表現を返します。
- * マイナーキー用の文字列では、例えば五度圏インデックスが0の場合、Cの代わりにAを返します。
- * @param language 言語モード
- * @return 文字列表現
+ * 引数で指定された音名のメジャーキー基準の五度圏インデックスを返します。
+ * @param noteSymbol 音名の文字列
+ * @return メジャーキー基準の五度圏インデックス
+ * @throws IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
*/
- public String toMinorKeyRootStringIn(NoteSymbolLanguage language) {
- return language.stringOf(majorCo5 + INDEX_OF_A);
+ public static int co5OfSymbol(String noteSymbol) {
+ return Language.SYMBOL.indexOf(noteSymbol) - INDEX_OF_C;
}
/**
* 指定の最大文字数の範囲で、MIDIノート番号が示す音名を返します。
+++ /dev/null
-package camidion.chordhelper.music;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * 音階や調を表すシンボルの言語モードによる違いを定義します。
- * <p>音名には、下記のような五度圏順のインデックス値(0~34)が割り当てられます。
- * <pre>Fbb=0, Cbb=1, .. Bb=13, F=14, C=15, .. B=20, F#=21, C#=22, .. B#=27, Fx=28, .. Bx=34</pre>
- */
-public enum NoteSymbolLanguage {
- /**
- * 記号表記(Bb, F#)
- */
- SYMBOL(Arrays.asList("bb","b","","#","x"),"","m"),
- /**
- * 英名表記(B flat, F sharp)
- */
- NAME(Arrays.asList(" double flat"," flat",""," sharp"," double sharp")," major"," minor"),
- /**
- * 日本名表記(変ロ, 嬰ヘ)
- */
- IN_JAPANESE(Arrays.asList("重変","変","","嬰","重嬰"),"長調","短調");
- //
- private NoteSymbolLanguage(List<String> sharpFlatList, String major, String minor) {
- if( (this.sharpFlatList = sharpFlatList).contains("変") ) {
- this.notes = "ヘハトニイホロ";
- this.majorMinorDelimiter = "/";
- } else {
- this.notes = "FCGDAEB";
- this.majorMinorDelimiter = " / ";
- }
- this.major = major;
- this.minor = minor;
- }
- /**
- * ♭や♯の表記を、半音下がる数が多いほうから順に並べたリスト
- */
- private List<String> sharpFlatList;
- /**
- * 引数の先頭にある、♭や♯などの変化記号のインデックス(0~4)を返します。
- * <p>変化記号がない場合、2 を返します。それ以外は次の値を返します。</p>
- * <ul>
- * <li>ダブルシャープ:4</li>
- * <li>シャープ:3</li>
- * <li>フラット:1</li>
- * <li>ダブルフラット:0</li>
- * </ul>
- * @param s 変化記号で始まる文字列
- * @return インデックス
- */
- private int sharpFlatIndexOf(String s) {
- int index = 0;
- for( String sharpFlat : sharpFlatList ) {
- if( ! sharpFlat.isEmpty() && s.startsWith(sharpFlat) ) return index;
- index++;
- }
- return 2;
- }
- /**
- * 音名を五度圏順で並べた7文字
- */
- private String notes;
- /**
- * インデックス値に該当する音名を返します。
- * @param index インデックス値(定義は{@link NoteSymbolLanguage}参照)
- * @return 音名(例:Bb、B flat、変ロ)
- * @throws IndexOutOfBoundsException インデックス値が範囲を外れている場合
- */
- public String stringOf(int index) {
- int sharpFlatIndex = index / 7;
- char note = notes.charAt(index - sharpFlatIndex * 7);
- String sharpFlat = sharpFlatList.get(sharpFlatIndex);
- return this == IN_JAPANESE ? sharpFlat + note : note + sharpFlat;
- }
- /**
- * 音名に対するインデックス値を返します。
- * 音名は通常、英大文字(ABCDEFG)ですが、英小文字(abcdefg)も認識します。
- * 日本語名(イロハニホヘト)はサポートしていません。
- *
- * @param noteSymbol 音名で始まる文字列
- * @return インデックス値(定義は{@link NoteSymbolLanguage}参照)
- * @throws UnsupportedOperationException このオブジェクトが {@link #IN_JAPANESE} の場合
- * @throws NullPointerException 引数がnullの場合
- * @throws IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
- */
- public int indexOf(String noteSymbol) {
- if( this == IN_JAPANESE ) throw new UnsupportedOperationException();
- Objects.requireNonNull(noteSymbol,"Musical note symbol must not be null");
- String trimmed = noteSymbol.trim();
- if( trimmed.isEmpty() ) throw new IllegalArgumentException("Empty musical note symbol");
- int noteIndex = notes.indexOf(Character.toUpperCase(trimmed.charAt(0)));
- if( noteIndex < 0 ) throw new IllegalArgumentException(
- "Unknown musical note symbol ["+noteSymbol+"] not in ["+notes+"]");
- return 7 * sharpFlatIndexOf(trimmed.substring(1)) + noteIndex;
- }
- /**
- * メジャーを表す文字列
- */
- private String major;
- /**
- * マイナーを表す文字列
- */
- private String minor;
- /**
- * メジャーとマイナーを併記する場合の区切り文字
- */
- private String majorMinorDelimiter;
- /**
- * 調の文字列表現を返します。メジャー/マイナーの区別が不明な場合、両方の表現を返します。
- * @param note 音名
- * @param majorMinor 負数:マイナー 0:不明 正数:メジャー
- * @return 調の文字列表現
- */
- public String keyStringOf(NoteSymbol note, Key.MajorMinor majorMinor) {
- String s = "";
- if( majorMinor == Key.MajorMinor.MAJOR || majorMinor == Key.MajorMinor.MAJOR_OR_MINOR ) {
- s = note.toStringIn(this) + major;
- if( majorMinor == Key.MajorMinor.MAJOR ) return s;
- s += majorMinorDelimiter;
- }
- return s + note.toMinorKeyRootStringIn(this) + minor;
- }
-}
{
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add( Box.createHorizontalStrut(5) );
- add(velocitySelecter = new VelocitySelecter(
- keyboardCenterPanel.keyboard.velocityModel)
- );
- add(keySelecter = new KeySignatureSelecter(false));
+ add(velocitySelecter = new VelocitySelecter(keyboardCenterPanel.keyboard.velocityModel));
+ add(keySelecter = new KeySignatureSelecter());
add( keyboardCenterPanel.keyboard.chordDisplay );
add( Box.createHorizontalStrut(5) );
}