オレ式「音階と周波数の対応表」(一応)

既に人さまの優秀なものがあって、別に不満があるということではなくて。

ちょいと今個人的に不可思議なことに出くわしてしまっていて、どうしても色んな種類の検証方法が必要なんだけれど、そのうちの一つとして、やはりシンプルな対応表をいつでも手にしたい、ということ。

FFT なスペクトラム可視化に音階をくっつけて(3)」で、「「log2 スケール的なこと」を、実はやるだけやってみた。」と言うだけ言って書き残さなかったもんで、自分でももう一度書くハメになったてのもあり、ただの表とその「「log2 スケール的なこと」グラフの両方をば:

  1 import numpy as np
  2 
  3 
  4 #
  5 # see details: https://en.wikipedia.org/wiki/MIDI_tuning_standard
  6 #
  7 _SCALES_L = ["C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B"]
  8 
  9 def _nn2scale(d):
 10     return int(d / 12) - 2, _SCALES_L[int(d) % 12]
 11 
 12 def _nn2freq(d):
 13     return np.power(2, (d - 69) / 12.) * 440
 14 
 15 def _freq2nn(f):
 16     return 69 + 12 * np.log2(f / 440.)
 17 
 18 #############################################################
 19 #
 20 # matplotlib's custom scale implementation for MTS scales.
 21 #
 22 from numpy import ma
 23 from matplotlib import scale as mscale
 24 from matplotlib import transforms as mtransforms
 25 import matplotlib.ticker as ticker
 26 
 27 
 28 class MTSScaleScale(mscale.ScaleBase):
 29     name = 'midituningstandardscale'
 30 
 31     def __init__(self, axis, **kwargs):
 32         mscale.ScaleBase.__init__(self)
 33 
 34     def get_transform(self):
 35         return self.MTSScaleTransform()
 36 
 37     def set_default_locators_and_formatters(self, axis):
 38         class _nnformat(ticker.Formatter):
 39             def __init__(self, minor):
 40                 ticker.Formatter.__init__(self)
 41                 if minor:
 42                     self._fmt = lambda oc, sc: sc
 43                 else:
 44                     self._fmt = lambda oc, sc: "%s%d    -" % (sc, oc)
 45         
 46             def __call__(self, f, pos=None):
 47                 if f > 0:
 48                     nn = _freq2nn(f)
 49                     if nn - int(nn) < 1e-2:
 50                         oc, sc = _nn2scale(nn)
 51                         return self._fmt(oc, sc)
 52                 return ""
 53 
 54         _all = np.arange(-24, 168)
 55         _maj = np.array([v for v in _all if v % 12 == 0])
 56         _min = np.array([v for v in _all if v % 12 in (2, 4, 5, 7, 9, 11)])
 57         axis.set_major_locator(ticker.FixedLocator(_nn2freq(_maj)))
 58         axis.set_minor_locator(ticker.FixedLocator(_nn2freq(_min)))
 59         axis.set_major_formatter(ticker.FuncFormatter(_nnformat(False)))
 60         axis.set_minor_formatter(ticker.FuncFormatter(_nnformat(True)))
 61 
 62     def limit_range_for_scale(self, vmin, vmax, minpos):
 63         return max(_nn2freq(-2), vmin), max(_nn2freq(-2), vmax)
 64 
 65     class MTSScaleTransform(mtransforms.Transform):
 66         input_dims = 1
 67         output_dims = 1
 68         is_separable = True
 69 
 70         def __init__(self):
 71             mtransforms.Transform.__init__(self)
 72 
 73         def transform_non_affine(self, f):
 74             masked = ma.masked_where((f == 0), f)
 75             if masked.mask.any():
 76                 return 69 + 12 * ma.log2(f / 440.)
 77             else:
 78                 return 69 + 12 * np.log2(f / 440.)
 79 
 80         def inverted(self):
 81             return MTSScaleScale.InvertedMTSScaleTransform()
 82 
 83     class InvertedMTSScaleTransform(mtransforms.Transform):
 84         input_dims = 1
 85         output_dims = 1
 86         is_separable = True
 87 
 88         def __init__(self):
 89             mtransforms.Transform.__init__(self)
 90 
 91         def transform_non_affine(self, d):
 92             return _nn2freq(d)
 93 
 94         def inverted(self):
 95             return MTSScaleScale.MTSScaleTransform()
 96 
 97 mscale.register_scale(MTSScaleScale)
 98 #############################################################
 99 
100 if __name__ == '__main__':
101     # to display as text
102     print("<table>")
103     print("<thead><tr><th>Note Number</th><th>Scale + Octave</th><th>Freq. (in Hz)</th></td></thead>")
104     for i in range(0, 132, 12):
105         for d in range(i, i + 12):
106             print("<tr><td>{:3d}</td><td>{:5s}{:-2d}</td><td>{:8.2f}</td></tr>".format(
107                     d, _SCALES_L[d % 12], d // 12 - 2, _nn2freq(d)))
108     print("</table>")
109 
110     # graphical display
111     import matplotlib.pyplot as plt
112     f = np.arange(44100)
113 
114     fig, ax = plt.subplots()
115     fig.set_size_inches(11.69, 16.53)
116     ax.plot(f, f, '-', lw=2)
117     ax.set_yscale('midituningstandardscale')
118     ax.grid(True)
119 
120     ax.set_xlabel("Hz")
121     ax.set_ylabel("MIDI Tuning Standard Scales")
122     #plt.show()
123     fig.tight_layout()
124     fig.savefig("MTS_Scales.jpg")

表のほうはこんな具合:

Note Number Scale + Octave Freq. (in Hz)
0 C -2 8.18
1 C#/Db-2 8.66
2 D -2 9.18
3 D#/Eb-2 9.72
4 E -2 10.30
5 F -2 10.91
6 F#/Gb-2 11.56
7 G -2 12.25
8 G#/Ab-2 12.98
9 A -2 13.75
10 A#/Bb-2 14.57
11 B -2 15.43
12 C -1 16.35
13 C#/Db-1 17.32
14 D -1 18.35
15 D#/Eb-1 19.45
16 E -1 20.60
17 F -1 21.83
18 F#/Gb-1 23.12
19 G -1 24.50
20 G#/Ab-1 25.96
21 A -1 27.50
22 A#/Bb-1 29.14
23 B -1 30.87
24 C 0 32.70
25 C#/Db 0 34.65
26 D 0 36.71
27 D#/Eb 0 38.89
28 E 0 41.20
29 F 0 43.65
30 F#/Gb 0 46.25
31 G 0 49.00
32 G#/Ab 0 51.91
33 A 0 55.00
34 A#/Bb 0 58.27
35 B 0 61.74
36 C 1 65.41
37 C#/Db 1 69.30
38 D 1 73.42
39 D#/Eb 1 77.78
40 E 1 82.41
41 F 1 87.31
42 F#/Gb 1 92.50
43 G 1 98.00
44 G#/Ab 1 103.83
45 A 1 110.00
46 A#/Bb 1 116.54
47 B 1 123.47
48 C 2 130.81
49 C#/Db 2 138.59
50 D 2 146.83
51 D#/Eb 2 155.56
52 E 2 164.81
53 F 2 174.61
54 F#/Gb 2 185.00
55 G 2 196.00
56 G#/Ab 2 207.65
57 A 2 220.00
58 A#/Bb 2 233.08
59 B 2 246.94
60 C 3 261.63
61 C#/Db 3 277.18
62 D 3 293.66
63 D#/Eb 3 311.13
64 E 3 329.63
65 F 3 349.23
66 F#/Gb 3 369.99
67 G 3 392.00
68 G#/Ab 3 415.30
69 A 3 440.00
70 A#/Bb 3 466.16
71 B 3 493.88
72 C 4 523.25
73 C#/Db 4 554.37
74 D 4 587.33
75 D#/Eb 4 622.25
76 E 4 659.26
77 F 4 698.46
78 F#/Gb 4 739.99
79 G 4 783.99
80 G#/Ab 4 830.61
81 A 4 880.00
82 A#/Bb 4 932.33
83 B 4 987.77
84 C 5 1046.50
85 C#/Db 5 1108.73
86 D 5 1174.66
87 D#/Eb 5 1244.51
88 E 5 1318.51
89 F 5 1396.91
90 F#/Gb 5 1479.98
91 G 5 1567.98
92 G#/Ab 5 1661.22
93 A 5 1760.00
94 A#/Bb 5 1864.66
95 B 5 1975.53
96 C 6 2093.00
97 C#/Db 6 2217.46
98 D 6 2349.32
99 D#/Eb 6 2489.02
100 E 6 2637.02
101 F 6 2793.83
102 F#/Gb 6 2959.96
103 G 6 3135.96
104 G#/Ab 6 3322.44
105 A 6 3520.00
106 A#/Bb 6 3729.31
107 B 6 3951.07
108 C 7 4186.01
109 C#/Db 7 4434.92
110 D 7 4698.64
111 D#/Eb 7 4978.03
112 E 7 5274.04
113 F 7 5587.65
114 F#/Gb 7 5919.91
115 G 7 6271.93
116 G#/Ab 7 6644.88
117 A 7 7040.00
118 A#/Bb 7 7458.62
119 B 7 7902.13
120 C 8 8372.02
121 C#/Db 8 8869.84
122 D 8 9397.27
123 D#/Eb 8 9956.06
124 E 8 10548.08
125 F 8 11175.30
126 F#/Gb 8 11839.82
127 G 8 12543.85
128 G#/Ab 8 13289.75
129 A 8 14080.00
130 A#/Bb 8 14917.24
131 B 8 15804.27

グラフはこんな:

この「対数グラフ(と近しいもの)」が「どう使いやすく、どう使いにくいのか」の感覚的なものについては、この online Spectrum Analyzer を動かしてみればわかるかと。(Logarithmic Frequency Scale? のチェックを付け外してみるといい。)低周波ほど「見やすくなる」一方で、それってのはつまり「低周波だけが妙に拡大されてみえてしまう」ことも意味するのであって、これがわかりやすく感じるかどうかはひとえに「慣れ次第」ってとこだろう。

なお、あげたスクリプトは整理すればそこそこな汎用モジュール目指せる。けどアタシはやんない。本題じゃないし。