音の大きさをステレオミニプラグから値として取得する


レベルメーター的な物を作りたいのです…！

# ステレオミニプラグには交流が流れている

ステレオミニプラグ(フォーンプラグ)から、音の大きさの大小を取得したい。3極のステレオミニプラグは手前からGND、R、Lとして取得できる。まずは信号の取得のために適当なオーディオジャック変換基板で手軽に扱えるようにする。

- http://www.aitendo.com/product/12150 こういうやつ

続いて一定の波形を出力するため、Mac で 1kHz のサイン波を出力し、音量最大でステレオミニプラグに入力する。今回はAudioTest のソフトウェア(シェアウェア)を利用した。

- http://www.katsurashareware.com/pgs/audiotest-j.html

以下はオシロスコープによるMacからの出力の波形。1メモリが左下にあるとおり500mVなので、だいたいピーク値が±1.9Vほどの交流で出力されている。また、iPhone 6s でアプリを使って同様に 1kHzのサイン波を流したところ、ピーク値が±1.4V。機器によって結構違うようだ。

![sound_ac.png](https://qiita-image-store.s3.amazonaws.com/0/5330/a294b836-e610-20e0-1b9a-6144fa0bd463.png "sound_ac.png")

つまるところ、音の大きさを読み取るには、交流 ±2.0V ほどの交流の電圧値をうまく Arduino 等から読み取れれば良いが、A/Dコンバータが交流を読めないので、値を読めるようにする必要がある。

## 単相半波で整流して値を取得する

まずは単純に机の上にあったスイッチングダイオード（1N4148)一本で単相半波で整流直流にしてみた。

[![rectification diode by hotchpotch b96c464bb5aa40ab - Upverter](https://upverter.com/hotchpotch/b96c464bb5aa40ab/rectification-diode/embed_img/14560526410000/)](https://upverter.com/hotchpotch/b96c464bb5aa40ab/rectification-diode/#/)

![sound_1n4148.png](https://qiita-image-store.s3.amazonaws.com/0/5330/7a2fd145-3c10-f3e6-56bb-0263be62c998.png "sound_1n4148.png")


最大値が1.2Vほどと、600mVほど下がってしまってる。1N4148のVF(順方向電圧)はスペック的には700mVほどなのでだいたい正しい。また0.6V以下の電圧だとそもそも0になってしまい、小さめの音はほぼ0になってしまう。

こんなときはVFが低いショットキーバリアダイオードを、ということで手元にある 1N5819 に変更したところ、だいたい400mVほど降下しているがだいぶましに。もっとVFが低いショットキーバリアダイオードのほうが良さそう。

![sound_1n5819.png](https://qiita-image-store.s3.amazonaws.com/0/5330/ea058fed-4eec-c848-a0c5-936b10120f4e.png "sound_1n5819.png")

あとはこの単相半波の値を Arduino で読み込んでそれっぽくする。なお通常の曲ではピーク値でも1Vも超えることがほぼ無いため、analogRead() の ADC の基準値を、標準の 5V から 1.1V へと ```analogReference(INTERNAL)``` で変更する。少しの期間をサンプリングして適当に平均を返す。わりとこれでもそれっぽく音の大小をとれる。

```
#define LEFT A2
#define SAMPLE 100

void setup() {
  Serial.begin(9600);
  analogReference(INTERNAL);
  pinMode(LEFT, INPUT);
}

void loop() {
  Serial.println(getLevel(LEFT));
}

uint8_t getLevel(uint8_t pin) {
  float avg = 0;
  float raw;
  for (int sample = 0; sample < SAMPLE; sample++) {
    raw = analogRead(pin);
    avg += raw;
  }
  return avg/SAMPLE;
}
```

ちなみに全波で整流すると、VF*2の電圧下降が起こるのでちょっと厳しそう。

追記・静かな曲だと 400mV の電圧降下でもほぼ値をとれなくなってしまう…。

## レベルシフトで持ち上げる (Arduino の 5V を使う)

ピーク値が交流±1.8Vなら、それをレベルシフトして読み取れる範囲にする方法。

- http://www3.big.or.jp/~schaft/hardware/LevelMeter/page001.html

にあるとおり、レベルシフトで Arduino の出力している直流 5V を抵抗分圧で 2.5V にしてを加えることによって、信号を2.5Vの±1.8Vにすることで、 ADC で読み取れる 0V~5V の範囲に収める。

[![2.5V level shift by hotchpotch 1be660bfa85b15e7 - Upverter](https://upverter.com/hotchpotch/1be660bfa85b15e7/25V-level-shift/embed_img/14560580830000/)](https://upverter.com/hotchpotch/1be660bfa85b15e7/25V-level-shift/#/)

![sound_levelshift_5v.png](https://qiita-image-store.s3.amazonaws.com/0/5330/25cd0e7d-cbc8-0e18-adad-b00b40cfee99.png "sound_levelshift_5v.png")

10uFのコンデンサは直流をカットして交流成分だけ流す用途、だと思う…。(違ったら教えてください…。)

なお交流からの入力が何も無い場合、2.5Vを期待する(analogRead()で512になってほしい)が、実際は抵抗分圧時の誤差があるので、少しずれる(カーボン抵抗の5%誤差から、金属皮膜抵抗の0.5%や1%誤差だとだいぶ緩和する)ので、中央値を512から変える必要が出てくる。

```
#define LEFT A2
#define SAMPLE 100
#define CENTER 512

void setup() {
  Serial.begin(9600);
  pinMode(LEFT, INPUT);
}

void loop() {
  Serial.println(getLevel(LEFT));
}

uint8_t getLevel(uint8_t pin) {
  float avg = 0;
  float raw;
  for (int sample = 0; sample < SAMPLE; sample++) {
    raw = analogRead(pin);
    avg += abs(raw - CENTER);
  }
  return avg/SAMPLE;
}
```

## OPアンプを使う

非反転アンプ単電源用 交流結合型の方法で、+2.5Vに増幅の基準電圧をあわせることにより、0~5V の値として取得できる。ググっても出てくるけど、書籍 回路の素101 の項目007の「非反転アンプ単電源用 交流結合型」が大変解りやすかった。

- http://shop.cqpub.co.jp/hanbai/books/45/45301.html

その回路のオペアンプを LM358N に変えて 1000Hz のサイン波のオーディオ入力を2倍の出力に増幅してみた。最初C1(0.1u)を入れずに全く出力が出なくてうーん、と思ったんだけど、コンデンサを入れて直流成分のカットが必要なことが解った。(直流成分のカットが具体的に何かはまだ解っていない…)

![zouhuku.png](https://qiita-image-store.s3.amazonaws.com/0/5330/2fb1c6d2-9505-1b0f-e8aa-84a738cb2643.png)

# まとめ

音量がコンスタントに大きいなら、単相短波なら1ショットキーと1抵抗の整流でも割とそれっぽい値がとれる。そうじゃないならダイオードによる電圧降下が馬鹿にならないので、レベルシフトなりオペアンプを使った方が良さそう。