MeCabをWindowsでコンパイルする
セットのリンク
エラーの修正方法
(... は変更しないので、省略しているということです。)
src/string_buffer.hのStringBuffer クラスに以下の関数を追加
class StringBuffer { ... StringBuffer& operator<<(unsigned long long n) { _UITOA(n); } ... }
Makefile.msvc.inのDDIC_VERSIONとDVERSIONを以下のように修正
64bitでビルドする場合に限りLDFLAGSの/MACHINE:X86を/MACHINE:X64に修正してください
... LDFLAGS = ... /MACHINE:X64 ... ... DEFS = ... -DDIC_VERSION="102" \ -DVERSION="\"0.996\"\ ... ...
以下のコマンドをコンソールから実行するとmecab.exeがコンパイルできます。
vcvarsall.batはMSVCについてくるやつで人によってパスが違うので適宜変えてください。
cd mecab-0.996/src/ "C:hogehoge/vcvarsall.bat" x64 nmake -f Makefile.msvc.in ...
Windowsのコンソールで直接UTF-8での入出力をするとバグる
概要
Windowsのコンソールを dhcp 65001 で一応UTF-8を表示させる。
C/C++のアプリケーションの標準入出力を使ってコンソールからインタラクティブな動作をさせたとき、日本語を入れるとバグる。
なので、ユーザー入出力部分はshift-jisにしておくのが良い。
詳細
Windowsのコンソールにはコマンドプロンプトとパワーシェルがある。
それぞれ dhcp 65001 コマンドで一応UTF-8環境にできる。
しかしパワーシェルではフォントが変えられないので表示できない。すべて、□や☆や?マークになってしまう。
コマンドプロンプトではフォントを変えれば表示できる。しかし、アプリケーションの入力に使えないことがあるようだ。
以下のコードは、入力テキストをそのまま返す機能+入力をバイトごと分けて値を表示する機能のアプリケーションのc++コードである。
input1()はC++の標準入出力で行単位で、input2()はCの標準入出力で行単位で、input3()はCの標準入出力でバイト単位で読み書きを行っている。
#include <iostream> #include <stdio.h> #include <string> void input1() { std::string s; std::cin >> s; std::cout << s<< std::endl; for (unsigned char c : s) { std::cout << int(c) << " "; } } void input2() { std::string s; char m[2048] = { 0 }; fgets(m, 2048, stdin); puts(m); s = m; for (unsigned char c : s) { printf("%d ", c, stdout); } } void input3() { std::string s; char m[2048] = { 0 }; char c; int i = 0; while ((c = getc(stdin)) != EOF) { m[i] = c; ++i; } puts(m); s = m; for (unsigned char c : s) { printf("%d ", c, stdout); } } int main() { std::string s; // input1(); // input2(); input3(); }
上記のコードを関数1つだけ実行されるよう修正してからコンパイルして、関数それぞれがどのように振舞うのか実験する。
結果が以下のとうりである。
input1()
>ConsoleApplication1.exe hogeほげhoge hogehoge 104 111 103 101 0 0 104 111 103 101 >echo hogeほげhoge|ConsoleApplication1.exe hogeほげhoge 104 111 103 101 227 129 187 227 129 146 104 111 103 101
input2()
>ConsoleApplication1.exe hogeほげhoge hoge 104 111 103 101 >echo hogeほげhoge|ConsoleApplication1.exe hogeほげhoge 104 111 103 101 227 129 187 227 129 146 104 111 103 101 10
input3()
>ConsoleApplication1.exe hogeほげhoge hoge 104 111 103 101 ^C >echo hogeほげhoge|ConsoleApplication1.exe hogeほげhoge 104 111 103 101 227 129 187 227 129 146 104 111 103 101 10
以上の結果から、アプリケーションからのパイプ入力であれば正しく動作する。
しかし、コンソールから入力するとアプリケーションはなぜか1ビットの0を受けとる。
したがって、ユーザー操作で動作させるコンソールアプリケーションは少なくとも入出力部分はUTF-8で作らない方がよい。内部でUTF-8を使う場合でも入出力はshift-jisでラップしておくと多分困らない。
波形のピッチを変えるフィルタのアイデア
内容
音の発生時間をほとんど変えずにピッチだけ変更する。フィルタ作成のアイデア
解説
入力から出力までの大まかな流れを以下のように考える。
- 音源
- >窓関数(サイズ=2048*補正値, 移動量)
- >ピッチ変更処理
- >Blackman関数
- >逆窓関数(サイズ=2048, 移動量)
- >出力
窓関数
まず人の聴覚のおおよそ20~20kHzぐらいの音が聞こえるらしい。
そのため大体20Hz以下のデータを加工し、Blackman関数を通してから合成すれば、あまりノイズが気にならない…多分。
音声データはおおよそ44.1kHz or 48kHzなので2000サンプルぐらい取ればギリギリ20Hzぐらいにはなる。
なので出力はそれぐらいのサイズを目指す。
入力サイズはそれに補正を加える。具体的には以下の値を掛ける。
再生速度=2 * (音程の変更量 / 12)
移動量は適当に決める。大きいとうなりのような音がすることがある。
ピッチ変更処理
時間方向に1/再生速度に縮めるだけ。
Blackman関数
そのままBlackman関数を掛ける
逆窓関数
窓関数で分割したデータを結合する
内部に十分なメモリを持っておいて、データを受け取り合算し移動量だけシフトをひたすら繰り返す。
はみ出した部分が合成終了したデータなので、それを使う。
その他
実際にはピッチの変更処理時のサンプルの初期位置の補正などいろいろ工夫がいる
宣伝
音をいろいろ弄れる感じのやつをちびちび作ってます。
github.com
Web Audio API で遊ぼう録 3
前書き
WebAudioAPIでいろいろ再生したい。つづき。
前回
nanamedou-h.hatenablog.com
メモ
ローカルファイルの音楽再生
(略) <audio> </audio> <input id='fileInput' type='file' accept='audio/*'> (略)
let fileInput = document.getElementById('fileInput') function beginSoundFileMode(file) { let audioCtx = new window.AudioContext(); let audioFileBlob = window.URL.createObjectURL(file) let music = new Audio(audioFileBlob) let sourceNode = audioContext.createMediaElementSource(music) sourceNode.connect(audioContext.destination) music.play() } fileInput.onchange = () => { beginSoundFileMode(fileInput.files[0]) }
ローカルファイルはhtml要素のinput type='input' でいけるっぽい。
この時filesの要素がfileの情報の配列になるので、これをの最初の要素をつかう。valueの値は無効なURLっぽい。
こやつ(files[0])を window.URL.createObjectURL() に入れるとファイルにアクセスできるURLができる。
これでめでたく、ローカルファイルが使えるようになった。
参考-MDN(https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/file)
あとは,このファイルがソースの Audio を新しく作成して、ソースノードを作ってお好みでつなげていくだけ。
ブラウザでパソコン内の曲が聞き放題だぜー。
エフェクトを作る
ホワイトノイズ
let whiteNoiseBuffer = audioCtx.createBuffer(1, 8000, 8000) let nowBuf = whiteNoiseBuffer.getChannelData(0) for (let i = 0; i < nowBuf.length; ++i) { nowBuf[i] = (Math.random() * 2 - 1) } whiteNoiseNode = audioCtx.createBufferSource() whiteNoiseNode.buffer = whiteNoiseBuffer whiteNoiseNode.loop = true whiteNoiseNode.start()
[-1,1]のランダムな値を入れたバッファを再生するとラジオのノイズみたいなホワイトノイズができる。
これを
- 音源->中間ノードA->中間ノードB->出力
- ノイズ->中間ノードA
となるようにconnect()を呼んでいけばノイズをmixした音ができる。
エコー
ノードの設計を
- 音源->中間ノードA->中間ノードB->出力
- 中間ノードA->DelayNode->GainNode->中間ノードA
となるようにconnect()していくと、DelayNodeで設定しただけずれて、GainNodeで設定しただけだんだんと弱まっていく、エコーができる。
今日の成果
入力が増えた。
電波の悪いラジオみたいな音が鳴らせるようになった。
予定
見た目をよくしたい。
Web Audio API で遊ぼう録 2
前書き
WebAudioAPIでなんか作りたい記録。つづき。
前回(nanamedou-h.hatenablog.com)
メモ
マイク入力を実装する。
let audioCtx = new (window.AudioContext || window.webkitAudioContext)(); if (navigator.mediaDevices.getUserMedia) { // メディア使用権限取得 navigator.mediaDevices.getUserMedia( { audio: true, video: false }).then((stream) => { /* ノードを作成する */ let sourceNode = audioCtx.createMediaStreamSource(stream) /* ノードを接続する */ sourceNode.connect(audioCtx.destination) }).catch((err) => { console.log('メディア初期化に失敗しました。: ' + err) }) } else { console.log('navigator.mediaDevices.getUserMedia がサポートされていません。') }
こんな感じで初期化できる。chromeだと初期化はユーザー入力イベント中じゃないとダメみたい。
ビジュアライザを作る
今日の成果
虹色
予定
ローカルディレクトリににある音楽を再生できるようにする。
Web Audio API で遊ぼう録
MeCabをPythonから使って遊びたい
例
コード
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import MeCab import re import pandas as pd ATTR = ['表層形', '品詞', '品詞細分類1', '品詞細分類2', '品詞細分類3', '活用形', '活用型', '原形', '読み', '発音'] TEXT = "今日はいい天気だった。\n明日も晴れるといいな。くぁせdrftgyふじこlp\n" def create_word_segmentation_func(cmd=''): """形態素解析を行う関数を生成する。 MeCabを呼び出し、この時に呼び出したMeCabで形態素解析する関数を作成する。 Args: cmd (str, optional): MeCab呼び出し時にMeCabに渡すコマンド 出力形式(--???-format)は設定しないでください。処理がうまくいかなくなる可能性があります。 Returns: (str) -> (pd.DataFrame): 形態素解析を行う関数 Args: (str): 形態素解析したい文字列 Returns: pd.DataFrame: 形態素解析を行う関数 [['表層形','品詞','品詞細分類1','品詞細分類2','品詞細分類3','活用形','活用型','原形','読み','発音'],\n ...\n ] """ # MeCab起動 # 同時に出力方法を設定しておく m = MeCab.Tagger(r'--node-format=%m\t%f[0]\t%f[1]\t%f[2]\t%f[3]\t%f[4]\t%f[5]\t%f[6]\t%f[7]\t%f[8]\n' + r' --unk-format=%m\t%f[0]\t%f[1]\t%f[2]\t%f[3]\t\t\t%f[6]\t\t\n' + r' --eos-format= ' + ' ' + cmd) def word_segmentation(s): """形態素解析を行う関数 具体的な説明は外の関数の戻り値参照 """ # MeCabで解析 # 表層形,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音 # の形で戻ってくる d = m.parse(s) # words_infos に結果を詰めていく words_infos = [] l = 0 winfo = [''] * 10 i = 0 for k, r in zip(d, range(0, len(d))): if(k == '\t'): winfo[i] = d[l:r] i += 1 l = r + 1 elif(k == '\n'): winfo[i] = d[l:r] words_infos += [winfo] winfo = [''] * 10 i = 0 l = r + 1 # テーブル words_infos を pd.DataFrame に変換して返す return pd.DataFrame(words_infos, columns=ATTR) return word_segmentation if __name__ == '__main__': # 形態素解析 word_segmentation = create_word_segmentation_func() words_infos = word_segmentation(TEXT) # 解析結果を '表層形 原形\n' で len(tword_info)行 で標準出力 for k in words_infos[['表層形', '原形']].values: print(k[0] + ' ' + k[1]) # 速度を計る import unittest class WS_TEST(unittest.TestCase): def test_ws(self): for i in range(100): word_segmentation(TEXT) unittest.main()
出力
今日 今日 は は いい いい 天気 天気 だっ だ た た 。 。 明日 明日 も も 晴れる 晴れる と と いい いい な な 。 。 く くい ぁせ drftgy ふじこ ふじこ l l p p . ---------------------------------------------------------------------- Ran 1 test in 0.100s OK
解説
基本的なこと
基本的なライブラリの入れ方や使い方は末尾にある参考ページへ。
MeCabの出力を使いやすくする
m = MeCab.Tagger(r'--node-format=%m\t%f[0]\t%f[1]\t%f[2]\t%f[3]\t%f[4]\t%f[5]\t%f[6]\t%f[7]\t%f[8]\n' + r' --unk-format=%m\t%f[0]\t%f[1]\t%f[2]\t%f[3]\t\t\t%f[6]\t\t\n' + r' --eos-format= ' + ' ' + cmd)
MeCabの初期化時にフォーマットを指定することで出てきた結果を後で使いやすくする。
今回は、tvs形式で出力させている。こうすることで、非常にデータの扱いやすさが増す。
具体的な指定方法は、MeCab公式ページ->目次->高度な使い方->出力フォーマットの詳細定義 から参照できる。
できるだけ早くテーブルを作りたい!!
# MeCabで解析 # 表層形,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音 # の形で戻ってくる d = m.parse(s) # words_infos に結果を詰めていく words_infos = [] l = 0 winfo = [''] * 10 i = 0 for k, r in zip(d, range(0, len(d))): if(k == '\t'): winfo[i] = d[l:r] i += 1 l = r + 1 elif(k == '\n'): winfo[i] = d[l:r] words_infos += [winfo] winfo = [''] * 10 i = 0 l = r + 1
pythonのstr型はことあるごとにコピーが発生するため文字列操作が結構遅い。
append(other) はotherのコピーを作ってそれを末尾に追加し、
list += other はotherの参照を末尾に追加する。
したがって、捨ててしまうwinfoを+=でwords_infos に結合させて最適化している。
また、リストの初期化は ['']*10 が早い気がする。
参考
MeCab (URL: http://taku910.github.io/mecab/)
mecab-python3 (URL: https://pypi.org/project/mecab-python3/)
Pandas (URL: https://pandas.pydata.org/)