斜道倉庫

いろいろ遊んだことについて書いていく。

MeCabをWindowsでコンパイルする

概要

MeCabをそのままWindowsコンパイルするときにでるエラーの対処法。およびutf-8化の方法。
すぐにコンパイル・インストール・実行ができるセットを作成したのでそのリンク。

セットのリンク

github.com

エラーの修正方法

(... は変更しないので、省略しているということです。)
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
...

utf-8

Makefile.msvc.inを変更する。
CFLAGS とDEFS に文字セットの設定を追加する。

...
CFLAGS = ... /source-charset:utf-8 /execution-charset:utf-8
        ...
...
DEFS = ... -DMECAB_USE_UTF8_ONLY
...

辞書のインストール

utf-8の場合は以下のコマンドを実行
"mecab-dict-index.exe" -d 辞書フォルダ -o 出力先フォルダ -f EUC-JP -t utf-8
別途dicrcを出力先フォルダにコピーする必要あり

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で設定しただけだんだんと弱まっていく、エコーができる。

今日の成果

f:id:nanamedou:20181230222505p:plain
入力が増えた。
電波の悪いラジオみたいな音が鳴らせるようになった。

予定

見た目をよくしたい。

コード

GitHubで更新中(https://github.com/nanamedou/Web-Player)

続く…

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だと初期化はユーザー入力イベント中じゃないとダメみたい。

ビジュアライザを作る

 Canvas API っていう2D描画が楽にできるAPIがあった。これを使う。
参考

今日の成果

f:id:nanamedou:20181224130833p:plain
虹色

予定

 ローカルディレクトリににある音楽を再生できるようにする。

Web Audio API で遊ぼう録

前書き

WebAudioAPIでなんか作りたい記録。はじまり。

なんかすげーやつ Web Audio API

 Web Audio API というブラウザで楽々に、音楽の録音再生だけでなく、動画の録画再生までこなせるAPIが普通のブラウザで使える。音楽の再生中とか、ボイチャ中に音に合わせていろいろ見た目が変わるなんかがあれば楽しそう!!
というわけで、とりあえず使い始めてみるのであった。

目標

  • 音楽の再生とマイクから入力ができる。
  • なんかカッコイイ見た目。
  • Firefox,Chrome,Edgeで動く。

とりあえずふんわりと目標を立てて、動かしつつ具体的に決めていこう。

コード

GitHubで更新していく予定(https://github.com/nanamedou/Web-Player)

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 が早い気がする。

感想

pythonの文字列やリストの操作が結構重いみたい。最初のほうは、MeCab出力を一行ずつ読み取って、正規表現で分割して、appendで足して次の行へ…としていたが、いろいろ最適化をしてみて10倍ぐらい早くなった。pythonもメモリのコピーついてよく考えないと大変なことが分かった。
今回の実装で結構早く形態素解析できるようになったので、いろいろできそう。機械学習を勉強して組み合わせて何かやってみたいなあ…。