どうせオレ的メモならも少し「ええもん」のほうがいい。
(4)は一定周期ごとに「まっさらに」してやり直しちゃってからに、「だせぇ」わけでしょ。
ので:
1 import numpy as np
2 import matplotlib.pyplot as plt
3 import matplotlib.ticker as ticker
4 from tiny_wave_wrapper import WaveReader, WaveWriter
5
6 #
7 _SCALES_L = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "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 class _nnformat(object):
19 def __init__(self, minor):
20 if minor:
21 self._fmt = lambda oc, sc: sc
22 else:
23 self._fmt = lambda oc, sc: "%s (%d)" % (sc, oc)
24
25 def __call__(self, f, pos):
26 if f > 0:
27 nn = int(_freq2nn(f))
28 oc, sc = _nn2scale(nn)
29 return self._fmt(oc, sc)
30 return ""
31
32 def _setup_locator(ax):
33 _all = np.arange(24, 144)
34 _maj = np.array([v for v in _all if v % 12 == 0])
35 _min = np.array([v for v in _all if v % 12 in (2, 4, 5, 7, 9, 11)])
36 ax.yaxis.set_major_locator(ticker.FixedLocator(_nn2freq(_maj)))
37 ax.yaxis.set_minor_locator(ticker.FixedLocator(_nn2freq(_min)))
38
39 def _saveasgraph(F, fnum, args):
40 freq = np.fft.fftfreq(F[0].shape[1], 1./rate)
41 X = np.arange(F[0].shape[0])
42
43 fig, ax_ = plt.subplots()
44 for chn in range(2):
45 ax = plt.subplot(1, 2, chn + 1)
46
47 Y = freq[:len(freq) // 2]
48 Z = F[chn].T[:len(freq) // 2,:]
49 if args.upper_limit_of_view:
50 ind = (Y <= args.upper_limit_of_view)
51 Y, Z = Y[ind], Z[ind, :]
52 if args.lower_limit_of_view:
53 ind = (Y >= args.lower_limit_of_view)
54 Y, Z = Y[ind], Z[ind, :]
55
56 ax.contour(X, Y, Z, cmap='jet')
57 ax.yaxis.set_major_formatter(ticker.FuncFormatter(_nnformat(False)))
58 ax.yaxis.set_minor_formatter(ticker.FuncFormatter(_nnformat(True)))
59 ax.set_xticks([])
60 _setup_locator(ax)
61 ax.grid(True)
62
63 fig.tight_layout()
64 fig.savefig("out_%05d.jpg" % (fnum))
65 plt.close(fig)
66
67
68 if __name__ == '__main__':
69 import argparse
70 parser = argparse.ArgumentParser()
71 parser.add_argument("-s", "--step", type=int)
72 parser.add_argument("-u", "--upper_limit_of_view", help="Hz", type=int)
73 parser.add_argument("-l", "--lower_limit_of_view", help="Hz", type=int)
74 parser.add_argument("target")
75 args = parser.parse_args()
76
77 with WaveReader(args.target) as fi:
78 nchannels, width, rate, nframes, _, _ = fi.getparams()
79 raw = np.fromstring(fi.readframes(nframes), dtype=np.int16)
80 channels = raw[::2], raw[1::2]
81
82 if args.step:
83 step = args.step
84 else:
85 step = rate // 8
86 nframes = len(channels[0])
87 nframes -= (nframes % step) # drop the fraction frames
88 tmp = np.fft.fft(channels[0][:step]) # FIXME: to be more smart.
89 F = np.array([
90 np.zeros((rate // step * 10, len(tmp))),
91 np.zeros((rate // step * 10, len(tmp)))
92 ])
93 p = 0
94 for i in range(0, nframes, step):
95 for chn in range(len(channels)):
96 channel = channels[chn]
97 f = np.abs(np.fft.fft(channel[i:i + step]))
98 f = f / f.max()
99 if p > F[chn].shape[0] - 1:
100 F[chn] = np.roll(F[chn], shift=-1, axis=0)
101 F[chn][min(p, F[0].shape[0] - 1)] = f
102 _saveasgraph(F, i // step, args)
103 p += 1
roll して「まわし」てる。
以下は前回みたいな「恣意的な人工音声」じゃなく「ちゃんとした曲」への適用:
こうなると「ほんとに正しいの?」ってなってくるね。辛うじて曲が始まってすぐの部分が、確かに音と連動してるのがはっきりわかる、てくらい。(4)がはっきり「これは正しいっしょ」とわかるわけだから、今回のも「きっと合ってるんであろうなぁ」て感じ。
ちなみに、上のプログラム改造よりかライセンス問題のないもん探す方が何十倍も時間かかっちまった。
使った曲について:
http://www.freemusicpublicdomain.com/
“What a Beautiful Sunset – radio mix!”
Exzel Music Publishing (freemusicpublicdomain.com)
Licensed under Creative Commons: By Attribution 3.0http://creativecommons.org/licenses/by/3.0/
それはそうと、(4)で「今の場合「step = rate // 8」、つまりフレームレート 8 の動画相当の静止画が出来る。のでこんなで動画に」としてる作り方、間違ってるかも。時間が合わず、
1 me@host: ~$ ffmpeg -framerate 4 -i out_%05d.jpg -c:v libx264 out2.mp4
としないといけなかった。どこかが、何かが間違ってる…、なんだろ??