PythonとPyAudioを使ってsin波を作成して音を鳴らす方法

## 説明

この記事では、PythonとPyAudioライブラリを使用して、
sin波（正弦波）を生成し、音を鳴らす方法を紹介します。
（なお、筆者は物理がかなり苦手なので、もし理解に間違いがあればご指摘いただけますと幸いです。）


## 動作環境
- Windows 11（Windows Powershell）
- Poetry: 1.8.2
- python: 3.10
- numpy: 1.26.4
- PyAudio: 0.2.14


## Poetryについて
PoetryはPython で依存関係を管理およびパッケージ化するためのツールです。
Poetryを活用することで、作業ディレクトリ以外の環境を汚さずにパッケージをインストールできます。

[こちらの公式サイト](https://python-poetry.org/docs/#:~:text=Poetry%20is%20a%20tool%20for,build%20your%20project%20for%20distribution.)からインストールできます


今回は本筋ではないので、インストール方法や基本的な使い方の説明は割愛しますが、
[Poetryをサクッと使い始めてみる](https://qiita.com/ksato9700/items/b893cf1db83605898d8a)などは大変参考になりました。

今回は、[Poetryをサクッと使い始めてみる](https://qiita.com/ksato9700/items/b893cf1db83605898d8a)の1~3章目までのステップにより、
作成されたpoetryのディレクトリ上で行います。

poetryがなくても、pip installで代替できると思います


## 必要なライブラリのインストール
- PythonとPyAudioを使って音を鳴らすためには、numpyとPyAudioライブラリが必要です

- PyAudioは、Pythonで音声データの再生や録音を行うためのクロスプラットフォームライブラリです

- numpy, PyAudioのインストールは、Windows Powershell上で、以下のコマンドで行えます

```
poetry add numpy=1.26.4 PyAudio=0.2.14
```
あるいは、
```
pip install numpy=1.26.4 PyAudio=0.2.14
```
でもいけるかもしれません（試してません）

## sin波の基本
#### 説明
sin波、または正弦波は、音の基本的な波形の一つです。
sin波の生成には、振幅（A）、周波数（f）、および時刻（t）の3つの要素が重要です。

- 振幅（A）：音の大きさを決定します。値が大きいほど音は大きくなります
- 周波数（f）：音の高さを決定します。値が大きいほど音は高くなります。
- 時刻（t）：音を生成する時刻を表します

#### sin波の計算式
sin波は, 今回は以下で計算しています。
ページ下部で補足説明を入れているので、気になる方は読んでみて下さい。
```
y(t)=Asin(2πft)
```

## Pythonでsin波を生成して音を鳴らす
以下のコードは、PythonとPyAudioを使ってsin波を生成し、音を鳴らすことができます。

```
import numpy as np
import pyaudio

def sound_sin(A, f, t):
    return A * np.sin(2 * np.pi * f * t)

A = 0.5
f = 440
play_length = 5
fs = 44100

p = pyaudio.PyAudio()

stream = p.open(format=pyaudio.paFloat32,
                channels=1,
                rate=fs,
                output=True)

samples = np.arange(fs * play_length) / fs
y = sound_sin(A, f, samples)

stream.write(y.astype(np.float32).tobytes())
stream.stop_stream()
stream.close()
p.terminate()
```

## コードの説明

#### 変数の説明
- fsはサンプリングレートです。サンプリングレートは、1秒間を何個の点に分割するかを決めます
- play_lengthは音の秒数です。fsと掛けることで、例えば5秒間について、分割点が求められます
- samplesは、時刻tの配列を求めています。fsで割っているのは、分割点を時刻に直すイメージです

（例）
例えばfs=10の場合、1秒間を10個の点に分解します。
この時、時刻tの配列は[0, 1, 2, ..., 9]となります。
samplesは、配列をfsの値で割るので、samples=[0, 0.1, 0.2, ..., 0.9]となります。
配列の各要素が時刻に該当するイメージです。



#### 処理の説明
- sound_sin関数によって、時刻tに対してsin波y(t)を返します。

- 今回は処理効率化のため、時刻tごとに処理を行なうのではなく、
時刻配列samplesに対して一括でsin波に変換する処理を行なっています。
numpyは便利ですね。


## 動作確認
Windows Powershell上で動かしています。無事に動くと「ラ」の音がなると思います。
（ファイル名は適宜ご自身の環境に合わせてください）

```
poetry run python ファイル名.py
```
poetryを入れていない方は、
```
python ファイル名.py
```
でも動くかなと思います（試してません）


## sin波の計算式の補足説明（読まなくても大丈夫です）
[wikipedia-正弦波](https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%BC%A6%E6%B3%A2)によると、正弦波は以下の計算式で表されるようです。
```
y=A・sin(ωt-φ)
```

今回のプログラムで出てこなかった、ω、φについての補足メモです。

#### ω
各周波数と呼ばれる「ω」は、以下のように2πfに変換できるようです。
なのでプログラム上は問題なさそうですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310536/1a14baca-3dc6-8886-3b3d-9f6f9d5cd719.png)
（引用元：[wikipedia-角周波数](https://ja.wikipedia.org/wiki/%E8%A7%92%E5%91%A8%E6%B3%A2%E6%95%B0)）

#### φ
~~こちらは「位相」と呼ばれるパラメータのようです。~~
こちらは「**初期位相**」と呼ばれるパラメータのようです。(2024/03/21修正)
sin波が始まったときに、yの値が0からどれくらいずれているかを表すようです。

正直、今回調査した範囲では正確に理解しきれませんでしたが、
今回のsin波はy=0からスタートすることを前提としており、ずれはなく、
φ=0となるため、いったんは現在のプログラムでも問題ないのではないか考えています。

追加調査は今後行いたいと思います。


## まとめ
この記事では、PythonとPyAudioを使用してsin波を生成し、音を鳴らす方法について説明しました。
sin波を鳴らすことで、これまで苦手だった物理を勉強しなおすきっかけになりました。

修正・コメントなどございましたら、お気軽にご連絡ください。

## 今後書きたい記事
- 生成した音を繰り返し再生すると、「ぶつぶつ」というノイズが発生することへの対処
- 各周波数、位相についての追加調査結果

## 参考にさせて頂いた記事
- Poetryをサクッと使い始めてみる：https://qiita.com/ksato9700/items/b893cf1db83605898d8a
- wikipedia-正弦波：https://ja.wikipedia.org/wiki/%E6%AD%A3%E5%BC%A6%E6%B3%A2
- wikipedia-角周波数：https://ja.wikipedia.org/wiki/%E8%A7%92%E5%91%A8%E6%B3%A2%E6%95%B0
- 位相（位相差・同位相・逆位相）：https://manabitimes.jp/physics/1824