5.4 マイク入力の音程解析
注文したマイクが届いた。
ちなみに、コレである。ケーズデンキの通販で買って2400円位だった。安い。
正しい用途は動画投稿サイトへの歌の投稿らしい。
SONY エレクトレットコンデンサーマイクロホン PCV80U ECM-PCV80U
- 出版社/メーカー: ソニー(SONY)
- 発売日: 2011/10/10
- メディア: エレクトロニクス
- 購入: 32人 クリック: 127回
- この商品を含むブログ (8件) を見る
サウンドライブラリ
マイク入力については少し置いておき、先日までに書いた音程解析メソッドを整理してライブラリとする。
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; } } }
マイク入力の音程解析
簡単に言うと、以下の様な仕組みである。
- AudioSourceのclip(流すファイル)とマイク入力を関連付ける
- そのオーディオを再生させる(録音音声を聞きたくない場合はミュートにする)
- 前回の記事の要領で音程解析を行う
重要なのは以下のメソッド。
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; } }
これで、適当にマイクに向かって発声してみました。
なかなか綺麗に出ます。さすが単一指向マイクです。
ちなみに、私の声域(マイクが拾ってくれた音)は約98Hz~630Hzでした。およそ2オクターブ半なので、まぁ妥当なところだと思います。
AudioSourceをもう一つ追加し、BGMを流しながらちょっとだけ歌いました。
CROSSOっぽくなってきました。
課題
- 最初のテストでは2秒の遅延が生じた(以降はほぼ遅延なし)。どうやら、録音準備が出来て実際に録音がスタートしてから、mic.Play()が有効化するのに時間がかかると遅延が発生するらしい。これは回避手段があるのだろうか…。あるとすれば、録音中の時間と再生時間を合わせるくらいか(出来るのか)。
- 「あ」から「ん」まで発音したが、さ行とた行辺りでノイズが強く発生した。また、「る」でも大きく波がブレた。もしかして、「る」の特殊歌唱ってあまり良くなかったのでは…