プログラミング習得への道のり #6 音の長さを変える ~PCキーボードで演奏したい~
プログラミング完全初心者の私が、プログラミングを習得するまでの全過程(主に悩み…)を記事にしていきます。
プログラミング学習にあたり、以下の順で進めています。
①アウトプットを決める、②基礎を学習する、③アウトプットを作り始める
アウトプットは PCキーボードで演奏できるアプリケーション に決めました。
前回、ステップ1. キーボード入力で音を鳴らすのうち、Pythonで”キープレスに応じて音階を鳴らす”ことができました。一方、現状はプログラム内で再生時間を指定しており、音の長さを自由に変えられません。
そこで本記事では、 ステップ1:キーボード入力で音を鳴らす のうち、“音の長さを自由に変える”にチャレンジしました。
PCキーボード演奏アプリの作成ステップ
- キーボード入力で音を鳴らす
☑1−1.1つの音を鳴らす
☑1-2.音階を鳴らす
1-3.音の長さを自由に変える
1-4.テンポを決める - 音を制御する
2-1.波形を変える
2-2.フィルタリング機能を追加する
2-3.複数の音を同時に鳴らす - 外部から音を取り込む
3-1.単音の音源サンプルから音を取り込む(1楽器)
3-2.音源から楽器を取り出す - 機能を追加する
4-1.録音&再生する(パターンをつくる)
4-2.外部と連携する - 曲を演奏する
- 曲を作る
☑︎本記事の内容
- 必要な材料:音の長さを変える
- レシピ①:pygame.time.get_ticks()メソッドを使用する
- レシピ②:pygame.key.get_pressed()メソッドを使用する
- レシピ③:辞書であらかじめ音の長さを定義する
- まとめと次回予告
☑︎著者の経験
この記事を書いている私は、非情報系の大学院を卒業後、通信関係の企業に3年勤めた後、現在までコンサルティング会社に勤めています。
学生時代〜現職までプログラミングとは縁がなく、前職でルーターの設定を少しかじった程度のプログラミング完全初心者です。
こういった私が、解説していきます。
Twitterもはじめました。
目次
必要な材料:音の長さを変える
○ライブラリ
今回使用するライブラリは以下です。
使用したライブラリ
- numpy:数値演算用ライブラリ → sin波を作る
- PyAudio:オーディオ関連ライブラリ → 音を鳴らす
- pygame:ゲーム関連ライブラリ → キープレスを検出する
主なメソッド
- pygame.event.get() → イベントキューからイベントを取得する
- pygame.key.name() → キーの名前を取得する
- pygame.time.get_ticks()(New) → 時間をミリ秒単位で取得する(レシピ①)
- pygame.key.get_pressed()(New) → キーボードボタンの状態を取得する(レシピ②)
使用したライブラリは前回同様です。
前回までにpygameの機能を使ってキープレスを検出し、キーによって波長を変えることに成功しました。そしてnumpyでsin波をつくり、PyAudioでPCから音データを再生できました。
今回は、前回に続き出力される音の長さを変えるためにpygameのメソッドを色々と試してみました。
○新たに加えるPython基礎要素
- input関数 → BPMを指定(レシピ③)
- 辞書:DURATION → 音の長さを指定(レシピ③)
レシピ①:pygame.time.get_ticks()メソッドを使用する
前回のコードをベースに、音の長さを変える方法を考えました。
最初に思いついたのは、キーを押した瞬間の時間と話した瞬間の時間を取得し、その差分を出力時間にする方法です。このキーを押すあるいは離す時間の取得はpygameのメソッドにありました。
- pygame.event.get() → イベントキューからイベントを取得する
ちなみに参考にしたサイトは以下です。
(参考)Pygame – pygame.time – pygameの時間はミリ秒(1/1000秒 …
これをもとのコードと組み合わせて
if event.type == KEYDOWN: # キーを押したとき
if event.key == K_a: # そのキーが”a”のとき
start_time = pygame.time.get_ticks() / 1000
とすることで、キーを押したときの時間を取得できると考えました。
更に
if event.type == KEYUP: # キーを離したとき
if event.key == K_a: # そのキーが”a”のとき
return_time = pygame.time.get_ticks() / 1000
とすることで、キーを離したときの時間を習得することができると考えました。
そして
press_time = return_time – start_time
とすることで、 キーを押している間の時間を取得 できるはずです。
試しに、「入力されたキー」、「キーを押した時間」、「キーを離した時間」、「キーを押している間の時間」をprintするコードを書いてみました。
サンプルコード
実行結果
それぞれ正しく取得できましたので、これを用いて音を鳴らしてみます。
まずは実験なので、音は”ド”だけでコードを書きました。
Warning画面
ちなみに、実行はできましたが上記のWarning画面が出ました。この意味はわかりませんのでわかったら加筆します!
○結果
以下のコードでキーを押している時間分、音が鳴りました。
○問題点
やってみて気づきましたが、play()関数の引数durationにpress_timeを渡しています。これが
t = np.arange(0, duration * fs) / fs
に渡されるわけです。
何が言いたいかというと、 キーを離してはじめて再生時間が決定し、sin波が作られます。
よって、押している時間分の音は鳴りましたが、キーを離してから音が鳴りはじめてしまうため演奏には使えません。。。
レシピ②:pygame.key.get_pressed()メソッドを使用する
押している間だけ処理が進む方法がないか考えてみました。
Pygameはもともとゲームを作る際に使用されるライブラリなので、例えば”矢印キーを入力している間ある物体を動かす”ような処理は容易のはずだと考えました。いつもの如く調べた結果、pygame.key.get_pressed()メソッドというものを使用しているようです。
参考にしたサイトは以下です。
(参考)【Pygame】キーイベントでキャラクター移動(長押し対応版)https://algorithm.joho.info › プログラミング › Python
(参考)Pygame – pygame.key – このモジュールには、キーボードを …
サンプルコード
これを用いてコードを書いてみました。
○結果
キーを長押しても音はなりませんでした。。。
失敗コード
○エラー
OverflowError: size does not fit in an int
○問題点
上記エラーの内容もわかりませんが、確かにキーを押す時間に沿ってsin波を時間軸方向に足していくというのは簡単な話じゃない気がします。。。
# キープレスに応じて音を変える
while True:
screen.fill((0, 0, 0))
pressed_key = pygame.key.get_pressed()
if pressed_key[K_a]:
duration += 1
なので、これをやろうとすると、今の波の式を用いて①波を作る→②渡す→③再生するの手順で音を鳴らすことは難しく、波の作り方から見直しが必要と考えられます。
\begin{eqnarray}
\left\{
\begin{array}{l}
\sin (t) = A\sin (2{\pi}f_{ 0 }t) \\
t=n/fs
\end{array}
\right.
\end{eqnarray}
レシピ③:辞書であらかじめ音の長さを定義する
本記事の目的は音の長さを変えるということで、キーを押している間音を鳴らそうとしたところうまくいきませんでした。
次のステップとして2案あると考えます。
①波の作り方を見直す
②押している間音を鳴らすという考えを見直す
①は、今の知識ではできそうにありません。
また、改めてPCキーボード演奏アプリを作るモチベーションに立ち返ってみると
- 演奏できない人でも簡単に演奏できたらなぁ
- 新しいスタイルで演奏できたらカッコいいなぁ
- 好きにアレンジしたり作曲できたら尚更よいなぁ
でした。
“カタカタ仕事しているようで実は演奏している”という新しいスタイルで演奏する方が新しいし面白いなと思いました。
そもそも普段PCを使うとき、キーを長押しすることってあまりないのでは?とも思いました。(PCゲームはやっていないので分かりませんが)。
よって、②側で進めようという考えに至りました。ではどうするかというと、楽器の要素を残しつつカタカタ演奏するため、あるキーを押すと全音符〜四分音符の間で長さが変わるようにするのはどうかと考えました。
イメージとして、日本語を打つとき例えば「は」と打つには「h」のあとに「a」と打ちますが、その要領で「h」を打つと全音符の長さになり、そのあと「a」を押すとドが全音符分鳴る、みたいなことです。
キーボード配置
○結果
以下のコードで音の長さを変えることができました。
作成したコード
以下、メモです。
音の長さdurationをピアノのように全音符〜8分音符で表し、辞書型DURATIONで定義しました。
このとき、音の長さはBPMで決まるため、input()関数でBPMを先に定義するようにしました。
# パラメータ
bpm = int(input('BPMを入力してください : '))
DURATION = {
'L1': (60 / bpm * 4), # 全音符
'L2': (60 / bpm * 4) / 2, # 二分音符
'L4': (60 / bpm * 4) / 4, # 四分音符
'L8': (60 / bpm * 4) / 8, # 八分音符
前回の記事で、while分の”event.type == KEYDOWN”とすることでキープレスを検出し、event.keyで任意のキーを指定できるとお伝えしました。これを用いて、音の長さdurationをキーによって変えるよにしました。
# キープレスに応じて音を変える
while True:
…
if event.type == KEYDOWN: # キーを押したとき
…
# キーに応じて音の長さ変化
elif event.key == K_j:
duration = DURATION['L8']
elif event.key == K_k:
duration = DURATION['L4']
elif event.key == K_l:
duration = DURATION['L2']
elif event.key == K_SEMICOLON:
duration = DURATION['L1']
…
○エラーと解決方法
プログラム実行後、最初に”a”を押すと以下エラーが出てきました。
NameError: name ‘duration’ is not defined
エラー画面
これは、while文のなかで”周波数が決まったときにsin波をつくる”ようコードが書かれているため、音の長さを決めるキーを押すより前に、周波数を決めるキーを押してしまうとsin波が作られずエラーが起こるとわかりました。
したがって、以下のように初期値を先に定義することで解消しました。
# 初期値
duration = DURATION['L4']
まとめ&次回予告
○まとめ
PCのキープレスの種類に応じて音を長さを変えることに成功しました(レシピ③)。
一方、キープレスの長さに応じて音の長さを変えることには失敗しました(レシピ①、②)。
使用したライブラリ(レシピ③)
- numpy
- PyAudio
- pygame
使用したメソッド(レシピ③)
- pygame.event.get() → イベントキューからイベントを取得する
- pygame.key.name() → キーの名前を取得する
最後に、作ったコードで遊んでみました。
一応演奏はできましたが、各音の長さを決めるテンポが分かりづらいと感じましたので、
メトロノームのような機能が必要かなと思いました。
名探偵コナンのOPを演奏してみた
○今日のエラーと解決方法
エラー
- NameError: name ‘duration’ is not defined
解決方法
- 初期値を設定することでwhile文内で起こるエラーを解消しました。
○次回予告
メトロノームのような機能を追加し、テンポが分かるようにしたいと思います。
- キーボード入力で音を鳴らす
☑1−1.1つの音を鳴らす
☑1-2.音階を鳴らす
☑1-3.音の長さを自由に変える
1-4. テンポを決める
lteru
最新記事 by lteru (全て見る)
- 【ノーコード・ローコード】OutSystemsでアプリづくり#04 ~マッチングアプリをつくる①~ - 2022年8月13日
- 【ノーコード・ローコード】OutSystemsでアプリづくり#03 ~簡単なリスト型アプリをつくる③~ - 2022年7月24日
- 【ノーコード・ローコード】OutSystemsでアプリづくり#02 ~簡単なリスト型アプリをつくる②~ - 2022年7月22日