Pythonロゴともろみ先輩

皆さんは長いwavファイルを書き出そうとしてPCが固まってしまったという経験ありませんか?

これはPCのメモリ容量を超えてしまうような大容量な配列を生成するために生じてしまいます。

一方で長時間の録音をしたり長時間の信号を書き出したいというシチュエーションはそこそこあるのではないかと思います。

今回はこれの解決法をご紹介します!

上手くいかないコーディング

まず失敗例を紹介します。今3時間のホワイトノイズを生成してwavファイルとして書き出すようなコードを書きたいとします。

このときまず最初に思いつくのは次のようなコードかと思います。

※ これを実行するとPCが固まる可能性が高いのでご注意ください

import numpy as np
import wave_func as wf

fs = 48000
length = fs*60*60*3
s = np.random.random(size=length)*2 - 1
wf.writeWave("./white_noise.wav", s)

ここでwave_funcは以下で紹介した自作のライブラリです。必要に応じて使ってみてください。

ではなぜこのコードは上手く走らないのでしょうか。

実はバグがあるわけではなく,これは純粋にメモリ不足で動かないというのが正解です。

少し真面目に考えてみると,float型(8Byte=64bit)で3時間分の配列のメモリを確保するためには

$$ 8 \times 48000 \times 60 \times 60 \times 3 = 4147200000 \, \, \mathrm{[Byte]} $$

つまり約4GBのメモリ確保が必要になります。

普段使いのPCとかだと搭載メモリ自体が4GBない場合もありますし,それ以上ある場合でもOSの起動やその他のアプリケーションにメモリを食われてますので足りなかったりします。

なので理論的にもPCによっては実行不可能なわけです。

上手くいくコーディング

それではメモリ容量を超えるような長時間音源はどのように生成したら良いでしょうか?

この答えは簡単で,

短時間の区間で区切って細切れにして書き出す

ことで解決できます!

細かい説明は後にしてまずコードをお見せします。

import numpy as np
import wave
from tqdm import tqdm
import wave_func as wf

output_path = "./white_noise_long.wav"
sampwidth = 4
fs = 48000
length = fs*60*60*3
loc_len = fs*60

output = wave.open(output_path, "wb")
output.setnchannels(1)
output.setsampwidth(sampwidth)
output.setframerate(fs)

loop = int(np.ceil(length/loc_len))

for i in tqdm(range(loop)):
    loc_sig = np.random.random(size=min(loc_len, length-loc_len*i))*2 - 1
    frames = wf.float2binary(loc_sig, sampwidth) # float to binary
    output.writeframes(frames)

output.close()

これで実行できるようになります!

ポイントはlengthと別にloc_lenという短い長さでlengthを区切ってやってから少しずつ書き出しているところです。

output = wave.open(output_path, "wb")

でループに入る前にwavファイルを書き出せる状態にしておきます。

そしてループ内ではloc_lenの長さのノイズを生成してすぐに

output.writeframes(frames)

で書き出しています。

ループが終わったらきちんとoutput.close()で終了しましょう。

まとめ

お疲れ様でした。

今回はメモリ容量よりも大きいwavファイルを書き出す方法について解説しました。

この記事が誰かの助けになってくれれば幸いです。

それではまた!