Pythonのなみのり!

ハイドロポンプよりなみのりの方が使い勝手が良くて好きだった者です。
最近のものは知りませんが、初代ではそうでした。
その後、黄色いネズミがなみのっちゃったりしてましたが、軽やかにスルーしました。
想像してご覧なさい。あの黄色いネズミとなみのりで海を渡る光景を。
いずれ必ず感電死しますよ。
てゆうか。
水上でのバトルで思いっきりかみなりとか使ってますから、リアルで考えたらそれ以上に凄惨な光景が待っていること確実なんですよね。
フィクションってのはそういうもんです。


さてさて、ノスタルジックな冒頭からでんこうせっかで切り替えて、話題を本題へと書き写します。
今回はタイトルにもあるように、PythonでWAVファイルを扱う、特に読み込みに関する記事になります。
最初に断っておきますが、この記事はwaveモジュールの扱いに関するものではありませんのでご注意を、とのことです。
じゃあ、何を書くかって言ったら、僕がPythonの練習用に書いたプログラムを載せるんですよ。
そこには、メモとしての意味合いはもちろん、運が良ければ先輩プログラマの皆さんから、「こんなこともできるよ」なーんて教えて貰えたらいいなあなんて、打算的な思惑もあります。
ただ一つの懸念は、そうした方がこんなページを見るかという点に尽きますが。
と言うか、最大の問題ですね。
そもそも、見てる人がいるのかという疑問が頭の上をぶんぶん飛び回っていますが、なに、殺虫剤でもまいとけば一発です。
虫けら同然の僕も死ぬかもしれませんが。


じゃあ、まあ今回はプログラム自体が比較的今までのに比べて長いので、早速本題に移ろうかと思います。
毎度ながらさっさと本題から入れよとか思うかもしれませんが、前座は、「こんなひどい記事を書く人間がこれからバカなことをするんですよ」って警告の機能を担っているので重要なのです。
冒頭を見てページを変えれば、それ以降の時間の浪費を防げますからね。
それは、僕からの心ばかりの贈り物なのです。
冒頭読んだ分の時間はどうするかって?
そうですねえ……じゃあとりあえず、「こんな奴でも毎日頑張って生きてんだなあ。良し!俺も明日から頑張ろう!」とか思っとけば少しは足しになる気がしないこともないです。
昔の人は良い事言いました。『急がば回れ』。
ただ、『回』ってる最中にも進んでいることが前提なのが、この言葉のミソだとは思いますが。
あ、始めるとか言って始めてないじゃん。

設計モドキ:

今回は実用的なものを作りたいというよりは、一通りテキストを読み終えた、不肖準人類の僕が、「じゃあ何か題材でも作って練習しよっか」とか思って書いたものです。
まあ、ここを何回か訪れている方には言うまでも無いんですが、例のごとく読めたコードではありません
で、前置きはともかくとして、どういう設計、ここではどういうデータ列を作りたかったかというと、

wav{[<key-value>]:[[<header-value>],[data-value]]}

てな感じで、辞書型で管理するというものです。ここで、

  • key-value :辞書型のキーワード。デフォルトはファイル名。
  • header-value:WAVファイルのヘッダーにおける数字の部分。10進で格納。
  • data-value :データの部分。10進で格納。

を表します。
で。
このデータ型に対して、色々なメソッドを付け足して便利にできたらなあなんて夢物語をリアルに描いたんですが……作者の表現力が表現力なので、その辺は察するに余りある出来栄えな訳です。
当初は色々メソッドも考えていたのですが、読み込みができた時点で満足してしまい、今回載せるのは読み込みonlyです。
勢いで読み込みができたなんて書きましたが、ほんとに読み込めてるのかはご想像にお任せいたします。


ええと、次からはもうソースコード貼り付けて、結論に入っていくんですが、その前に。
ここでは、WAVファイルのフォーマットを調べた上で書いています。従って、訳分からん数字とか文字列とか出てくるかもしれませんが、大抵それはフォーマットに準じるために使っています。
一番訳分からんのは、僕自身の存在かもしれませんが、そこはそっとしておくのが優しさというものです。

ソースコード

自身でももっと改良してからとか思いましたが、とりあえず恥を晒しておくのも悪くないと思って、掲載に踏みきりました。
まあ、それで迷惑するのは、こんなコードで目を汚してしまう僕以外の皆さんなわけですが。
てなわけで、ソースコードです。

#!/usr/bin/env python
#coding: utf-8

import struct

class OrgWav(object):
    """
    OrgWave class.
     methods:
      load()
      header()
    """

    __slots__=('wav','key_list','raw_wav') # Access control for attributes.

    """------------------------------------------------------------
    Private methods in this class.
    ------------------------------------------------------------"""
    def __init__(self):
        self.raw_wav={}
        self.wav={}
        self.key_list=[]

    #**: Use in load() method. Separate header and data.
    def __separate_data(self,key):
        data_start=self.raw_wav[key].find("data")+8 # "+8" from WAV format.
        # raw_wav[<key>] = [[<header part>],[<data part>]]
        self.raw_wav[key]=[self.raw_wav[key][:data_start],self.raw_wav[key][data_start:]]

    #**: Use in __make_wav_dic() method.
    def __convert_header(self,key):
        for chunk_name in ("RIFF","fmt","data"):
            value=0 # converted value's box.
            seek_point = self.raw_wav[key][0].find(chunk_name) # string index search.
            #->: "fmt" case.
            if chunk_name == "fmt":
                seek_point+=4 # Skip "fmt "'s 4byte.
                for seek_delta in (4,2,2,4,4,2,2):# Tuple's value is size[byte].
                    if seek_delta == 4:
                        value = struct.unpack('<I',self.raw_wav[key][0][seek_point:seek_point+seek_delta])[0]
                    else:
                        value = struct.unpack('<H',self.raw_wav[key][0][seek_point:seek_point+seek_delta])[0]
                    seek_point+=seek_delta # <seek_delta> is loading size[byte]
                    self.wav[key][0].append(value) # append <value> for wav{}
            #<-:"fmt" case.
            else: # not "fmt" cases.
                value = struct.unpack('<I',self.raw_wav[key][0][seek_point+4:seek_point+8])[0]
                self.wav[key][0].append(value) # append <value> for wav{}

    #**: Use in __make_wav_dic() method.
    def __convert_data(self,key):
        flag="<h"
        seek_delta=2
        seek_point=0
        if self.wav[key][0][7] == 8: # 8 [bit/sample] case.
            flag="<H"
            seek_delta=1
        while seek_point < len(self.raw_wav[key][1]):
            self.wav[key][1].append(struct.unpack(flag,self.raw_wav[key][1][seek_point:seek_point+seek_delta])[0])
            seek_point+=seek_delta

    #**: Use in load() method.
    def __make_wav_dic(self,key):
        self.wav[key]=[[],[]] # Make new <wav> dictionary.
        self.__separate_data(key) # Separate header and data.
        self.__convert_header(key) # Convert binary to digit at header.
        self.__convert_data(key) # # Convert binary to digit at data.
        self.raw_wav={} # <raw_wav> clear.

    """++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Public methods in this class.
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"""
    def load(self,fname,key=""):
        """
        load() is method in OrgWav.
        usage: load(<fname>,<options>)
        """

        if key == "": # If <key> isn't defined, set a <fname>.
            key = fname
        self.key_list.append(key) # Append <key> for keys list.
        #->: File read & write start.
        fp=open(fname,"rb")
        self.raw_wav[key]=fp.read()
        fp.close()
        #<-: File access finish.
        self.__make_wav_dic(key)

    def header(self,key):
        return self.wav[key][0]

    def data(self,key):
        return self.wav[key][1]

    # __call__() called when new style class's object called as method.
    def __call__(self):
        print "What happen?"

ow = OrgWav()
ow.load("sample.wav")
print ow.header("sample.wav")
print ow.data("sample.wav")[0:100]

通例ですが、英語のスペルミス、誤用に関しては大目に見ていただくとして。
ソースは、こんな感じです。
こってりというか、もうドロドロしてます。
で、まあsample.wavってゆうWAVファイルをロードするようにしてるんですが、これの実行例は、

[42218532, 16, 1, 2, 44100, 176400, 4, 16, 42218496]
[166, 129, 150, 136, 147, 145, 143, 148, 123, 134, 91, 105, 48, 66, -10, 4, -79, -75, -164, -163, -259, -255, -347, -345, -438, -440, -523, -527, -597, -600, -654, -664, -695, -702, -707, -711, -691, -691, -655, -643, -596, -572, -514, -474, -411, -355, -292, -233, -157, -93, -22, 48, 100, 175, 215, 297, 322, 398, 409, 472, 461, 518, 481, 532, 475, 520, 445, 482, 389, 422, 313, 343, 226, 250, 136, 158, 46, 73, -32, 1, -99, -67, -146, -118, -159, -140, -151, -143, -113, -116, -63, -68, -5, -8, 68, 64, 158, 141, 245, 220]

って具合です。最初のリストがヘッダの数字部分、次がデータの一部になります。


ちなみに。
最後のprint文で表示する範囲区切ってますが、こうしないとディスプレイが酷いことになります。
当たり前です。
この例では、サンプリングレート44100Hzのファイルを扱っていますから、1秒のデータを表すのに44100点のデータを使っているということになります。
ちなみに、題材曲の再生時間は3分ちょっと。
………………ね。
どういうことになるのか気になって仕方がないという方でも、試さないことをお勧めします
うっかり、全部書き出させてしまった時の「しまった!」感と言ったらありません。
また、読み込みも非常に遅いです。この辺はスレッドを使ったらいい気がしますが、それはまたその内。いずれ、ということで。

結論:

今回はひとまず動くものを書きたいと思ったので、こんな感じであがりにしました。これから、徐々に改良を加えていきたいなと考えていますが、それもいつになることやら。


とにかく、struct.unpack()が見つかって良かった。

2010/12/17 追記:

遅いのは、__convert_data()でした。
ファイルの読み込み自体は瞬間で終わります。失礼しました。
改めて確認したところ、該当メソッドのunpack()とappend()が鈍足の原因のようです。
データが大きいですからねえ、アルゴリズムも精査されたそれではありませんし。
で。
まあ、諸々勉強足らずな部分があったということです。