音を1サンプル毎に扱う音響合成環境をPythonでつくる(2)

はじめにはっきりさせておかないといけないことがあった。

前のポストで触れておくべきだったかも。
まあいいね。

Python自体の性質がそうなので仕方がないのだけども、なにかとモジュールべったりなコードが多いですよね? 例えば今回みたいな内容なら必ずといっていいほどnumpyが出てくる。
仕方がないのです。わかります。
でも、抗います。

なぜならPythonにはnumpyはあるけどmicroPythonには無いからです!
waveモジュールも同じ。microPythonの環境によっては存在しているけどほとんど無い。

環境を選ばず動かせるのを目標としますので少なくともnumpyとwaveは使用禁止という縛りでいきます。

本題

今回はファイルアクセス部分です。
まだwaveファイルにはしません。ただファイルの読み書きが出来るよってところをどうにかしましょうってことなんですが、前回気になっていたサンプリングレートのこととか、あと、このままモジュールが増えてゆくとディレクトリに山のようなpyスクリプトがどっかどっか増えて気持ちよくないのでサブディレクトリにスクリプトを移してやったり。何気に盛りだくさんなのでひとつずついきます。

サブディレクトリ

libディレクトリに細かなモジュールは移すことにしました。
パス操作とかは煩わしいのでbareaudio.pyだけはカレントに残ってもらって煩わしい感じのインポートを一手に引き受けます。

./
├── bareaudio.py
├── lib
│   ├── eg.py
│   └── osc.py
└── main.py

こんな風になります。
サブディレクトリに移ったことで普通のimportが通らなくなりますので bareaudio.py ではそのあたりをどうにかします。

bareaudio.py
import lib.osc as osc
import lib.eg as eg

抜粋。サブディレクトリのインポートをしつつ以前どおりの名前で利用出来るように。

サンプルレートおよび出力ファイル対応

出力ファイルの対応は暫定です。
というのも複数ファイル扱いたいことだってあるはずだよね。
とりあえずの動作確認

サンプリングレート

OSCクラスに直接かいてあったサンプリングレートを表まで引きずり出します。
そのためにbareaudio.pyとosc.pyに変更を加えます。

osc.py
    def __init__(self,smpl):
        self.smpl = smpl
bareaudio.py
    def __init__(self,fileName,smpl=44100):
        self.smpl = smpl
        self.osc = osc.OSC(smpl)

指定しなければ自動で44100Hzになるようにデフォルト指定。

main.py
b = bareaudio.BareAudio("test.dat",44100)

外からこの様に指定する、44100なら前述のとおり省略可能。

ファイル対応

悩んだ末にひとまずbareaudio.pyにファンクションとして登録したけど別途クラスにした方がいいかもしれない。

bareaudio.py
    def __init__(self,fileName,smpl=44100):
        self.smpl = smpl
        self.osc = osc.OSC(smpl)
        self.eg  = eg.EG()
        try:
            self.f=open(fileName,"r+b")
        except OSError:
            self.f=open(fileName,"w+b")

    def writeByte(self,frame,value):
        self.f.seek(frame)
        self.f.write(bytes([0xff & value]))

    def readByte(self,frame):
        self.f.seek(frame)
        return self.f.read(1)

    def free(self):
        self.f.close()

    def write(self,frame,value):
        d = int(value * 32767)
        self.writeByte(frame*2,d>>8)
        self.writeByte(frame*2+1,d & 0xff)

    def read(self,frame):
        hb = self.readByte(frame*2)
        lb = self.readByte(frame*2+1)
        d = hb[0]<<8 | lb[0]
        if d > 32767:
            d=d-65536
        return d / 32767

実数のままバイナリ保存できて、読み込めば実数に戻る。けど16bitだから精度は当然落ちるけど。

main.py
#!/usr/bin/python3

import bareaudio

b = bareaudio.BareAudio("test.dat",44100)

for fr in range(200):
    a = b.osc.sinOsc(fr,b.mtof(69))
    b.write(fr , a)

for fr in range(200):
    print(b.read(fr))

b.free()

テスト。なんとなく上手くいってる。

コード全体

変更が多かったbareaudio.pyとosc.pyのコード全体を出して今回はおわり。
次回はファイルの取扱いをしっかり考えたり、wavファイルまわりとかできたらいいな。

bareaudio.py
import lib.osc as osc
import lib.eg as eg

class BareAudio:
    def __init__(self,fileName,smpl=44100):
        self.smpl = smpl
        self.osc = osc.OSC(smpl)
        self.eg  = eg.EG()
        try:
            self.f=open(fileName,"r+b")
        except OSError:
            self.f=open(fileName,"w+b")

    def writeByte(self,frame,value):
        self.f.seek(frame)
        self.f.write(bytes([0xff & value]))

    def readByte(self,frame):
        self.f.seek(frame)
        return self.f.read(1)

    def free(self):
        self.f.close()

    def write(self,frame,value):
        d = int(value * 32767)
        self.writeByte(frame*2,d>>8)
        self.writeByte(frame*2+1,d & 0xff)

    def read(self,frame):
        hb = self.readByte(frame*2)
        lb = self.readByte(frame*2+1)
        d = hb[0]<<8 | lb[0]
        if d > 32767:
            d=d-65536
        return d / 32767

    def mtof(self, midiNoteNo):
        freq = 8.1758125
        octave = int( midiNoteNo / 12 )
        freq = freq*(2**octave)*(1.0594628987669812**(midiNoteNo-octave*12))
        return freq
osc.py
import math

class OSC:
    def __init__(self,smpl):
        self.smpl = smpl

    def sinOsc(self, t, freq, phase=0 ):
        return math.sin( t * math.pi*2 / self.smpl * freq + math.pi/180.0 * phase )
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.