pythonメニュー

Pythonで扱いサウンド

標準のwaveモジュールでサウンドファイル(.wav)を生成する

以下は、8000サンプリングレート(1秒間に8000個)で16bit符号ありのビットレート仕様で、 1KHzの音を0.2秒間鳴らすtest.wavを生成する例です。
# mk_sin_wave.py
import numpy as np
sample_rate = 8000# サンプリングレート(samples/seconds)
frequency=1000 # 単位 Hz
seconds = 0.2 # 生成時間(秒)
CHANK = 1024
# 1KHzの波形を生成-----------------------
# n=1つのCHANKに必要な1KH波形の数=sample_rateHzの周期の1つのCHANK当たりの時間 ÷ frequency Hzの周期
n = (1/sample_rate)*CHANK/(1/frequency)
print( f"1つのCHANK(={CHANK})に必要な{frequency}Hz波形の数={n}")
dx = 2* np.pi*n / (CHANK)
a=0x07fff * np.sin(np.arange(0, sample_rate * seconds, dx))
a=a.astype(np.int16)
print(a, a.shape)
import matplotlib.pyplot as plt
plt.plot(a)
plt.show()
barray = a.tobytes()
print("生成バイナリのbyte数", len(barray))

def save_wave_file( barray, path="test.wav"):
    ''' barray の16bitリトルエンディアンバイト列から、未圧縮でpathのサウンドファイルを作る'''
    import wave
    wavefile = wave.open(path, 'wb') # waveファイルをバイナリ保存用として開く。
    wavefile.setnchannels(1) # モノラル(単一の音信号)
    wavefile.setsampwidth(2) # 2byteのデータ群と録音する指定
    wavefile.setframerate(sample_rate) # サンプリングレート
    wavefile.writeframes(barray) # 上記で設定した属性で、ファイルに出力
    wavefile.close()

save_wave_file( barray) # ファイル生成

def print_inf_wave_file( path="test.wav"):
    import wave
    wavefile = wave.open(path, mode='rb')
    print(f"-----{path}ファイルの情報-----") 
    print('type: ', type(wavefile)) 
    print('チャンネル数:', wavefile.getnchannels()) # モノラルなら1,ステレオなら2
    print('サンプル幅:', wavefile.getsampwidth()) # バイト数 (1byte=8bit)
    print('サンプリング周波数:', wavefile.getframerate()) # CDは44100Hz
    print('フレーム数:', wavefile.getnframes()) # サンプリング周波数で割れば時間
    params=wavefile.getparams()# 上記+αのパラメータクラスを返す
    print('パラメータ',type(params) ,":", params) 

print_inf_wave_file() # ファイル情報の確認

以下の実行イメージです。
1つのCHANK(=1024)に必要な1000Hz波形の数=128.0
[     0  23169  32767 ...  23169      0 -23169] (2038,)
生成バイナリのbyte数 4076
-----test.wavファイルの情報-----
type:  
チャンネル数: 1
サンプル幅: 2
サンプリング周波数: 8000
フレーム数: 2038
パラメータ  : _wave_params(nchannels=1, sampwidth=2, framerate=8000, nframes=2038, comptype='NONE', compname='not compressed')

pyaudioモジュールのインストール

Windowsであれば、「管理者」としてコマンドプロンプトで次のように「pip install pyaudio」のキー入力で行います。
なおスタートメニューのコマンドプロンプトアイコンの右クリックでを選択することで起動するか、 または、「ファイル名を指定して実行」で、「cmd」と入力し、「Ctrl + Shift + Enter」キーを押して起動します。
C:\Windows\system32>pip install pyaudio
Collecting pyaudio
  Downloading PyAudio-0.2.11-cp36-cp36m-win_amd64.whl (52kB)
    100% |????????????????????????????????| 61kB 3.4MB/s
Installing collected packages: pyaudio
Successfully installed pyaudio-0.2.11

C:\Windows\system32>

サウンドファイルの録音とファイル保存、そしてプロット

キー入力まで録音し、録音データをファイルに保存して、プロットする例です。 キー入力があるので、pythonコマンドで、以下のソースファイルを実行する起動方法で使います。
import sys
import time
import _thread # Python3では、「_thread」を使う
import pyaudio
import wave

key_code = 0 #キーが押されたらキーデータがセット

def input_key(): # スレッドで実行予定のキー入力関数
   print("キー入力待機")
   c = sys.stdin.read(1) # 1文字入力
   global key_code
   key_code = c

# 新しいスレッドを開始して、input_keyを実行させ、そのIDを返します。
thread_id = _thread.start_new_thread(input_key, ()) 

FORMAT = pyaudio.paInt16 # 2byteのデータ群と録音する指定
CHUNK=1024 # 1回のinput で 1024個のデータを録音(サイズは1024 × 2 byteのバイナリ)
RATE=44100
CHANNELS=1

audio=pyaudio.PyAudio()

stream=audio.open(format = FORMAT,
   channels = CHANNELS,
   rate = RATE,
   frames_per_buffer = CHUNK,
   input = True,
   output = True) # inputとoutputを同時にTrueにする

print("録音スタート")
frames = []
while key_code == 0:
   input = stream.read(CHUNK) # 録音データを取得 (CHUNK×2byte のバイナリ)
   frames.append(input) # 録音されたバイナリデータを配列の最後の要素として追加している。
   output = stream.write(input)

stream.stop_stream()
stream.close()
audio.terminate()
print("録音ストップ")

wavefile = wave.open("test.wav", 'wb') # waveファイルをバイナリ保存用として開く。
wavefile.setnchannels(CHANNELS)
wavefile.setsampwidth(audio.get_sample_size(FORMAT))
wavefile.setframerate(RATE)
wavefile.writeframes(b''.join(frames)) # 上記で設定した属性で、ファイルに出力
wavefile.close()

# グラフの描画
import numpy as np
def array_16_from_chunk(frames, chunkSize=1028, bigFlag=False): # チャンクのバイナリの配列から1次 16bit整数に変換
   a = []
   for i in range(len(frames)):
      # a += array_16_from_binary(frames[i], bigFlag )  #2バイト、リトルエンディアンで変換と同じ処理
      a += np.frombuffer(frames[i], dtype="int16").tolist() # 上記と同じ結果が得られるnumpyのメソッド
   return a

import matplotlib.pyplot as plt

a = array_16_from_chunk(frames, True) # バイナリの配列から、int のリストに変換
plt.plot(np.arange(len(a)), np.array(a), label='サウンド波形')
plt.show()
framesは上記設定で、バイナリ(CHUNK×2byte のバイナリ)の配列になる。
これは上記で、「てすと」と発音してできた時の波形です。

サウンドファイルの再生

上記の録音で得たファイルを再生するプログラム例
import pyaudio # 録音や再生に使う
import wave # waveファイル読み取り用

CHUNK = 1024
filename="test.wav"
wavefile = wave.open(filename, 'rb') # waveファイルを読み取りでバイナリオープン

audio = pyaudio.PyAudio()
sampwidth = wavefile.getsampwidth() #サンプル幅
print("sampwidth:", sampwidth)
format=audio.get_format_from_width(sampwidth) # ストリームを読み書きするときのデータ型
print("format:", format) # ステレオかモノラルかの選択 1でモノラル 2でステレオ
channels=wavefile.getnchannels() # ステレオかモノラルかの選択 1でモノラル 2でステレオ
print("channels:", channels)
rate=wavefile.getframerate() # サンプル周波数
print("ramerate:", rate)

stream = audio.open(format=format,channels=channels,rate=rate,output=True)
# 上記は、引数で指定する順番が定義と違うので、引数名を指定して実行させている。

num=0; # チャンクのカウント
data = wavefile.readframes(CHUNK) # 1024個読み取り
while len(data) > 0:
    num += 1
    stream.write(data)  # ストリームへの書き込みで、これで音が出力される。
    data = wavefile.readframes(CHUNK) # ファイルから1024個*2個の

print("チャンク数",num)
stream.stop_stream()
stream.close()
audio.terminate()

サウンドファイルを加工して、別名で保存

上記で使ったファイルを読み込んだ後、 先頭 26000個のデータを削除し、後ろ 32000個のデータを削除し、最大値が32567か最小値が-32768になるような増幅を行って、 それをプロットし、ファイルに保存する例です。

サウンド再生でなく、単なるファイル操作であれば、wave.openで得られたインスタンスのreadframesで、1回の一括読み取りも可能であり、以下ではそれを使っています。
これで得られるデータは、一つのバイナリになります。この1つのバイナリでデータを、上記と違ってnumpuのfrombufferで、numpy配列にしています。
それにより、加工やグラフ表示が簡単にできます。
import numpy as np
import wave

wavefile = wave.open("test.wav", "r")
nframes = wavefile.getnframes() # フレーム数(データの個数)
print("nframes:" , nframes) # 実行例: nframes: 88064
framerate = wavefile.getframerate() # フレームレート(1秒間のデータ出力数)
print("framerate:", framerate) # 実行例: framerate: 44100
buffer = wavefile.readframes(nframes) # ファイルからの読み取り(読み取りフレーム数)で、ファイルからバイナリのデータ部を読み取り、返す。
wavefile.close() # ファイルを閉じる 

# print(buffer) # b'\xbe\xff-\xfeS・・・・・・・\xb6\xfd\x9a\xfdx\xfd'のようなバイナリで得られる。

y1 = np.frombuffer(buffer, dtype="int16") # bufferのバイナリが、16bit整数(リトルエンディアン)である前提で、1次numpyの配列に変換
print(y1) # 実行例: [ -66 -467 -941 ..., -586 -614 -648]
print("type:",type(y1)) # 実行例: type: <class 'numpy.ndarray'>
print("type:",type(y1[0])) # 実行例: type: <class 'numpy.int16'>
print(len(y1), '数' ) # 88064 数

y1 = np.delete(y1, range(0,26000)) # 先頭 26000個のデータを削除
y1 = np.delete(y1, range( len(y1)-32000,len(y1) ) ) # 後ろ 32000個のデータを削除
print(y1) # 先頭 26000個のデータを削除
max = y1.max()
min = y1.min()
print("max:", max ,"min:", min )
raise_y=1 # 倍率算出
if max >= abs(min):
   raise_y = 32767 / max
else :
   raise_y = abs(32767 / min)

y1 = y1 * raise_y # フルスケールを使う倍率変換
y1 = y1.astype(np.int16)

import matplotlib.pyplot as plt
t1 = np.arange(0, len(y1)) / float(framerate) # プロットのx軸用データ生成
plt.plot(t1, y1, 'r')
plt.xlabel('Time [sec]')
plt.show()

# numpy の配列を wave モジュールを使って wav ファイルに保存
w = wave.Wave_write("output.wav") 
w.setnchannels(1) # チャンネル数
w.setsampwidth(2) # サンプル(量子化)サイズで、16bitなら2(バイト)。
w.setframerate(44100) # チャンネル数
w.writeframes(y1) # 保存するnumpy の配列
w.close()







一定時間だけ録音して、それを再生します。

一定時間だけ録音して、framesの配列に記憶したバイナリー記憶します。
numb=100 で、このチャンクの操作回数だけ録音です。
その後に、それを再生します。

'''
  一定時間だけ録音して、framesの配列に記憶したバイナリー記憶します。
numb=100 で、このチャンクの操作回数だけ録音です。
その後に、それを再生します。 
'''
import pyaudio # 録音や再生に使う

audio = pyaudio.PyAudio()

FORMAT = pyaudio.paInt16 # 2byteのデータ群と録音する指定
CHUNK=1024 # 1回のinput で 1024個のデータを録音(サイズは1024 × 2 byteのバイナリ)
RATE=32000
CHANNELS=1

stream=audio.open(format = FORMAT,
   channels = CHANNELS,
   rate = RATE,
   frames_per_buffer = CHUNK,
   input = True,
   output = True) # inputとoutputを同時にTrueにする

numb=100 # チャンクの操作回数

print("録音スタート", int( (1/RATE) *CHUNK*numb*1000), "ミリ秒")
frames = []
for idx in range(numb):
    d = stream.read(CHUNK) # 録音データを取得 (CHUNK×2byte のバイナリ)
    frames.append(d) # 録音されたバイナリデータを配列の最後の要素として追加している。

# 次のコードを有効にすると上記録音の代わりにサイン波形の音を埋め込む
'''
import math
import numpy as np
frames = []
for idx in range(100):
    dx = np.pi/16
    a = (0x1ff * np.sin(np.arange(0, dx * 1024, dx))).astype(np.int16)+0x1ff
    #from plotting import plotting
    #plotting(np.arange(1024),a, True)
    #exit()
    d = a.tobytes()
    frames.append(d)
'''

input("Enter で再生します。>>>")

idx=0 # チャンクのカウント
while idx < len(frames):
    stream.write(frames[idx])  # ストリームへの書き込みで、これで音が出力される。
    idx += 1

stream.stop_stream()
stream.close()
audio.terminate()

上記コメントは、録音のサイン波の生成データで記憶域を埋める処理です。 サウンドデータ生成の手法参考になるでしょう。




待ち行列でサウンドデータを管理して再生する例(ライブラリとして使えるように作成)

サウンドの待ち行列と、再生スレッドを作っています。
mainでは確認用で、1KHzのsin波の音を、待ち行列に入れて再生しています。再生終了前でEnterでスレッドを終えることができます。
解像度(ビットレート)は、pyaudio.paInt16の指定により、16bit符号付きデータです
sample_rate を変更しても、1KHzのsin波を作っているので、聞き比べ可能です。
音が鳴る長さはsample_rate を変更すると、音が鳴る期間が変わります。(forの繰り返し数で変更できます)
play_sin.py

import numpy as np
import threading
from collections import deque
from time import sleep
import pyaudio

# 再生関連関数定義 ---------------ここから
audio = pyaudio.PyAudio()
# サンプリングレート(1秒間で何個のデータを使うかの値)の指定
sample_rate = 48000 # 映像業界の音の標準(DVD)
sample_rate = 44100 # 音楽業界の標準(CD-DA)
sample_rate = 32000 # FMステレオ放送中継
sample_rate = 16000 # 高音質IP電話など
sample_rate = 8000 # 電話(ISDNなど)
stream=audio.open(format = pyaudio.paInt16, # 2byteのデータ群と録音する指定
   channels = 1,
   rate = sample_rate, # サンプリングレート(1秒間で何個のデータを使うかの値)
   frames_per_buffer = 1024,#1回の処理で使うサイズは1024 × 2 =2048byte
   input = False, #録音は使わない 
   output = True) # 再生をTrueにする

queue = deque() #再生待ち行列
lock = threading.Lock() # Lockモジュール生成

def set_data(data):# 再生データをキューにセットする。
    lock.acquire() # 排他制御開始
    queue.append(data)
    lock.release() # 排他制御解除

flag_playing = True
def playing():# 再生スレッド関数
    while flag_playing:
        if len(queue) == 0:
            sleep(0.0001)
            continue
        lock.acquire() # 排他制御開始
        data=queue.popleft()
        lock.release() # 排他制御解除
        stream.write(data)  # ストリームへの書き込みで、これで音が出力される。
        #print(data)

# 再生関連関数定義 ---------------ここまで

snd_id = threading.Thread(target=playing) #再生部分を別スレッドにする
snd_id.start() #再生スレッドをスタート

# 1KHzの波形を生成して鳴らす-----------------------
# n=1CHANKに必要な1KH波形の数=sample_rateHzの周期の1CHANK当たりの時間 ÷ 1KHの周期
n = (1/sample_rate)*1024/(1/1000)
print( "1CHANKに必要な1KH波形の数=", n)
dx = 2* np.pi*n / (1024)
a=0x1ff * np.sin(np.arange(0, dx * 1024, dx))
a=a.astype(np.int16) + 0x1ff
print(a, a.shape)
import matplotlib.pyplot as plt
plt.plot(a)
plt.show()
d = a.tobytes()
print(d, len(d))
for idx in range(10):
    set_data(d) # sin波のバイナリーデータ1CHANK分をキューに入れる(それが再生される)
input("終了 Wait Enter>>>")
flag_playing = False



参考: https://people.csail.mit.edu/hubert/pyaudio/docs/#class-stream