技術的なやつ

技術的なやつ

5.4 マイク入力の音程解析

  1. Unityを用いる
    1. C#について少し復習する
    2. Unityの2D描画について簡単に学ぶ
  2. Unityの音声制御
    1. MIDIを再生させる手段を見つける
    2. 音声のマイク入力手段を見つける
  3. 入力音声の解析
    1. UnityのFFTライブラリを触ってみる
    2. FFTされたデータから音程を確定させるアルゴリズムを考える
  4. MIDIの解析
    1. 第1トラックの楽譜を解析する
    2. MIDIの再生箇所を解析する手段を見つける
  5. 採点のための解析
    1. 全体の点数計算アルゴリズムを考える
    2. ビブラート判定アルゴリズムを考える
    3. しゃくり判定アルゴリズムを考える
    4. タイミング判定アルゴリズムを考える
    5. なめらかさ判定アルゴリズムを考える

注文したマイクが届いた。
f:id:ibako31:20140206223250j:plain
ちなみに、コレである。ケーズデンキの通販で買って2400円位だった。安い。
正しい用途は動画投稿サイトへの歌の投稿らしい。

SONY エレクトレットコンデンサーマイクロホン PCV80U ECM-PCV80U

SONY エレクトレットコンデンサーマイクロホン PCV80U ECM-PCV80U

サウンドライブラリ

マイク入力については少し置いておき、先日までに書いた音程解析メソッドを整理してライブラリとする。

using UnityEngine;
using System.Collections;

public static class SoundLibrary{

    // オーディオの周波数を調べる
    // ac: 解析したいオーディオソース
    // qSamples: 解析結果のサイズ
    // threshold: ピッチの閾値
    public static float AnalyzeSound(AudioSource ac, int qSamples, float threshold)
    {
        float[] spectrum = new float[qSamples];
        ac.GetSpectrumData(spectrum, 0, FFTWindow.BlackmanHarris);
        float maxV = 0;
        int maxN = 0;
        //最大値(ピッチ)を見つける。ただし、閾値は超えている必要がある
        for (int i = 0; i < qSamples; i++)
        {
            if (spectrum[i] > maxV && spectrum[i] > threshold)
            {
                maxV = spectrum[i];
                maxN = i;
            }
        }

        float freqN = maxN;
        if (maxN > 0 && maxN < qSamples - 1)
        {
            //隣のスペクトルも考慮する
            float dL = spectrum[maxN - 1] / spectrum[maxN];
            float dR = spectrum[maxN + 1] / spectrum[maxN];
            freqN += 0.5f * (dR * dR - dL * dL);
        }

        float pitchValue = freqN * (AudioSettings.outputSampleRate / 2) / qSamples;
        return pitchValue;
    }

    // ヘルツから音階への変換
    public static float ConvertHertzToScale(float hertz)
    {
        float value = 0.0f;
        if (hertz == 0.0f) return value;
        else
        {
            value = 12.0f * Mathf.Log(hertz / 110.0f) / Mathf.Log(2.0f);
            // while (value <= 12.0f) value += 12.0f;
            // while (value > 36.0f) value -= 12.0f;
            return value;
        }
    }

    // 数値音階から文字音階への変換
    public static string ConvertScaleToString(float scale)
    {
        // 12音階の何倍の精度で音階を見るか
        int precision = 2;

        // 今の場合だと、mod24が0ならA、1ならAとA#の間、2ならA#…
        int s = (int)scale;
        if (scale - s >= 0.5) s += 1; // 四捨五入
        s *= precision;

        int smod = s % (12 * precision); // 音階
        int soct = s / (12 * precision); // オクターブ

        string value; // 返す値

        if (smod == 0) value = "A";
        else if (smod == 1) value = "A+";
        else if (smod == 2) value = "A#";
        else if (smod == 3) value = "A#+";
        else if (smod == 4) value = "B";
        else if (smod == 5) value = "B+";
        else if (smod == 6) value = "C";
        else if (smod == 7) value = "C+";
        else if (smod == 8) value = "C#";
        else if (smod == 9) value = "C#+";
        else if (smod == 10) value = "D";
        else if (smod == 11) value = "D+";
        else if (smod == 12) value = "D#";
        else if (smod == 13) value = "D#+";
        else if (smod == 14) value = "E";
        else if (smod == 15) value = "E+";
        else if (smod == 16) value = "F";
        else if (smod == 17) value = "F+";
        else if (smod == 18) value = "F#";
        else if (smod == 19) value = "F#+";
        else if (smod == 20) value = "G";
        else if (smod == 21) value = "G+";
        else if (smod == 22) value = "G#";
        else value = "G#+";
        value += soct + 1;

        return value;
    }

    // 数値音階から生波形を出す
    public static void ScaleWave(float[] scale, int size, LineRenderer line)
    {
        line.SetVertexCount(size);
        float x = -150.0f;
        for (int i = 0; i < size; i++)
        {
            line.SetPosition(i, new Vector3(x, -80.0f + scale[i] * 2.5f, 0));
            x += 1.0f;
        }
    }
}

マイク入力の音程解析

簡単に言うと、以下の様な仕組みである。

  1. AudioSourceのclip(流すファイル)とマイク入力を関連付ける
  2. そのオーディオを再生させる(録音音声を聞きたくない場合はミュートにする)
  3. 前回の記事の要領で音程解析を行う

重要なのは以下のメソッド。

AudioClip Microphone.Start(string deviceName, bool loop, int lengthSec, int frecency)

deviceName

録音に使うマイクの名前。

loop

録音時間が尽きたら最初から録音を再開するか

lengthSec

録音時間

frecency

サンプリング周波数

以下が音程解析ソース。以前までの記事に詳しいことは書いているので、特に説明は必要ないと思う。

using UnityEngine;
using System.Collections;

public class MicroFFT : MonoBehaviour {
    // 波形を描画する
    public LineRenderer line;

    // マイクからの音を拾う
    public AudioSource mic;
    private string mic_name = "UAB-80";

    // 波形描画のための変数
    private float[] wave;
    private int wave_num;
    private int wave_count;


    void Start () {
        // 波形描画のための変数の初期化
        wave_num = 300;
        wave = new float[wave_num];
        wave_count = 0;
        
        // micにマイクを割り当てる
        mic.clip = Microphone.Start(mic_name, true, 999, 44100);
        if (mic.clip == null)
        {
            Debug.LogError("Microphone.Start");
        }
        mic.loop = true;
        mic.mute = true;

        // 録音の準備が出来るまで待つ
        while (!(Microphone.GetPosition(mic_name) > 0)) { }
        mic.Play();
    }
	
    void Update () {
        // 諸々の解析
        float hertz = SoundLibrary.AnalyzeSound(mic, 1024, 0.04f);
        float scale = SoundLibrary.ConvertHertzToScale(hertz);
        string s = SoundLibrary.ConvertScaleToString(scale);
        Debug.Log(hertz + "Hz, Scale:" + scale + ", " + s);
        
        // 波形描画
        wave[wave_count] = scale;
        SoundLibrary.ScaleWave(wave, wave_count, line);
        wave_count++;
        if (wave_count >= wave_num) wave_count = 0;
    }
}

これで、適当にマイクに向かって発声してみました。
f:id:ibako31:20140206224458j:plain
なかなか綺麗に出ます。さすが単一指向マイクです。
ちなみに、私の声域(マイクが拾ってくれた音)は約98Hz~630Hzでした。およそ2オクターブ半なので、まぁ妥当なところだと思います。
AudioSourceをもう一つ追加し、BGMを流しながらちょっとだけ歌いました。
f:id:ibako31:20140206225203j:plain
CROSSOっぽくなってきました。

課題

  • 最初のテストでは2秒の遅延が生じた(以降はほぼ遅延なし)。どうやら、録音準備が出来て実際に録音がスタートしてから、mic.Play()が有効化するのに時間がかかると遅延が発生するらしい。これは回避手段があるのだろうか…。あるとすれば、録音中の時間と再生時間を合わせるくらいか(出来るのか)。
  • 「あ」から「ん」まで発音したが、さ行とた行辺りでノイズが強く発生した。また、「る」でも大きく波がブレた。もしかして、「る」の特殊歌唱ってあまり良くなかったのでは…