少し前に、私はエキゾチックな問題の解決に専念することを決めました-音声ファイルやビデオエディターのように、Pythonを使用して、ウェーブファイルの波を描きます。 その結果、これを非常にうまく処理できる小さなスクリプトが手に入りました。 したがって、上の写真は、クイーンの歌「Under Pressure」から彼によって生成されました。 比較のために、オーディオエディターのウェーブのタイプ:
numpyライブラリを使用してサウンドを解析し、 matplotlibを使用してサウンドをプロットしました。 カットの下で、wavファイルとスクリプトアルゴリズムの操作の基本を概説します。
UPD1:間引き係数kは、経験的に選択された約k = nframes / w / 32を取る方が適切です。 写真を新しい係数で更新しました。
WAVは、メディア業界で広く使用されている非圧縮オーディオストリームを保存するための形式です。 その特徴は、固定数のビットが振幅コーディングに割り当てられることです。 これは出力ファイルのサイズに影響しますが、非常に読みやすくなります。 一般的なWaveファイルは、ヘッダーパート、オーディオストリームを含む本文、および追加情報のテールで構成され、オーディオエディターは独自のメタデータを記録できます。
主なパラメーターはヘッダー部分から抽出されます-チャンネル数、ビットレート、フレーム数-これに基づいて、オーディオストリームが解析されます。 ウェーブファイルには1または2チャネルが格納され、各チャネルは8、16、24または32ビットでエンコードされます。 ある時点での波の振幅を記述する一連のビットは、サンプルと呼ばれます。 特定の瞬間のすべてのチャネルのサンプルのシーケンスは、フレームと呼ばれます。
たとえば、 \ xe2 \ xff \ xe3 \ xfaは16ビットwavファイルのフレームです。 したがって、 \ xe2 \ xffは最初の(左)チャネルのサンプルで、 \ xe3 \ xfaは2番目(右)のチャネルです。 サンプルは、符号付き整数です(8ビットサンプルのファイル、符号なしの数値を除く)。
リッチPythonライブラリには、wavファイルを解析するためのwaveモジュールがあります。 サウンドの基本的な特性を取得し、別のフレームで読み取ることができます。 これにより、その可能性が終わり、オーディオストリームを個別に解析する必要があります。
import wave wav = wave.open("music.wav", mode="r") (nchannels, sampwidth, framerate, nframes, comptype, compname) = wav.getparams() content = wav.readframes(nframes)
これらの行を使用して、wavファイルを読み取るためのオブジェクトを作成します(「r」パラメーターを省略すると、書き込み用のオブジェクトが作成されますが、これは適切ではありません)。 getparams()メソッドは、チャネルの数、サンプルごとのバイト数、1秒あたりのフレーム数、フレームの総数、圧縮タイプ、圧縮タイプの名前など、基本的なファイルパラメーターのタプルを(順番に)返します。 オブジェクトのフィールドに毎回アクセスしないように、それらをすべて別々の変数に入れます。
readframes()メソッドは、オブジェクトの内部ポインターを基準にして指定された数のフレームを読み取り、それをインクリメントします。 この場合、一度に1バイト文字列のすべてのフレームを変数コンテンツに読み込みます。
次に、この行を解析する必要があります。 sampwidthパラメーターは、1つのサンプルをエンコードするのに必要なバイト数を決定します。
- 1 = 8ビット、符号なし整数(0〜255)、
- 2 = 16ビット、符号付き整数(-32768-32767)
- 4 = 32ビット、符号付き長整数(-2147483648-2147483647)
分析は次のとおりです。
import numpy as np types = { 1: np.int8, 2: np.int16, 4: np.int32 } samples = np.fromstring(content, dtype=types[sampwidth])
numpyライブラリを使用します。 その主な目的は、配列と行列を使用した数学演算です。 Numpyは、独自のデータ型で動作します。 fromstring()関数はバイト文字列から1次元配列を作成し、dtypeパラメーターは配列の要素の解釈方法を決定します。 この例では、データタイプは「タイプ」ディクショナリから取得され、サンプルサイズとnumpyデータタイプを比較します。
これで、オーディオストリームサンプルの配列ができました。 その中に1つのチャネルがある場合、配列全体がそれを表し、2つ(または複数)がある場合、各チャネルの各n番目の要素を選択して配列を「間引く」必要があります。
for n in range(nchannels): channel = samples[n::nchannels]
このループでは、[offset :: n]という形式のスライスを使用して、各オーディオチャネルがチャネル配列に選択されます。ここで、offsetは最初の要素のインデックスで、nはサンプリングステップです。 しかし、チャネル配列には膨大な数のポイントが含まれており、3分間のファイルのグラフ出力には膨大な量のメモリと時間が必要になります。 コードにいくつかの追加変数を導入します。
duration = nframes / framerate w, h = 800, 300 DPI = 72 peak = 256 ** sampwidth / 2 k = nframes/w/32
durationは秒単位のストリームの継続時間、wおよびhは出力画像の幅と高さ、DPIはピクセルをインチに変換するために必要な任意の値、peakはサンプル振幅のピーク値、kは画像幅に応じたチャンネル間引き係数です; 経験的に選択された。
グラフ表示を調整します。
plt.figure(1, figsize=(float(w)/DPI, float(h)/DPI), dpi=DPI) plt.subplots_adjust(wspace=0, hspace=0)
これで、チャネルの出力を含むループは次のようになります。
for n in range(nchannels): channel = samples[n::nchannels] channel = channel[0::k] if nchannels == 1: channel = channel - peak axes = plt.subplot(2, 1, n+1, axisbg="k") axes.plot(channel, "g") axes.yaxis.set_major_formatter(ticker.FuncFormatter(format_db)) plt.grid(True, color="w") axes.xaxis.set_major_formatter(ticker.NullFormatter())
ループはチャネル数をチェックします。 すでに述べたように、8ビットサウンドは符号なし整数で保存されるため、各サンプルから振幅の半分を取得して正規化する必要があります。
最後に、下軸の形式を設定します
axes.xaxis.set_major_formatter(ticker.FuncFormatter(format_time))
画像にグラフを保存して表示します。
plt.savefig("wave", dpi=DPI) plt.show()
format_timeとformat_dbは、横軸と縦軸のスケールの値をフォーマットするための関数です。
format_timeは、サンプル番号で時間をフォーマットします。
def format_time(x, pos=None): global duration, nframes, k progress = int(x / float(nframes) * duration * k) mins, secs = divmod(progress, 60) hours, mins = divmod(mins, 60) out = "%d:%02d" % (mins, secs) if hours > 0: out = "%d:" % hours return out
format_db関数は、音量によって音量をフォーマットします。
def format_db(x, pos=None): if pos == 0: return "" global peak if x == 0: return "-inf" db = 20 * math.log10(abs(x) / float(peak)) return int(db)
スクリプト全体:
import wave import numpy as np import matplotlib.pyplot as plt import matplotlib.ticker as ticker import math types = { 1: np.int8, 2: np.int16, 4: np.int32 } def format_time(x, pos=None): global duration, nframes, k progress = int(x / float(nframes) * duration * k) mins, secs = divmod(progress, 60) hours, mins = divmod(mins, 60) out = "%d:%02d" % (mins, secs) if hours > 0: out = "%d:" % hours return out def format_db(x, pos=None): if pos == 0: return "" global peak if x == 0: return "-inf" db = 20 * math.log10(abs(x) / float(peak)) return int(db) wav = wave.open("music.wav", mode="r") (nchannels, sampwidth, framerate, nframes, comptype, compname) = wav.getparams() duration = nframes / framerate w, h = 800, 300 k = nframes/w/32 DPI = 72 peak = 256 ** sampwidth / 2 content = wav.readframes(nframes) samples = np.fromstring(content, dtype=types[sampwidth]) plt.figure(1, figsize=(float(w)/DPI, float(h)/DPI), dpi=DPI) plt.subplots_adjust(wspace=0, hspace=0) for n in range(nchannels): channel = samples[n::nchannels] channel = channel[0::k] if nchannels == 1: channel = channel - peak axes = plt.subplot(2, 1, n+1, axisbg="k") axes.plot(channel, "g") axes.yaxis.set_major_formatter(ticker.FuncFormatter(format_db)) plt.grid(True, color="w") axes.xaxis.set_major_formatter(ticker.NullFormatter()) axes.xaxis.set_major_formatter(ticker.FuncFormatter(format_time)) plt.savefig("wave", dpi=DPI) plt.show()
その他の例: