Pythonでキーボードをピアノの鍵盤と想定して音を鳴らす

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212705/bc06972d-8c54-e838-1a94-cd445f197d94.jpeg" alt="ピアノの鍵盤" title="ピアノの鍵盤" width=50%>

<span style="font-size: 120%;"><b>やっほー!　こんにちはー!</b></span>
物理好きのオザキ([@sena0801masato](https://twitter.com/sena0801masato))です。

今回は、キーボードをピアノの鍵盤だと想定して、Pythonを使ってパソコンから音を鳴らしてみたいと思います。
思いついたのは、アニメ『四月は君の嘘』を見て感動した時でした笑
音楽は全然詳しくないのでむしろ教えてください。

### この記事の対象者
- パソコンから音階を鳴らしてみたい!
- 標準モジュールだけ使いたい
- 音源をダウンロードしたくない
- Windowsを使っている

## 今後改善したいこと
- ビープ音ではなく、ダウンロードした音源を使えるようにしたい
- 和音を出力したい
- 長押しで単一の音を出し続けたい

## 音階について
音階というと、ドレミファソラシを思い浮かべると思います。
この7音に加えて黒鍵の5音を加えた計12音で周回し、1オクターブ上のドからまたスタートします。

音階の基準となる周波数はラの440Hzとされており、１オクターブ上がると周波数は2倍になります。
そのため、12半音で2倍より1半音で周波数は$2^{1/12}$倍されます。

今回は基準のラ(440Hz)が含まれるオクターブを4段階目とし、音のことをpitchとして変数名を定義しました。

|音|ドイツ語の音階表記|
|:-:|:-:|
|ド|C4|
|ド#|Cis4|
|レ|D4|
|レ#|Dis4|
|ミ|E4|
|ファ|F4|
|ファ#|Fis4|
|ソ|G4|
|ソ#|Gis4|
|ラ(440Hz)|A4|
|ラ#|Ais4|
|シ|H4|
|高いド|C5|

## 苦労した点
キーボードの入力をどのようにプログラムが受け取るかに悩みました。毎回音程を確定させるためにエンターを押すのもピアノっぽくないので、inputは使えず、最終的にgetchを使いました。
getchは打鍵を読み取り、読み出された文字を返しますが、コンソールには何もエコーバックされず、Enterを押す必要もないというまさにピッタリの標準ライブラリがありました。
(これに行きつくまでに時間がかかりました...　普通ならPygameなどを使うみたいです。)

## 完成したコード

```python:digital_piano_ver01.py
from winsound import Beep
from msvcrt import getch

# 半音のズレが何倍か定義
onestep_pitch = 2 ** (1.0/12.0)
# 音を鳴らす時間をミリ秒で定義
duration = 300

# 音を鳴らす関数を定義
def play_pitch(frequency, duration):
    Beep(frequency, duration)
# 半音上げ下げする関数を定義
def down_pitch(base_pitch):
    return int(round(base_pitch / onestep_pitch))
def up_pitch(base_pitch):
    return int(round(base_pitch * onestep_pitch))

# 各音程の周波数を定義
A4 = 440
Ais4 = up_pitch(A4)
H4 = up_pitch(Ais4)
C5 = up_pitch(H4)
Cis5 = up_pitch(C5)
D5 = up_pitch(Cis5)
Dis5 = up_pitch(D5)
E5 = up_pitch(Dis5)

Gis4 = down_pitch(A4)
G4 = down_pitch(Gis4)
Fis4 = down_pitch(G4)
F4 = down_pitch(Fis4)
E4 = down_pitch(F4)
Dis4 = down_pitch(E4)
D4 = down_pitch(Dis4)
Cis4 = down_pitch(D4)
C4 = down_pitch(Cis4)
H3 = down_pitch(C4)
Ais3 = down_pitch(H3)
A3 = down_pitch(Ais3)

# キーボードと音程を関連づける。キーボードの"d"がC4、つまりドの音など
pitchs = {}
pitchs["a"] = A3
pitchs["w"] = Ais3
pitchs["s"] = H3
pitchs["d"] = C4
pitchs["r"] = Cis4
pitchs["f"] = D4
pitchs["t"] = Dis4
pitchs["g"] = E4
pitchs["h"] = F4
pitchs["u"] = Fis4
pitchs["j"] = G4
pitchs["i"] = Gis4
pitchs["k"] = A4
pitchs["o"] = Ais4
pitchs["l"] = H4
pitchs[";"] = C5
pitchs["@"] = Cis5
pitchs[":"] = D5
pitchs["["] = Dis5
pitchs["]"] = E5

while True:
    # 入力されたキーを認識する
    bytes_keyboard = getch()
    # バイト列から文字列に変換する
    str_keyboard = bytes_keyboard.decode("utf-8")
    # 文字列を小文字に揃える
    pitch = str_keyboard.lower()
    print("音階", pitch)
    # 押したキーが辞書の中に存在するとき、音を鳴らす
    if pitch in pitchs:
        play_pitch(pitchs[pitch], duration)
    # 終了させるときはqを押す
    elif pitch == 'q':
        break
```

## まとめ
とりあえず、ピアノっぽいものはできました。でも改善の余地はまだまだあります。とても気になっているのが和音が出せないこと、長押ししても定義したミリ秒で音が切れてしまうことです。
この2つは非同期処理を使ったらうまくいけないかなぁと思っているので、誰かできたら教えてください笑。
他のアイデアとしては、音を波形から生成したら、和音と長押しして減衰する音が表現できるのではないかと考えています。
実際に売られている電子ピアノってすごいなと思うばかりでした笑

<span style="font-size: 120%;"><b>ではでは!</b></span>

## 参考文献
[winsound --- Windows 用の音声再生インタフェース]
(https://docs.python.org/ja/3/library/winsound.html)
[msvcrt --- MS VC++実行時システムの有用なルーチン群](https://docs.python.org/ja/3/library/msvcrt.html)
[Pythonでキーイベントを取得したかったけどできなかった話←できました。](https://www.tohuandkonsome.site/entry/2017/05/22/002531)
[Tkinter でGUI音遊びソフト作り(4) - Python で音を出す]
(https://takasa-5.blogspot.com/2017/04/tkinter-gui4.html)