音の再生をピッチで制御する、カセットテープのようなもの

# はじめに（どうでも良い話なのですが…）

これは2021年のアドベントカレンダーの記事です。今年は色々と新しい趣味に出会いましたが、**何よりもカセットテープです。**新作インディーズのカセットテープを何十個も買い求め散財してしまいました。

ふと、**カセットテーププレイヤ**ーをUnityで作りたい衝動に駆られ、荒いですが、作ってみました。（録音は使わないので、実装していません）

https://twitter.com/gift10industry/status/1473702865000890369

この記事ではソースコードを載せませんので、ご興味ある方はgithubを覗いてみてください。そんな大した内容ではないですが**CassetteTapeというフォルダ**に一式あります。

https://github.com/TakashiHamada/advent_calendar_2021/tree/main/Assets/CassetteTape

# ボタンの配列と挙動

まずは自宅のカセットテーププレイヤーを調べます。ボタンの配列を調べたり、同時押ししたりして挙動を確認します。ちなみに現行で買えるカセットテーププレイヤーはかなり限られています。海外のノーブランド商品を何台か試しましたが、再生速度が不安定だったり、電池駆動のときに音が変になったりで、最終的にパナソニック製の**「RX-FS27」**というものに落ち着きました。本当は７０年代のレトロ・カッコいいのがほしかったのですが、高すぎて諦めました。
![Screen Shot 2021-12-23 at 2.29.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95460/37ed74e4-939e-c21d-8cb6-37fb4aad2b3b.png)
**「RX-FS27」**のボタンの配列は、**左から順番**に以下のようになります。

- 一時停止
- 停止/取り出し
- 早送り/頭出し
- 巻き戻し/くり返し
- 再生
- 録音

調べた限りでは、配列はどのプレイヤーも似ているものの若干の差異があるようです。
下の写真は「国産初のカセットテープデッキ」と紹介されている１９６８年の**「A-20」**（カッコいい…）というプレイヤーですが、一時停止、録音ボタンの配列が異なるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95460/9686267b-1d5e-9139-6ee3-8b97ff4e48bc.png)
参照元：https://teac.jp/jp/magazines/detail/205

ややこしいことに**ボタンによって押した後の挙動が異なります。**
例えば**「再生」**は、押したら、ボタンが下がったままで、もう一度押せません。
一方で**「一時停止」**は下がった状態でもう一度押すと、初期位置に戻ります。

下の表は、ボタンの機能と、押した後の挙動をまとめたものです。
**「早送り/頭出し」**は停止中**「早送り」** 再生中は**「頭出し」**になります。
**「巻き戻し/くり返し」**は停止中**「巻き戻し」** 再生中は**「くり返し」**になります。

|ボタン名  |押した後の挙動  |初期位置に戻せるか？  |
|---|---|---|
|一時停止  |押されたまま  |v  |
|停止/取り出し  |すぐに戻る  |v  |
|早送り  |押されたまま  |X  |
|頭出し  |すぐに戻る  |v  |
|巻き戻し  |押されたまま  |X  |
|くり返し  |すぐに戻る  |v  |
|再生  |押されたまま  |X  |

**「押されたまま」**で初期位置に戻せないボタンは、**「停止/取り出し」**を押すと初期位置に戻ります。その他、見つけたボタンの挙動は以下の通りです。

- **停止中に**「停止/取り出し」を押すと、テープを取り出せる。
- 「早送り/頭出し」と「巻き戻し/くり返し」は**どちらかしか、**押されたままにならない。
- 「停止/取り出し」を押すと、押されたままのボタンが初位置に戻る。
- ただし、一時停止ボタンは、**他のボタンの影響を一切受けない。**

その他、全ボタンを同時押したり、排他になっているボタンを無理やり押したりしていましたが、壊れそうな予感がしたので、ほどほどで引き上げました。webやゲームでボタンを作っていると、不必要なボタンはグレーアウトして触れなくしてしまえばいいや！みたいな考えでしたが、物理的なものは、**存在を消したり当たり判定を無くしたりできないので大変だな、**と思いました。

最初は、ボタンを含めて状態遷移図を書こうとしていましたが、だいぶ複雑になったので、**ボタン関連のメカ的な挙動と、プレイヤーのロジックを分けて**設計することにしました。
こちらの図は、**カセットテープを入れる前の**プレイヤーをエミュレートしたものです。（実際のプレイヤーもカセットテープが入っていなくてもボタンを押せます）停止ボタンを押すと、押したままのボタンが初期位置に戻る様子などを確認できると思います。
![Screen Recording 2021-12-23 at 3.30.43.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95460/a167c5d7-14b2-670d-2e29-a86cb9394c91.gif)
このボタンの様子をプレイヤーのスクリプトが監視して、どのように振る舞うのか決めます。

# 音の再生をピッチで制御する

AudioSouceのpitch（ピッチ）を操作することで、もろもろ制御します。
**0にすることで一時停止、マイナスの値を入れることで逆再生を実現しています。**

```c#
GetComponent<AudioSource>().pitch =   1f; // 再生
GetComponent<AudioSource>().pitch =   0f; // 一時停止
GetComponent<AudioSource>().pitch =  20f; // 早送り
GetComponent<AudioSource>().pitch =   4f; // 頭出し
GetComponent<AudioSource>().pitch = -20f; // 巻き戻し
GetComponent<AudioSource>().pitch =  -4f; // くり返し
```

すべての再生操作をpitchで行っているため、**Stop()**や**Pause()**は使っていません。

注意点ですが、最初に**Play()**しないとピッチの変化は確認できません。
あと、clipの一番最後や、最初に到達すると、**自動的に再生が止まります。**
楽曲の冒頭で「巻き戻し」しても、すぐに再生が停止してしまいます。

# 特定の秒数から始める

すぐに逆再生をしたい場合は、**楽曲の一番最後に移動してから、ピッチをマイナスにしましょう。**

```c#
var audioSource = GetComponent<AudioSource>();
audioSource.time = audioSource.clip.length - 0.001f; // ※ 1
audioSource.pitch = -1f;
audioSource.Play(); // ※ 2
```
※ 1
0.001fを引いている変なところがありますが、これは楽曲の長さぴったりの時間を指定するとエラーになるためです。不思議です。

※ 2
timeを指定した場合、Play()しないと、その値が有効にならないようです。

# 今後の課題
テープのA面、B面の切り替えも表現してみたいです。