まぁ(3)の直接的な続きではあるけれど。
1 import itertools
2 import wave
3 import numpy as np
4
5 try:
6 # for python 2.7
7 zip = itertools.izip
8 except AttributeError:
9 pass
10
11 class _WaveRWBase(object):
12 def __init__(self, waveobj):
13 self._wave = waveobj
14
15 def __enter__(self):
16 return self
17
18 def __exit__(self, type, value, tb):
19 try:
20 self._wave.close()
21 except:
22 pass
23
24 def __getattr__(self, attrname):
25 return getattr(self._wave, attrname)
26
27 class _WaveReader(_WaveRWBase):
28 def __init__(self, fname):
29 _WaveRWBase.__init__(self, wave.open(fname, "r"))
30
31 class _WaveWriter(_WaveRWBase):
32 def __init__(self, fname, nchannels, sampwidth, samprate):
33 _WaveRWBase.__init__(self, wave.open(fname, "w"))
34 self._wave.setparams(
35 (nchannels, sampwidth, samprate,
36 # For seekable output streams, the wave header will automatically
37 # be updated to reflect the number of frames actually written.
38 0,
39 'NONE', 'not compressed' # you can't change these
40 )
41 )
42
43 def _writepcmraw(self, pcm):
44 import struct
45
46 packed = struct.pack('h' * len(pcm), *pcm)
47 self._wave.writeframes(packed)
48
49 def writechannels(self, channels):
50 import struct
51 CHUNK_SIZE = 4096
52
53 _pcm = []
54 for d in itertools.chain.from_iterable(zip(*channels)):
55 _pcm.append(d)
56 if len(_pcm) == CHUNK_SIZE:
57 self._writepcmraw(_pcm)
58 _pcm = []
59 if _pcm:
60 self._writepcmraw(_pcm)
61
62
63 if __name__ == '__main__':
64 import os
65 import argparse
66 parser = argparse.ArgumentParser()
67 parser.add_argument(
68 "mode", choices=[
69 "copy", "reverse", "swaplr", "rdelay",
70 "left2mono", "right2mono", "negate", "sampx2"
71 ])
72 parser.add_argument("--modeintparam", type=int, default=0)
73 parser.add_argument("target")
74 args = parser.parse_args()
75
76 with _WaveReader(args.target) as fi:
77 nchannels, width, rate, nframes, _, _ = fi.getparams()
78 onchannels = nchannels
79 raw = np.fromstring(fi.readframes(nframes), dtype=np.int16)
80 if args.mode == "reverse":
81 channels = reversed(raw[::2]), reversed(raw[1::2])
82 elif args.mode == "swaplr":
83 # if nchannels is 1, actualy this swap odd and even.
84 channels = raw[1::2], raw[::2]
85 elif args.mode == "rdelay":
86 if not args.modeintparam:
87 delay = rate // 50
88 else:
89 delay = args.modeintparam
90 channels = [
91 np.hstack((raw[::2], np.zeros(delay, dtype=np.int16))),
92 np.hstack((np.zeros(delay, dtype=np.int16), raw[1::2]))]
93 elif nchannels == 1 and args.mode in ("left2mono", "right2mono"):
94 channels = (raw,)
95 elif args.mode == "negate":
96 channels = (-raw[::2], -raw[1::2])
97 elif args.mode == "left2mono":
98 channels = (raw[::2],)
99 onchannels = 1
100 elif args.mode == "right2mono":
101 channels = (raw[1::2], )
102 onchannels = 1
103 elif args.mode == "sampx2":
104 left, right = raw[::2], raw[1::2]
105 left = itertools.chain.from_iterable(zip(left, left))
106 right = itertools.chain.from_iterable(zip(right, right))
107 channels = left, right
108 else:
109 channels = raw[::2], raw[1::2]
110
111 ofname = os.path.splitext(args.target)[0] + "_" + args.mode + '.wav'
112 with _WaveWriter(ofname, onchannels, width, rate) as fo:
113 fo.writechannels(channels)
例によってスクリプトの機能は「欲しいから」とか「皆様のお役に立つに違いない」じゃなくて、「本当にやりたいことのための下ごしらえ」で増やしたもの。sampx2 モードは実際には「0.5倍速」に相当することが起こりまする。
(3)同様に「オレ的動機」の一部だけは説明しとく。izip と CHUNK_SIZE だけが意図…だけで伝わるかなぁ? 一般的な曲はだいたい長くて5分程度でしょ。そのくらいのサイズを処理するのに、「リスト化」とか「'h' * len(pcm)
」がベラぼうなコストになることがあるのね。sampx2 モードやその他「色々なごにょごにょ」をやり出すと、処理時間だけならともかく PC が限りなくハングアップ状態に近くなることも普通に起こっちゃうわけな。だから出来るだけリストの実体化を避けたい、というわけだ。
まぁここまでするなら、しかも元から numpy は使っている上にこのあと scipy もどうせ使うよな、ってノリなんで、scipy に含まれてる wave ファイルを扱うモジュールを使っちゃえばいいのに、みたいなことにはなってくるんだけれども。ただあたしが根本的にやりたいのは「wave ファイルの扱い」つーよりは PCM データそのもののデータ処理だったりするんで、別に wave ファイルの扱いがダルかろうがそうでなかろうが、あんまし関係ないんだよね。(どっちでもたかが知れておろう。)
あ、コードみればわかると思うんだけれど、(3)までは Python 3.x 前提だったけど、今回のは 2.7 でも動く。わかるよね? コンテクストマネージャが 2.7 版にはないの。
あと一応。正しい理解をしている人には自明と思うけれど、出力の sampwidth、samprate を書き換えると「知らなかった人には面白い」かもしれないんで、興味あったら遊んでみるといいよ。sampwidth を 2倍にするとどうなる? samprate を 2倍にするとどうなる?
ちなみに蛇足。「なんで 44100?」の理由を知ってちょっと感動した。最初の4つの素数の自乗の積(2**2 * 3**2 * 5**2 * 7**2
)、なんだね。だから何で割ってもあんまし端数が出ない。考えた人天才。
02:30 追記
一応これは言っておこうと思う。
ワタシの興味が「音声「ファイル」を扱う」ではないので、上の例も、今後の派生も「ハイレゾ」(24ビット)を扱う気はないです。パッキング時の「’h’」は 16ビットだから、だし、読み込み時の int16 も 16ビット前提だから。
それをしたい人は探せば日本語で見つかるよ。
じゃぁあたしの興味範囲はなんなのか? まぁ整理出来始めて来たら何か形にするかもしれんけれど、要するに「音声もその一味」という興味の持ち方…、てことで今のところは許して。しかるに「音声だけ特別に興味を持っているわけでない」てこと。画像、映像、音声、テキスト。これらは「所詮データでしょ」、と言う見方をしたいが、そのためには…、て学び方の最中なの。
ちなみに上の sampwidth を書き換えて遊ぶ、てのは、「真面目な製品品質のものを作るならダメ」よ。まさにその「ハイレゾ」あたりとセットで正しく理解してセットしないと「不当なもの」を作っちゃうことになる。ただ書き換えるとプレイヤーがどんな反応をするのかを知っとくのは理解の助けにはなると思うのでね。