ひばりログ

Apple関連やプログラミングなど趣味を書き綴る。

Pythonでwavの前後の無音部分をカットする

概要

f:id:hibari2978:20190811153448p:plain これを f:id:hibari2978:20190811153450p:plain こうする、ということです。

コード

import wave
import numpy as np
import matplotlib.pyplot as plt
import struct

input_filename = 'after_wav/001.wav'
output_filename = 'output.wav'

theta = 0.02
keep_frame = 50

wav = wave.open(input_filename, "rb")
data = wav.readframes(wav.getnframes())
ch = wav.getnchannels()
sample_width = wav.getsampwidth()
framerate = wav.getframerate()

wav.close()
x = np.frombuffer(data, dtype="int16") / float(2**(sample_width*8-1))

front_index = 0
after_index = 0

wav_len = len(x)

print(sample_width)

for i in range(wav_len):
    if theta < np.abs(x[i]):
        front_index = i
        break

for j in range(wav_len):
    if theta < np.abs(x[wav_len-j-1]):
        after_index = wav_len - j
        break

print(front_index)
print(after_index)

x = x[front_index-keep_frame:after_index+keep_frame] * float(2**(sample_width*8-1))
w_x = [int(d) for d in x]
w_x = struct.pack("h" * len(w_x), *w_x)

# グラフ表示
plt.figure(figsize=(15, 3))
plt.plot(x)
plt.show()

output_wav = wave.open(output_filename, 'wb')
output_wav.setnchannels(ch)
output_wav.setsampwidth(sample_width)
output_wav.setframerate(framerate)
output_wav.writeframes(w_x)
output_wav.close()

thetaが無音部分のしきい値で、値を大きくしていくと

削る範囲が大きくなっていきます。

keep_frameは切り取る前後を伸ばすためのもので、

単位はフレームです。

機械学習のデータ作成に使えるかも……?

Pydubでモノラル変換やサンプリングレートを変えてwavを出力

目的

pythonを使って、wavファイルを一括でモノラル化やサンプリングレートなどを変更したい。

方法

まず、pydubのAudioSegmentを使って、音ファイルを読み込む。

sound = AudioSegment.from_file(filepath, format="wav")

ステレオからモノラルにしたいならば、set_channels()を使い、引数を1にする。

sound = sound.set_channels(1)

サンプリング周波数を変えるなら、set_frame_rate()、

量子化bitを変えるならset_sample_width()を使う。 ただし、引数はbyteなので、16bitにしたいならば2を指定する。

コード

コードはこちら。 globを使って、フォルダ内のwavファイルを一括処理できる。

import glob
from pydub import AudioSegment
from pathlib import Path

for name in glob.glob('before_wav/*.wav'):
    filename = Path(name).name
    sound = AudioSegment.from_file(name, format="wav")

    sound = sound.set_channels(1)
    sound = sound.set_frame_rate(24000)
    sound = sound.set_sample_width(2)
    sound.export("after_wav/"+filename, format="wav")

before_wavにあるwavファイルを全て、

モノラル、サンプリング周波数16000Hz、量子化ビット16bitのwavファイルを

after_wavに出力するというプログラムになっている。

参考

pydub/API.markdown at master · jiaaro/pydub · GitHub

vuedraggableを使って並び変えたリストをflaskに送る【Flask】【Vue.js】

はじめに

そもそもjavascriptを使ったことがほとんどなく

node.jsやnpmに関して、全く知識がなかったため、

Flaskでそれらを使わずにVue.jsを使いたかったために

その方法を四苦八苦したので、忘れないようにメモしておきます。

コードはより良い方法があったり、実践環境で使うにはアレかもしれません。

とりあえず、動くことを目標に作りました。

私の環境ではIDEにpycharmを使っているので、

javascriptライブラリはローカルに保存されて、便利です。

完成図

f:id:hibari2978:20190713161053g:plain

コード

全体コードです。

list_move.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="../static/css/list_move.css">
    <script src="../static/js/vue.min.js"></script>
    <script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

</head>

<body>
    <div id="app">
        <form v-on:submit.prevent="submit">
            <draggable v-model="list.list_id" element="ul" :options="{animation:400}">
                <li v-for="item in list.list_id" :key="item.id" class="input_list">[[ item.id ]]</li>
            </draggable>
            <input type="submit" value="submit">
        </form>
            <div v-if="display">
                <h2>ResultList</h2>
                <ul>
                    <li v-for="item in result_list" class="output_list"> [[ item.id ]] </li>
                </ul>
            </div>
    </div>
</body>

<script>
    new Vue({
        el: '#app',
        delimiters: ['[[', ']]'],
        data: {

            list: {
                list_id:[
                    { id: 1 },
                    { id: 2 },
                    { id: 3 },
                    { id: 4 },
                    { id: 5 },
                    { id: 6 },
                    { id: 7 }
                ]
            },
            display: false,
            result_list: []

        },
        methods :{
           submit: function() {
               axios.post('/api', this.list)
                    .then(response => {
                        this.result_list = response.data.result_list;
                        this.display = true;
                    })
                    .catch(error => {
                        console.log(error);
                    })
            }
        }
    })
</script>

app.py

from flask import Flask, request, render_template, send_file, jsonify
import os

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('list_move.html')

@app.route('/api', methods = ['POST'])
def get_list_data():
    data_list = {
        "result_list": request.json["list_id"]
    }
    return jsonify(data_list)

Vue.jsのdelimiters

Flaskの標準テンプレートエンジンであるJinja2との共存を防ぐために、

delimitersを{{ }}から へと変更します。

delimiters: ['[[', ']]']

注意!

ここでハマったのが、開発バージョンではdelimitersが効いてないようです。

こちらのドキュメントでは"完全ビルドでのみ利用可能"とあります。 API — Vue.js

はじめに — Vue.js こちらの本番バージョンのことであるのか、分かりませんが

開発バージョンではなく、本番バージョンを使うと動作しました。

npmを使わずにvuedraggableを使う

リストをマウスで並び替えられるvuedraggable。

使用方法を検索しても、ほとんどnpmを使ったものばかりで、苦戦しました。

まず

<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

で、必要なライブラリをimportします。

Vueのデータをとりあえず、こんな風に設定します

 data: {
            list: {
                list_id:[
                    { id: 1 },
                    { id: 2 },
                    { id: 3 },
                    { id: 4 },
                    { id: 5 },
                    { id: 6 },
                    { id: 7 }
                ]
            }
}

HTMLにはlist_idのidを表示させ、並び替えられるようにします。

 <draggable v-model="list.list_id" element="ul" :options="{animation:400}">
        <li v-for="item in list.list_id" :key="item.id" class="input_list">[[ item.id ]]</li>
 </draggable>

これでlistを並び替えられるようになりました。

Vue.jsのDataをFlaskに送信

こちらを参考にしました。

kivantium.hateblo.jp

まず、htmlでaxiosをimportします

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

htmlのformはこんな感じで、先ほどのリストを入れ子にしています。

<form v-on:submit.prevent="submit">
    <draggable v-model="list.list_id" element="ul" :options="{animation:400}">
         <li v-for="item in list.list_id" :key="item.id" class="input_list">[[ item.id ]]</li>
    </draggable>
    <input type="submit" value="submit">
</form>

vueのmethodsに送信ボタンが押された時のメソッドを書きます。

methods :{
    submit: function() {
        axios.post('/api', this.list)
                 .then(response => {
                        this.result_list = response.data.result_list;
                        this.display = true;
                  })
                 .catch(error => {
                        console.log(error);
                 })
    }
}

これで送信ボタンが押されると、/apiにPostされるので

Flaskの方には、送信先のメソッドを作ります。

@app.route('/api', methods = ['POST'])
def get_list_data():
    data_list = {
        "result_list": request.json["list_id"]
    }
    return jsonify(data_list)

request.jsonで送信されたjsonファイルが参照できるので、

それを色々活用できるかと思います。

Pythonでsin波を鳴らして遊ぶ

コード

import numpy as np
import wave
import struct
import pyaudio
import matplotlib.pyplot as plt

sample_num = 1024   # 1フレームあたりのサンプル数
channel = 1
rate = 16000        # サンプリング周波数
width = 2
format = pyaudio.paInt16

play_seconds = 1
frequency = 44{0}

total_sample_num = rate * play_seconds
t = np.arange(0, total_sample_num)
s = 32767.0 * np.sin(2*np.pi*frequency*t/rate)
s = s.astype(np.int16)

data = struct.pack("h" * total_sample_num , *s)

p = pyaudio.PyAudio()
stream = p.open(
    format=format,
    channels=channel,
    rate=rate,
    output=True
)

frame = data[0:sample_num]
for i in range(1, len(data)):
    stream.write(data)
    data = data[sample_num*i:sample_num*(i+1)]

stream.close()
p.terminate()

plt.plot(s[0:1000])
plt.show()

実行すると、作ったsin波の音が再生され、

sin波がグラフで表示されます。

f:id:hibari2978:20190709163018p:plain

sin波の作成

total_sample_num = rate * play_seconds
t = np.arange(0, total_sample_num)
s = 32767.0 * np.sin(2*np.pi*frequency*t/rate) * volume
s = s.astype(np.int16)

ここでsin波の作成をしています。

sin波に32767.0を乗算しているのは、

量子化ビットが16bit=(-32768~32768)だからです。 

量子化ビット

量子化ビットというのは、振幅をどれだけ細かくとるか、という単位です。

音をデジタルで表現するには、線ではなく点で表す必要があります。

f:id:hibari2978:20190709163916p:plain

上図が実際に440Hzのデジタル上の表現です。

縦軸は省略していますが、実際には

-32768、-32767、-32766…… 0、1、2…… 32766、32767、32768

というような細かさになっています。

これを量子化ビットという単位で決めています。

サンプリング周波数

横軸はサンプリング周波数で決めていて、

この場合は16000Hzと指定しているので

1秒間に16000個の点があります。

つまり、このグラフは横軸が1000まで表示されているので、

1 / 16000 * 1000 = 0.0625 なので

0.0625秒分となっています。

周波数を変える

このsin波をいじることで、様々な音を作れます。

一番簡単なのは周波数を変えることで、

音の高さが変わります。

440HzはA4(ラ)の音ですね。

(ピアノなどは442Hzなどにされていることが多いそうですが)

周波数を増やすと、音は高くなり、

減らすと低くなります。

sin派の振幅を制限する

他にもsin波の振幅を制限することで、

矩形波に変えて音色を変えることもできます。

upper_limit = 20000
downer_limit = -20000

total_sample_num = rate * play_seconds
t = np.arange(0, total_sample_num)
s = 32767.0 * np.sin(2*np.pi*frequency*t/rate)
s = s.astype(np.int16)

s = np.where(s > upper_limit, upper_limit, s)
s = np.where(s < downer_limit, downer_limit, s)

f:id:hibari2978:20190709165412p:plain

こちらは上限を20000、下限-20000にし、 それを超えた場合は上限下限に抑える

というようにして、波形をいじっています。

このように波形をいじることで、

色々と音を変えて遊ぶことができます。

DontDestroyOnLoadで特定のシーンだけ破壊されないObjectを作る

方法

OnLevelWasLoadedというイベント関数を使う。

docs.unity3d.com

Sceneが切り替わった際に呼び出されるようイベント関数です。

このイベント関数は非推奨のようですが

2019.1では使うことができました。

その内、使えなくなる可能性があります。

DontDestroyObjectを使うと、ずっと残り続けてしまうため、

例えばリトライ(同じシーンの再読み込み)するときに、

BGMは継続したい、といったときに、こういった工夫が必要になります。

コード

やっていることは単純で、シーンが読み込まれた際に、

インスペクタで設定した_sceneNameと現在のシーンの名前が同じならば、

新しくできてしまったObjectをDestroyしているだけです。

OnLevelWasLoadedは引数にlevelを取っているので、

名前でなくて、シーンレベルでも良いと思います。

public class TargetSceneDontDestroy : MonoBehaviour
{
    public string _sceneName;
    private static GameObject _thisObject;

    // Start is called before the first frame update
    void Awake()
    {
        DontDestroyOnLoad(gameObject);

        if (_thisObject != null)
            Destroy(gameObject);
        else
            _thisObject = gameObject;
       
    }

    private void OnLevelWasLoaded(int level)
    {
        if (SceneManager.GetActiveScene().name != _sceneName)
            Destroy(gameObject);
    }
}

なぜAwakeで処理しないのか

始め、AwakeやOnEnableのイベント関数で処理していたのですが、

どうやらシーン切り替え時には呼ばれないようです。

(インスタンス生成時に呼ばれるので、今考えれば当然なのですが)

そこでOnLevelWasLoadedを使うことで、

シーンの読み込み時にオブジェクトを削除することができるようになります。

もはやPythonAppleも関係なしです。

PyaudioとMatplotlibでリアルタイムに音の波形をグラフ化する

できたもの

f:id:hibari2978:20190705154853g:plain

ソースコード

import pyaudio
import numpy as np
import matplotlib.pyplot as plt

# 1フレームあたりのサンプル数
CHUNK = 1024
# サンプリング周波数(1秒間にとる音の入力点の数)                                
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
# 何秒間をグラフ化するか                                
DISPLAY_SECONDS = 4                         

pa = pyaudio.PyAudio()

# 実際のサンプル数
sample = int(RATE/CHUNK) * CHUNK
# 表示する時間のサンプル数            
all_sample = (DISPLAY_SECONDS+1) * sample   
# x軸のデータ
x = np.arange(0, all_sample, 1)
# y軸のデータを0でパディング             
y = np.zeros(all_sample)                    

try:
    while True:
        # 収録の設定
        stream = pa.open(
            format = FORMAT,
            channels = CHANNELS,
            rate = RATE,
            input = True,
            frames_per_buffer = CHUNK
        )

        # 次に表示するフレームの音をずらす
        y[:all_sample-CHUNK] = y[CHUNK:all_sample]                          
        data = np.frombuffer(stream.read(CHUNK), dtype="int16")/32768.0
        # ずらした部分にデータを入れる
        y[all_sample-CHUNK:all_sample] = data                               

        plt.plot(x, y)   
        plt.pause(0.01)  # 引数はsleep時間
        plt.cla()        # 現在描写されているグラフを消去
        
        stream.close()
        
        
        
except KeyboardInterrupt:
    stream.close()
    pa.terminate()
    pass

音の収録

こちらのサイトを参考にさせて頂きました。 qiita.com

まずstreamを開き、1フレーム(1024)のデータを読み込みます。

stream = pa.open(
            format = FORMAT,
            channels = CHANNELS,
            rate = RATE,
            input = True,
            frames_per_buffer = CHUNK
        )

data = np.frombuffer(stream.read(CHUNK), dtype="int16")/32768.0

次に、1フレームのデータを読み込んだので、

表示する音のサンプルを入れてあるyの配列を

1フレーム(1024)分、右から左へとずらし、

読み込んだデータを入れます。

y[:all_sample-CHUNK] = y[CHUNK:all_sample] 
y[all_sample-CHUNK:all_sample] = data 

リアルタイム図示

こちらのサイトを参考にしました。

space-denpa.jp

plt.plot(x, y)   
plt.pause(0.01)  
plt.cla()

参考にしたサイトと同じことをやって、

リアルタイムで表示することができました。

TouchBarを便利にするアプリ『BetterTouchTool』

Macを便利にするBetterTouchTool(BTT)とは

f:id:hibari2978:20190704125645p:plain このようにトラックパッドやTouchBarに

キーボードショートカットや様々なアクションを

作動させることができる魔法のようなアプリです。

設定できる項目

トラックパッドの設定でできること

例えば、Safariに対して3本指でスワイプさせることによりタブを閉じたり、

左や右にスワイプして複数タブの移動など、

少ない動作で手軽にショートカット機能を実行することができます。

TouchBarをカスタマイズ

ここからが本題です。

現在のTouchBarはMacOSのアプデによって

クイックアクションなど多少のカスタマイズはできるようになりましたが、

個人的にはまだ全然です。

しかし、BTTを使えばTouchBarは自由自在!

とても便利に使うことができるのです。

例えば、こちらはFInderに対してのTouchBarです。

f:id:hibari2978:20190704130941p:plain

このようにカラフルに表示したり、好きなテキストで表示することもできます。

上記の設定は、すべてのアプリで表示させる項目と

Finderで表示させる項目の二つからなります。

f:id:hibari2978:20190704131631p:plainf:id:hibari2978:20190704131658p:plain

今の所はこんな感じで設定しています。

FinderでTouchBarで新規ファイル作成したり、

開いているフォルダをVSCodeなどで開いたりすることができます。

ショートカットの便利さ

BTTでTouchBarに割り振ることが多いのはショートカットだと思います。

ショートカットを登録するくらいなら覚えればいいじゃん、という方もおられるかと思いますが、

TouchBarにショートカットを登録することは非常に便利なんです。

もちろんCmd+Z(Undo)やCmd+C(コピー)などはどれにも共通するものだし、

簡単なので、登録する必要はないです。

しかし、アプリケーションによっては最初から登録されているショートカットが多すぎて

このコマンドよく使うけど、押しやすいショートカットキーが残っていないという時に

非常に有効に使えます!

例えば、Ctrl+Shift+Opt+Cmd+Aなど、実用的ではないショートカットを登録しておいて

TouchBarに登録することで、1タッチで呼び出せるので、便利です!

複雑なショートカットにすることで、他のショートカットを汚染することもないので

効率化まちがいなし!

価格

残念ながらBTTを無料で使えるのは期間がありますが、

最安値で360円で手に入ります。

お得感!

ぜひ使ってみてください。