クーの自由研究

マスターのかえるのクーは、弟子達の召喚術により新たな依り代を得てⅡ世として復活しました。

演算のため音を32bit浮動小数点に変換する

今日もリハビリです

こんにちわ、こんばんわ。かえるのクーの助手の「井戸中 聖」(いとなか せい)でございます。あまりにもサボっていたので、リハビリが必要なため、日々特訓しております。

今日のお題は「音ファイルのフォーマットを思い出す」です。

16Bit形式の普通のwave ファイルを内部演算に適した32Bit浮動小数点に変換する方法を思い出します。ちょっとしたことは16Bit整数のまま演算してしまうところですが、ほとんどの場合、整数のまますこし演算しただけでかなり音は劣化します。オーバーサンプリングまではする必要ないですが、普通の場合でも、浮動小数点化してから音の演算するのは「常識」だった気がします。

あれ?どうやるんだっけ

まずは2チャンネル wave 形式(48KHz/sec)のデータをpythonで読み込んでみます。

お題:左は正弦波、右はクリック音のwaveファイルがあります。右、左の16Bit/48K音を32Bit/浮動小数点(普通のfloat)に変換してからモノラルにミキシングしてMax0.98(16Bitで±32112)の範囲でノーマライズしてから16Bitモノラル/48Kで出力しよう。できるかな?はてはてふふ~ん。さてさてほほ~!

どうにかできました!

測定ツールは浮動小数点の表示がありますが、出力は16Bit/48Kです。

内部演算はfloat にしています。

-.-.- -.. ..--. .-.- -..-- -.-.. -..- .--. --. -... 

#! /usr/bin/python
# -*- coding: utf-8 -*-
import time
import pyaudio
import wave

# Input / Output
filename="Sample.wav"

CHUNK = 1024
MAX_LIMIT = 0.98

w = wave.open(filename, 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(w.getsampwidth()),
channels=1, # 出力はモノラルです!
rate=w.getframerate(),
output=True)
adj = MAX_LIMIT
for i in range(10):
data = w.readframes(CHUNK)
while len(data) > 0:
# 16 Bitデータを浮動小数点に変換します
# [左2バイトの符号付き整数、右2バイトの符号付き整数].*の形式で連続格納されています
# 2バイト目が16進の上位桁となる
leftAry = []
rightAry = []
mixAry = []
for i in range(int(len(data) / 4)):
leftOne = data[(i * 4):(i * 4 + 2)]
leftInt = int.from_bytes(leftOne, byteorder='little', signed=True)
leftFloat = 1.0 * leftInt / 2**15
leftAry.append(leftFloat)
rightOne = data[(i * 4 + 2):(i * 4 + 4)]
rightInt = int.from_bytes(rightOne, byteorder='little', signed=True)
rightFloat = 1.0 * rightInt / 2**15
rightAry.append(rightFloat)
mixSum = leftFloat + rightFloat
if abs(mixSum) * adj > 1.0:
# Clip扱い
adj = MAX_LIMIT / abs(mixSum)
mixFloat = mixSum * adj
mixAry.append(mixFloat)
# 効率は度外視して処理方法の説明に特化しています
data2 = b''
for i in range(len(mixAry)):
mixInt = int(mixAry[i] * 2**15)
data2 = data2 + mixInt.to_bytes(2, 'little', signed=True)
stream.write(data2)
data = w.readframes(CHUNK)
stream.write(b'\x00\x00' * (48000 - w.getnframes()))
w.rewind()

stream.stop_stream()
stream.close()
p.terminate()

---- .-.-. -.-- .. .--. --.-. .. ..- .- ..-. -.-. ..-. -..- .-.-- .. .-.-- .. ---.-

補足:pyaudioは以前は pip でインストールに失敗する時期が長く続いていましたが、現在は pip コマンドで問題なくインストールできます!

 

正解は「リトルエンディアンで、左、右の順で切り出してからfloatに正規化(1.0を上限の浮動小数点にする)し、各種演算後、再びリトルエンディアンの16Bit整数にして出力する」でした。