【その4】Pythonでメロディーを奏でたい-モジュール化とwavファイル出力

f:id:nuakam:20170929172616j:plain



準備編 pythonでメロディーを作りたい - エンジニア戦記
第2回 【その2】pythonでメロディーを作りたい - エンジニア戦記
第3回 【その3】Pythonでメロディーを奏でたい - エンジニア戦記←前回
第4回 ここ

目標

モジュール化をして、作った音楽ファイルをwav形式にして保存したい。

環境

  • python3
  • windows10
  • anaconda
  • 第3回まで終わっている。

概要

前回は別ファイルに保存した音データを流してみた。
今回は

  1. 冗長化したソースを機能ごとに別ファイルに分ける
  2. 作った音楽をwav形式にして保存させる。

ソース

前回のソース

import wave
import struct
import numpy as np
import pyaudio
import sys

from pylab import *

def openfile(argv):
    a_file = open (argv,encoding="utf-8")
    befor_music = a_file.read()
    a_file.close()

    return befor_music

def conversion(befor_music):
    befor_list = befor_music.split(',')

    return befor_list


def createSinWave (A, f0, fs, length):

    data = []
    for n in arange(length * fs):
        s = A * np.sin(2 * np.pi * f0 * n / fs) 
        if s > 1.0: s = 1.0
        if s < -1.0: s = -1.0
        data.append(s)

    data = [int(x * 32767.0) for x in data]
    data = struct.pack("h" * len(data), *data)

    return data

def play(data, fs, bit):
    p = pyaudio.PyAudio()
    stream = p.open(format=pyaudio.paInt16,
            channels=1,
            rate=int(fs),
            output=True)
    chunk = 1024
    sp = 0
    buffer = data[sp:sp+chunk]
    while buffer != b'':
        stream.write(buffer)
        sp = sp + chunk
        buffer = data[sp:sp+chunk]

    stream.close()
    p.terminate()


if __name__ == "__main__" :
    freqList =conversion(openfile(sys.argv[1]))
    count = len(freqList)
    del freqList[count-1]

    freqList = [int(i) for i in freqList]

    for f in freqList:
        data = createSinWave(1.0, f, 8000.0, 2.0)
        play(data, 8000, 16)

ちょっと長い。見通しも悪い。

オブジェクト指向チックに機能ごとに分けてファイルを分割していく。
Cやjavaと違ってpythonは分割がとても楽。


モジュール

pythonはモジュールという概念がある。
大きな規模のプログラムを作るときは複数のプログラムに分けたくなる。


そのときに役立つのがモジュール(module)という機能。



pythonでは、スクリプトが書かれた1つのファイルを、1つのモジュールとして扱う。
そして、モジュールの中で定義した関数などを、別のファイルの中で利用できるように工夫されている。



そのために使うのがimport文。
よく一番上に書いてあるやつ。

import(モジュール名)
モジュールの例

hoge.py内でfuga()という関数を書いたとする。

  • 別のpythonスクリプトから使うには import hoge と書く(拡張子は省略可能)。
  • 同じディレクトリ内に2つのソースコードを置く。
  • 関数を呼ぶときにはhoge.huga() と呼び出す。

つまりは

hoge.py

def fuga:
  print ("hello")
  return 0

をmain.pyから呼び出すには

main.py

import hoge

hoge.fuga()

これだけでいいのだ。

実行結果

$ python main.py
hello

これを現在作っているファイルにも利用しよう。

実際に分割してみる。

作るファイルは

  1. File_open.py
  2. play_music.py
  3. test.py

の3つ

File_openには音楽ファイルの入力をまとめておく。

import sys
import wave
import struct
import numpy as np
from pylab import *

def openfile(argv):
    a_file = open (argv,encoding="utf-8")
    befor_music = a_file.read()
    a_file.close()

    return befor_music

def conversion(befor_music):
    befor_list = befor_music.split(',')

    return befor_list


test.pyは正弦波作成と音を鳴らす関数を。
(名前の適当さは許してほしい。)

import wave
import struct
import numpy as np
from pylab import *

def createSinWave (A, f0, fs, length):
    data = []
    for n in np.arange(length * fs):
        s = A * np.sin(2 * np.pi * f0 * n / fs)
        if s > 1.0: s = 1.0
        if s < -1.0: s = -1.0
        data.append(s)
    data = [int(x * 32767.0) for x in data]
    data = struct.pack("h" * len(data), *data)
    return data


def play(data, fs, bit):
    import pyaudio

    p = pyaudio.PyAudio()
    stream = p.open(format=pyaudio.paInt16,
            channels=1,
            rate=int(fs),
            output=True)

    chunk = 1024
    sp = 0
    buffer = data[sp:sp+chunk]
    while buffer != b'':
        stream.write(buffer)
        sp = sp + chunk
        buffer = data[sp:sp+chunk]

    stream.close()
    p.terminate()

play_music.pyはメイン関数として最終的な処理をさせる。

import struct
import numpy as np
from pylab import *

#オリジナルモジュール
import File_open 
import test       #ここの2つで定義

if __name__ == "__main__" :
    freqList = File_open.conversion(File_open.openfile(sys.argv[1])) #File_openのopenfileとconversionを使って代入
    count = len(freqList)
    del freqList[count-1]

    freqList = [int(i) for i in freqList]

    for f in freqList:
        data = test.createSinWave(1.0, f, 8000.0, 2.0) #test内のcreateSinWaveを利用
        test.play(data, 8000, 16)            #同様に。


そして前回作った音楽のテキストファイルを引数に実行すると

$python play_music.py music.txt

問題なく音楽がなるはず。

作った音楽ファイルをwavにして保存

キレイになったところで、機能の追加を。

いじるのはFile_open.pyとPlay_music.pyの2つ。


wavファイルはマイクロソフト社が開発した音声フォーマットでとても便利。
import waveでwavファイルの多くの機能を導入できる。


まずはopen_Fileに以下の関数を追加。

def save_wave(binwaves):
    w = wave.Wave_write("output.wav")
    p = (1, 2, 8000, len(binwaves), 'NONE', 'not compressed')
    w.setparams(p)
    w.writeframes(binwaves)
    w.close()

入力にbinwave(バイナリデータ)を。
Wave_write関数でoutput.wavとして出力させる。
音声フォーマットにはいろいろなステータスを入れる必要があるので、それらを一気に入れて、ファイルクローズ。


ちなみにpython3以降とpython2以前は扱い方が全く違う。
2のときはおそらくこのように書くことになる。

    wf = wave.open(filename, "w")
    wf.setnchannels(1)
    wf.setsampwidth(bit / 8)
    wf.setframerate(fs)
    wf.writeframes(data)
    wf.close()


次はplay_music.pyを以下のように書き加える

if __name__ == "__main__" :
    freqList = File_open.conversion(File_open.openfile(sys.argv[1]))
    datas=b"" #ここと
    count = len(freqList)
    del freqList[count-1]

    freqList = [int(i) for i in freqList]

    for f in freqList:
        data = test.createSinWave(1.0, f, 8000.0, 2.0)
        test.play(data, 8000, 16)
        datas += data #ここと

    File_open.save_wave(datas) #ここ


datas=b"" で空のリストを作る。

これもバージョン2とは違って、3は文字列型に厳しくなったため、バイナリデータだと示す必要がある。
リスト名 = b"" のようにbをつけることでバイナリデータ型のリストを作れる。

datas = data で正弦波に変換した音をリストに代入していき

File_open.save_wave(datas) で先程作った保存機能を使っておしまい。


これでoutpot.wavができたはず。

準備編 pythonでメロディーを作りたい - エンジニア戦記
第2回 【その2】pythonでメロディーを作りたい - エンジニア戦記
第3回 【その3】Pythonでメロディーを奏でたい - エンジニア戦記←前回
第4回 ここ