ハンズフリーカラオケの土台をつくる

## ハンズフリーでカラオケがしたい
料理をしているとき、家で仕事をしているとき、車に乗っているとき、手は離せないけど歌いたい、そんなときのためのハンズフリーカラオケをつくってみたい！
今回は要素技術に着目してプロトタイピングをしてみます。

## どんな機能があれば良いか、要素を整理してみる

このような流れを想定しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/aca8a0de-028b-c1bf-e286-f43022d86ca7.png" width=80%>

音声で「カラオケをしたい」と言ったら曲が流れ、マイクで音声を拾い、歌唱力評価の演算をする、という工程です。
1. **音声でカラオケをスタートし曲を流す**
1. **歌声を録音する**
1. **録音した歌を解析し評価する**

## 1.音声でカラオケをスタートし曲を流す
voiceflowで作成します。

https://www.voiceflow.com/

シンプルに「起動→曲再生→終了」という手順で進みます。
途中でどのような曲を歌うのか、もう一度歌うのかなどの会話も入れられます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/986141/bb78e3e1-29ee-d21a-ae54-54140504c490.png" width=80%>

これをAlexaスキルとしてデプロイします。こちらでアカウントをつくり、Voiceflowからデプロイすれば良いです。

https://developer.amazon.com/en-US/alexa


## 2.歌声を録音する
webアプリで作成しました。ボタンを押すとレコーディングをスタートし、ストップを押すとレコーディングを終了、その後音声ファイルをつくります。1.の工程と繋げると、開始は音声で操作、終了は曲が終わったタイミングとなります。

`navigator.mediaDevices`で`audio: True`にすることでweb上で音声の記録が可能です。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="mdomxBE" data-user="yutowac" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/yutowac/pen/mdomxBE">
  testaudio</a> by yuto (<a href="https://codepen.io/yutowac">@yutowac</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

<details><summary>実装例</summary>
```html
<div>
  <button type="button" id="buttonStart">Start</button>
  <button type="button" id="buttonStop" disabled>Stop</button>
</div>
<div>
  <audio controls id="player"></audio>
</div>
```

```js
main()

async function main () {
  const buttonStart = document.querySelector('#buttonStart')
  const buttonStop = document.querySelector('#buttonStop')
  const player = document.querySelector('#player')

  const stream = await navigator.mediaDevices.getUserMedia({
    video: false,
    audio: true,
  })

  if (!MediaRecorder.isTypeSupported('audio/webm')) {
    console.warn('audio/webm is not supported')
  }

  const mediaRecorder = new MediaRecorder(stream, {
    mimeType: 'audio/webm',
  })

  buttonStart.addEventListener('click', () => {
    mediaRecorder.start()
    buttonStart.setAttribute('disabled', '')
    buttonStop.removeAttribute('disabled')
  })

  buttonStop.addEventListener('click', () => {
    mediaRecorder.stop()
    buttonStart.removeAttribute('disabled')
    buttonStop.setAttribute('disabled', '')
  })

  mediaRecorder.addEventListener('dataavailable', event => {
    player.src = URL.createObjectURL(event.data)
  })
}
```
</details>

## 3.録音した歌を解析し評価する
録音した歌の評価は、今回は感情分析で代用します。
使用したのはこちらの「AssemblyAI」です。
音声データからテキストをテキストを生成する、感情判定をするなどのメソッドがあります。利用するにはSign Inが必要です。

https://www.assemblyai.com/

こちらのサービスでは、感情は声の抑揚ではなく言葉の内容から決定しているのではと思います。声の調子から感情を推定するAPIはこちらの「Empath」がありますが、APIの利用までに少し手間がかかるので今回はAssemblyAIを使っています。

https://webempath.net/lp-jpn/

Pythonで実装しました。
感情は以下のように出力されます。Positive、Negative、Neutralの3パターンであらわされます。
```
SentimentType.positive
0.4788665473461151
```

<details><summary>実装例</summary>

```py
import assemblyai as aai

aai.settings.api_key = "API_KEY"

audio_url = "https://storage.googleapis.com/aai-web-samples/news.mp4"
config = aai.TranscriptionConfig(sentiment_analysis=True)
transcript = aai.Transcriber().transcribe(audio_url, config)

for sentiment_result in transcript.sentiment_analysis:
    print(sentiment_result.text)
    print(sentiment_result.sentiment)  # POSITIVE, NEUTRAL, or NEGATIVE
    print(sentiment_result.confidence)
    print(f"Timestamp: {sentiment_result.start} - {sentiment_result.end}")

```
</details>

## 個々の技術を試してみて
個々に深堀りする点がありました。
### 1.ハンズフリーで音楽再生
再生する音源をどう手に入れるかで悩んでいます。

### 2.音声録音
声での操作や音声をどこで切るかということを設定する必要があります。1曲分の記録だと次工程の解析で時間がかかってしまいます。

### 3.歌の解析
どのような解析をするか、APIやライブラリを探してみる必要があります。せっかくなので、単純に声がでかいと良い、などの独自の評価基準を設けるのも良いと思いました。