import java.util.*; import java.awt.*; import javax.swing.*; public class MusicTheory { public static final int SCREEN_LINE = 255; public static void main(String[] args) throws Exception { JFrame frame = new JFrame("MusicTheory"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Vector vec = new Vector(); int i; for( i = -7; i <= 7; i++ ) vec.add(i); vec.add(SCREEN_LINE); JList list = new JList(vec); list.setCellRenderer(new TickRenderer()); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.setFont( list.getFont().deriveFont((float)20) ); Container cp = frame.getContentPane(); // cp.add( new JLabel("log2(1) : log2(1.25) : log2(1.5)") ); cp.add( list, BorderLayout.CENTER ); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } } class TickRenderer extends JLabel implements ListCellRenderer { public static final int MAX_OCTAVES = 4; public Component getListCellRendererComponent( JList list, // the list Object value, // value to display int index, // cell index boolean isSelected, // is the cell selected boolean cellHasFocus) // does the cell have focus { String s = value.toString(); setText(s); if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } setEnabled(list.isEnabled()); setFont(list.getFont()); setOpaque(true); return this; } public void paint(Graphics g) { int v = getValue(); super.paint(g); paintTicks(g); if( v == MusicTheory.SCREEN_LINE ) { for(v = 7; v >= 1; v--) { paintValue(g,-v,true); paintValue(g,v,true); } paintValue(g,0,true); return; } paintValue(g,v,false); } private void paintTicks(Graphics g) { Dimension d = getSize(); int octave, max_octave=MAX_OCTAVES, semitone; double x2; double dpo = (double)d.width / (double)max_octave; for( octave = -1; octave < max_octave; octave++ ) { // オクターブ位置 x2 = (double)octave; // n * (log2(2) - 0) // オクターブ目盛の描画 g.setColor(Color.red); g.fillRect( (int)Math.round( x2 * dpo ), d.height-1, 1, -d.height ); // 半音目盛(12分の1オクターブ)を描画 g.setColor(Color.gray.brighter()); for( semitone=1; semitone<12; semitone++ ) { g.fillRect( (int)Math.round( ( x2 + (double)semitone/12 ) * dpo ), 0, 1, d.height/2 ); } } } private void paintValue(Graphics g, int value, boolean overlap) { Dimension d = getSize(); int octave, max_octave=MAX_OCTAVES, x, bar_width, bar_height; double x2, x3, x3_offset, x5_offset; double dpo = (double)d.width / (double)max_octave; Color default_color; if( Math.abs(value) <= 1 ) { bar_width = 9; bar_height = d.height; default_color = Color.pink; } else { // default_color = getForeground(); bar_width = 3; bar_height = d.height/2; default_color = Color.black; } for( octave = -1; octave < max_octave; octave++ ) { // オクターブ位置 x2 = (double)octave; // n * (log2(2) - 0) // 3倍、5倍について、2を底とする対数から小数部を取り出し、 // 基準との距離を算出 x3_offset = (Math.log(3) / Math.log(2)) - 1; // log2(3) - 1 == log2(1.5) x5_offset = (Math.log(5) / Math.log(2)) - 2; // log2(5) - 2 == log2(1.25) // 範囲内に入るようオクターブ補正(転回) x3 = x2 + value * x3_offset; while( x3 < 0 ) x3 += max_octave; while( x3 >= max_octave ) x3 -= max_octave; // 和音模様の描画 g.setColor( default_color ); g.fillRect( (int)Math.round( x3 * dpo ) - bar_width/2, 0, bar_width, bar_height ); g.fillRect( (int)Math.round( (x3 + x3_offset) * dpo ) - bar_width/2, 0, bar_width, bar_height ); g.fillRect( (int)Math.round( (x3 + x5_offset) * dpo ) - bar_width/2, 0, bar_width, bar_height ); // 和音の根音を示す赤枠、そこから上に飛び出すジョイント、 // 倍率の値を描く(ただし重ねるときは描かない) if( ! overlap ) { g.setFont( g.getFont().deriveFont((float)14) ); g.setColor( Color.red ); // x = (int)Math.round( x3 * dpo ) - bar_width/2; g.drawRect( x, 0, bar_width-1, bar_height-1 ); if( value == 0 ) { g.drawString( String.valueOf( Math.round( Math.pow( 2, x3 ) * 50.0 ) / 100.0 ), x, d.height-1 ); } // x = (int)Math.round( (x3 + x3_offset) * dpo ); g.fillRect( x, bar_height, 1, -bar_height/2 ); if( value == 0 ) { g.setColor( getForeground() ); g.drawString( String.valueOf( Math.round( Math.pow( 2, x3 + x3_offset ) * 50.0 ) / 100.0 ), x, d.height-1 ); g.setColor( Color.red ); } // x = (int)Math.round( (x3 + x5_offset) * dpo ); if( value == 0 ) { g.setColor( getForeground() ); g.drawString( String.valueOf( Math.round( Math.pow( 2, x3 + x5_offset ) * 50.0 ) / 100.0 ), x, d.height-1 ); } } } } public int getValue() { return new Integer(Integer.parseInt(getText())); } }