Python ねこふんじゃったを演奏する「PyAudio」

'20/06/13更新:音が多少ずれてたのを修正しました。
 ライブラリpyaudioのインストールは下記のようにcondaで出来ます。

conda install pyaudio

 音は波(三角関数sin)です。音階は周波数の違いで表現できます。そのため、楽譜に沿って、ドレミファソラシドの音符を順番に指定すれば、曲を演奏できます。4分音符や8分音符といった音の長さも指定します。ゲイン(音の強さ)は曲風が変わります。
 下図は「ねこふんじゃった」の音(波)をプロットしたグラフです。正弦波でない波は、2つの正弦波を足した和音です。

f:id:HK29:20200613194345p:plain

■本プログラム

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import pyaudio
import numpy as np
import wave
import struct
import glob
import re

#import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt

import datetime
now = datetime.datetime.now()
now = now.strftime("%y%m%d")

# サイン波を生成する関数
def tone(freq, length, gain): # 引数:(周波数, 長さ, 振幅)
    slen = int(length * RATE) # 基本周波数
    if not isinstance(freq, list): # freqの型がリストでない場合
        t = float(freq) * np.pi * 2 / RATE
        sin_curve = np.sin(np.arange(slen) * t) * gain
    else: # freqがリストの場合の処理。和音
        amp = float(gain) / len(freq)
        sin_curve = 0
        for f in freq:
            t = float(f) * np.pi * 2 / RATE
            sin_curve += np.sin(np.arange(slen) * t) * amp

    plt.figure(num=1, figsize=(10,6))
    plt.plot(sin_curve[0:500], label=freq)
    plt.legend(bbox_to_anchor = (1.05, 1.0))
    plt.tick_params(labelsize=16)
    plt.tight_layout() 
    plt.grid()
    plt.savefig(now + '_sin_curve.png') 
    #plt.close()
    
    return sin_curve

# 音声を再生する関数
def play_wave(stream, sin_curve):
    stream.write(sin_curve.astype(np.float32).tostring())

# 楽曲をリストで作成
def create_music():
    sample_list = []
    #                  音階, 音符, 強さ
    sample_list.append([Dsharp4, L8, GAIN]) #ね
    sample_list.append([Csharp4, L8, GAIN]) #こ
    sample_list.append([Fsharp3, L2, GAIN]) #ふん
    sample_list.append([[Asharp3, Fsharp4], L4, GAIN]) #じゃ # 和音
    sample_list.append([[Asharp3, Fsharp4], L4, GAIN]) #った
    
    sample_list.append([Dsharp4, L8, GAIN]) #ね
    sample_list.append([Csharp4, L8, GAIN]) #こ
    sample_list.append([Fsharp3, L2, GAIN]) #ふん
    sample_list.append([[Asharp3, Fsharp4], L4, GAIN]) #じゃ
    sample_list.append([[Asharp3, Fsharp4], L4, GAIN]) #った

    sample_list.append([Dsharp4, L8, GAIN]) #ね
    sample_list.append([Csharp4, L8, GAIN]) #こ
    sample_list.append([Fsharp3, L2, GAIN]) #ふんー
    sample_list.append([[Asharp3, Fsharp4], L4, GAIN]) #じゃー
    sample_list.append([Dsharp3, L2, GAIN]) #ふんー
    sample_list.append([[Asharp3, Fsharp4], L4, GAIN]) #じゃー
    sample_list.append([Csharp3, L2, GAIN]) #ふん
    sample_list.append([[B3, F4], L4, GAIN]) #じゃ
    sample_list.append([[B3, F4], L4, GAIN]) #った
    
    return sample_list

# カレントディレクトリに存在してるwavファイルを読み込んで再生する関数
def read_wav(sample_list):
    p=pyaudio.PyAudio()
    stream = p.open(format = pyaudio.paInt16,
             channels = 1,
             rate = RATE,
             frames_per_buffer = 1024,
             input = True,
             output = True) # inputとoutputを同時True

    wav_list = glob.glob('./*wav')
    wav_list = sorted(wav_list, key=lambda x:int((re.search(r"[0-9]+", x)).group(0)))
    for wavfile in wav_list:
        print(wavfile)
        wr = wave.open(wavfile, "rb")
        input = wr.readframes(wr.getnframes())
        output = stream.write(input)
    stream.close()

# メイン関数
def main():
    ### 出力用のストリームを開く
    p = pyaudio.PyAudio()
    stream = p.open(format=pyaudio.paFloat32,
                    channels=2,
                    rate=RATE,
                    frames_per_buffer=1024,
                    output=True)

    ### 音楽の再生とwavファイル出力
    sample_list = create_music()
    binary_data_list = []
    for i, sample in enumerate(sample_list):
        print(i, sample)
        # sin波の生成
        sin_curve = tone(*sample)
        
        # 音波をcsvファイルに出力
        np.savetxt(now + '_' + str(i) + '_sin_curve.csv', # 書き出すファイル名
                   sin_curve, # 書き出すnumpyアレイ形式のデータ
                   delimiter=',', # カンマ区切り
                   fmt='%.1f') # fは小数

        # 音を再生
        play_wave(stream, sin_curve)

        # wavファイルへ保存するために、バイナリデータへ変換する
        # sinカーブを-32768から32767の整数値へ変換(16bit pcm)
        sin_curve = [int(x * 32767.0) for x in sin_curve]       
        # バイナリ化
        binary_data = struct.pack("h" * len(sin_curve), *sin_curve)
        binary_data_list.append(binary_data) # バイナリデータをリストで追加
    # リスト内の要素をバイト型へ変換
    binary_data_list = list(map(bytes, binary_data_list))
    # 全要素を結合
    binary_data = b''.join(binary_data_list)
    #sin波をwavファイルで出力
    w = wave.Wave_write(now + "_sine.wav")
    p = (1, 2, 44100, len(binary_data), 'NONE', 'not compressed')
    w.setparams(p)
    w.writeframes(binary_data)
    w.close()

    stream.close()

    ### カレントディレクトリに存在してるwavファイルを読み込んで再生する
    read_wav(sample_list)

if __name__ == '__main__':
    # サンプリングレート
    RATE = 44100
    
    # ゲイン
    GAIN = 1

    # BPM(Beats Per Minute)
    BPM = 100 # 1分間あたりに刻む拍数を100回
    
    # 音長
    L1 = (60 / BPM * 4) # 音の長さを秒単位で、全分音符
    L2, L4, L8 = (L1/2, L1/4, L1/8) # 2分音符(L2)、4分音符(L4)、8分音符(L8)を計算する

    # 周波数
    # ド  レ ミ  ファ ソ ラ シ
    #  C, D,  E,  F,  G, A, B
    
    C3, Csharp3, D3, Dsharp3, E3, F3, Fsharp3, G3, Gsharp3, A3, Asharp3, B3 \
    = (130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220.000, 233.082, 246.942)
    C4, Csharp4, D4, Dsharp4, E4, F4, Fsharp4, G4, Gsharp4, A4, Asharp4, B4 \
    = (261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440.0, 466.164, 493.883)
    C5, Csharp5, D5, Dsharp5, E5, F5, Fsharp5, G5, Gsharp5, A5, Asharp5, B5 \
    = (523.251, 554.365, 587.330, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880.000, 932.328, 987.767)

    main()
    print('finished')

●参考リンク

https://people.csail.mit.edu/hubert/pyaudio/docs/

以上

<広告>