プログラミング習得への道のり #5 キープレスで音を変える ~PCキーボードで演奏したい~
プログラミング完全初心者の私が、プログラミングを習得するまでの全過程(主に悩み…)を記事にしていきます。
プログラミング学習にあたり、以下の順で進めています。
①アウトプットを決める、②基礎を学習する、③アウトプットを作り始める
アウトプットは PCキーボードで演奏できるアプリケーション に決めました。
前回の記事から”③アウトプットを作り始める”をはじめ、ステップ1. キーボード入力で音を鳴らすのうち、Pythonで”1つの音(ド)を鳴らす”ことができました。
本記事では、 ステップ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.外部と連携する - 曲を演奏する
- 曲を作る
☑︎本記事の内容
- 必要な材料:キープレスに応じて音階を鳴らす
- レシピ①:input関数で音階をで鳴らす
- レシピ②:pygameでキープレスを検出する
- まとめと次回予告
☑︎著者の経験
この記事を書いている私は、非情報系の大学院を卒業後、通信関係の企業に3年勤めた後、現在までコンサルティング会社に勤めています。
学生時代〜現職までプログラミングとは縁がなく、前職でルーターの設定を少しかじった程度のプログラミング完全初心者です。
こういった私が、解説していきます。
Twitterもはじめました。
必要な材料:キープレスに応じて音階を鳴らす
○ライブラリ
今回使用するライブラリは以下です。
音階を鳴らすために必要なライブラリ
- numpy:数値演算用ライブラリ → sin波を作る
- PyAudio:オーディオ関連ライブラリ → 音を鳴らす
- pygame:ゲーム関連ライブラリ(New) → キープレスを検出する(レシピ②)
前回、numpyでsin波をつくり、PyAudioでPCから音データを再生することに成功しました。
今回は、前回に続きキープレスに応じて音を変えるためにpygameの機能を使います。
※pygameは前回、音を鳴らすライブラリとして紹介しましたが、今回はキープレス検出に使います。
○新たに加えるPython基礎要素
- input関数 → キープレスを取得(レシピ①)
- リスト → 周波数を変えるための周波数リスト(ド、レ、ミ、、、)(レシピ①)
- 辞書 → リストに名称を対応させたもの(ド:261.636、、、)(レシピ②)
レシピ①:input関数で音階をで鳴らす
まずは、前回のコードをベースに、キープレスごとに音を変える方法を考えました。
そこで使用したのがinput関数です。play関数内にinput()を配置することでplay関数実行時にキーを入力するようにします。
また、リストを用いて入力キーが”c”のとき周波数freqが”ド”、入力キーが”d”のとき周波数freqが”レ”、入力キーがそれ以外のとき周波数freqが”ミ”になりうようにしました。
○結果
以下のコードで入力するキーによって出力される音が変わりました。
以下、メモです。
f0_scaleで周波数を変えるための周波数リストを定義しています。
# パラメータ
f0_scale = [261.626, 293.665, 329.628] # ド レ ミ
scale = input(‘Enter scale : ‘)でキーを入力した値を取得し、値によって周波数$freq$を変えています。
def play(s: pyaudio.Stream, duration: float):
scale = input('Enter scale : ')
if scale == "c":
freq = f0_scale[0]
elif scale == "d":
freq = f0_scale[1]
else:
freq = f0_scale[2]
○エラーと解決方法
play()関数を前回と同様に定義したはずが下記Typeエラーが発生しました。
def play(s: pyaudio.Stream, freq: float, duration: float):
“TypeError: play() missing 1 required positional argument: ‘duration’ “
音の長さdurationはplay(stream, 0.5)の0.5にあたるはずなのになぜだ。。。と思いましたが、理由はplay()関数内の引数でした。
1 required positional argumentは引数が足りませんよという意味のようです。もともと、play()関数には、3つの引数(s: pyaudio.Stream, freq: float, duration: float)を用意していました。
よって、引数が3つの関数に対し、実行時にplay(stream, 0.5)のように引数を2つしか指定しなかったため発生したエラーです。
解決策として、freqは今回キープレスによって一意に決まる定数となったので、play()関数の引数から除外し、引数を(s: pyaudio.Stream, duration: float)にすることで解消しました。
○問題点
input()を用いることでそれらしい機能になりましたが、厳密にはキープレスを検出しているのではなく、文字入力をしています。なので、文字入力→Enterキー→音声出力になり、リアルタイム性がありません。また、今のコードではplay()を何回も書き込まなければなりません。
これを解決するためには、 キープレスを検出する ことが必要です。
この方法を調べた結果、pygameを利用することに行き着きました。
レシピ②:pygameでキープレスを検出する
『Python キープレス』で検索するとpygameの記事が複数出てきました。特にわかりやかった以下の記事を参考にすると、pygame内のメソッドにevent.typeというものがあり、”event.type == KEYDOWN”とすることでキープレスを検出を検出することができるようです。また、event.keyで任意のキーを指定できるようです。
(参考)【Python】Pygame キーボード入力のイベント操作 – とある …
サンプルコード
以下はメモです。
# 画面作成
pygame.init() # Pygameを初期化
screen = pygame.display.set_mode((400, 330)) # 画面を作成
pygame.display.set_caption("keyboard event") # タイトルを作成
pygameを使用するには少なくとも1つの画面が必要なようです。
これをなくすと”error: video system not initialized ”というエラーが出てきました。
#構文
while True:
screen.fill((0, 0, 0))
for event in pygame.event.get():
・
・
・
pygame.display.update()
pygameを使うときの構文のようです。whileで処理をループさせています。
if event.type == KEYDOWN: # キーを押したとき
# ESCキーならスクリプトを終了
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
キープレスの取得部分です。他にも、これを使った例として以下も参考にしました。
(参考)Python keydown combinations (ctrl + key or shift + key) – Stack …
ESCAPEキーが押されたときに終了するよう記述されています。例えば、”a”が押されたときに何らかの処理を行うには”K_a”を記述すればよいみたいです。
○結果
下記のコードでキー入力に応じてリアルタイムに音を鳴らすことに成功しました。
なんかエラーぽいの出たけどなんとかできました。エラーの理由は誰か教えてください。。。
以下、メモです。
音のバリエーションを対応をわかりやすくするためにf0_scaleをリスト型から辞書型へ変更しました。
# パラメータ
f0_scale = {
'ド/C4' : 261.626,
'レ/D4' : 293.665,
'ミ/E4' : 329.628,
'ファ/F4' : 349.228,
'ファ#/F#4' : 369.994,
'ソ/G4' : 391.995,
'ソ#/G#4': 415.305,
'ラ/A4' : 440.000,
'ラ#/A#4': 466.164,
'シ/B4' : 493.883,
'ド/C5' : 523.251,
}
escapeキーを押すとpygameが終了します。
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN: # キーを押したとき
# ESCキーならスクリプトを終了
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
入力されたキーに応じてf0_scaleから周波数freqをもってきます。
pygame.key.get_mods() & pygame.KMOD_XXXは同時押しの場合を定義しています。
if event.type == KEYDOWN: # キーを押したとき
・
・
・
# キーに応じて周波数変化
elif event.key == K_a:
if pygame.key.get_mods() & pygame.KMOD_CTRL:
freq = f0_scale['ド/C5']
else:
freq = f0_scale['ド/C4']
elif event.key == K_s:
freq = f0_scale['レ/D4']
elif event.key == K_d:
freq = f0_scale['ミ/E4']
elif event.key == K_f:
if pygame.key.get_mods() & pygame.KMOD_SHIFT:
freq = f0_scale['ファ#/F#4']
else:
freq = f0_scale['ファ/F4']
elif event.key == K_w:
if pygame.key.get_mods() & pygame.KMOD_SHIFT:
freq = f0_scale['ソ#/G#4']
else:
freq = f0_scale['ソ/G4']
elif event.key == K_e:
if pygame.key.get_mods() & pygame.KMOD_SHIFT:
freq = f0_scale['ラ#/A#4']
else:
freq = f0_scale['ラ/A4']
elif event.key == K_r:
freq = f0_scale['シ/B4']
else:
continue
○問題点
音を鳴らすことはできましたが、音の長さを自由に変えられません。
今は、play()関数内の引数durationで指定秒数ならすようにしています。
次回は、音の長さを任意に変える方法を探したいと思います。
まとめ&次回予告
○まとめ
pygameを用いてPCのキープレスに応じて音を変えることに成功しました。
使用したライブラリ
- numpy
- PyAudio
- pygame(New)
○今日のエラーと解決方法
エラー
- TypeError: play() missing 1 required positional argument: ‘duration’
- error: video system not initialized
解決方法
- play()関数の引数に定数(freq)が含まれていたため、play()関数の引数から除外することで解消しました。
- pygameを使用するには少なくとも1つの画面が必要なため、画面を定義することで解消しました。
○次回予告
次回は音の長さを自由に変える方法を探したいと思います。
- キーボード入力で音を鳴らす
☑1−1.1つの音を鳴らす
☑1-2.音階を鳴らす
1-3.音の長さを自由に変える
lteru
最新記事 by lteru (全て見る)
- 【ノーコード・ローコード】OutSystemsでアプリづくり#04 ~マッチングアプリをつくる①~ - 2022年8月13日
- 【ノーコード・ローコード】OutSystemsでアプリづくり#03 ~簡単なリスト型アプリをつくる③~ - 2022年7月24日
- 【ノーコード・ローコード】OutSystemsでアプリづくり#02 ~簡単なリスト型アプリをつくる②~ - 2022年7月22日