プログラミング習得への道のり #7 アプリの画面をつくる① ~PCキーボードで演奏したい~
プログラミング完全初心者の私が、プログラミングを習得するまでの全過程(主に悩み…)を記事にしていきます。
プログラミング学習にあたり、以下の順で進めています。
①アウトプットを決める、②基礎を学習する、③アウトプットを作り始める
アウトプットは PCキーボードで演奏できるアプリケーション に決めました。
前回、ステップ1. キーボード入力で音を鳴らすのうち、”音の長さを自由に変える”にチャレンジしました。結果、キーを押している間だけ音を流すことは難しく、BPMとキープレスに応じて音の長さを指定することで音の長さを変えることに成功しました。実際に演奏してみたものが以下です。
次の課題として、BPMを最初に決めるものの、テンポがわからず演奏しづらいことが挙げられます。そこで、”メトロノーム機能を追加してテンポを分かるようにする”にチャレンジしました。。。が、ネタバレするとメトロノーム機能の実現には新たな壁がたくさんあることが分かりました。結果、本記事で、 “新たにアプリケーション画面(GUI部分)をつくりはじめる” にチャレンジしました。
PCキーボード演奏アプリの作成ステップ
- キーボード入力で音を鳴らす
☑1−1.1つの音を鳴らす
☑1-2.音階を鳴らす
☑1-3.音の長さを自由に変える
1-4.画面をつくる(New)
1-5.メトロノーム機能をつくる(New) - 音を制御する
2-1.波形を変える
2-2.フィルタリング機能を追加する
2-3.複数の音を同時に鳴らす - 外部から音を取り込む
3-1.単音の音源サンプルから音を取り込む(1楽器)
3-2.音源から楽器を取り出す - 機能を追加する
4-1.録音&再生する(パターンをつくる)
4-2.外部と連携する - 曲を演奏する
- 曲を作る
☑︎本記事の内容
- 【序章①】メトロノーム機能をつくる方法を考える
- 【序章②】アプリ画面をつくる要素を調べる
- 必要な材料:GUI部分の作成
- レシピ①:波形をプロットする
- レシピ②:波形を動的に変化させる
- まとめと次回予告
☑︎著者の経験
この記事を書いている私は、非情報系の大学院を卒業後、通信関係の企業に3年勤めた後、現在までコンサルティング会社に勤めています。
学生時代〜現職までプログラミングとは縁がなく、前職でルーターの設定を少しかじった程度のプログラミング完全初心者です。
こういった私が、解説していきます。
Twitterもはじめました。
目次
【序章①】メトロノーム機能をつくる方法を考える
○テンポを知る方法
前回、BPMとキープレスに応じて音の長さを変えるようにしました。ただ、そのままではテンポがわからず思うように演奏できませんでした。そこで、メトロノームのような演奏者にテンポを知らせる機能が必要と考えました。
ここで、テンポをを知る方法は以下の2通りあると考えます。
- 耳で知る方法
- 目で知る方法
メトロノームを思い浮かべると「カチッカチッ」という音でテンポがわかるイメージなので、前者で実装を考えました。
○耳で知る方法
Pythonでメトロノームをつくる方法を調べるといくつか出てきました。
Pythonでメトロノームをつくる
https://github.com/Gataromaro/metronome
これを参考にメトロノーム機能をつくろうと考えましたが、ここで問題が発生しました。
それはPC内のスピーカーが1つであることです。
いまつくろうとしているものが音楽演奏アプリであるため、PCに内蔵されているスピーカーは演奏に使われます。そのためそこに重ねる形でメトロノームの音を追加するのは単純な話でないことがわかりました。
複数の音を同時に鳴らす方法としては、あらかじめ音を合成し合成音を出力するという方法もあります。ただし、演奏アプリのようにリアルタイムに音が作られていくなかでメトロノーム音と他の音を合成するのは難しいと考えられます。
したがって、視覚的にテンポを知る方法でメトロノーム機能をつくろうと考えました。
○目で知る方法
視覚的にテンポがわかるように方法を考えると、画面上で点滅するエフェクトを追加する、というのが思い浮かびました。
これを実現するためには画面(GUI)が必要です。まずはPythonで画面をつくるイメージを調べてみました。
(参考①)pygameでメトロノームをつくる
https://www.pygame.org/project-Metronome-1041-.html
(参考②)TkinterでSynthesizerアプリをつくる
Pythonで画面をつくるためのライブラリとして、どうやらpygameとTkinterというものがあるようです。
一旦、これらを用いて簡単な画面から作成し、ゆくゆくはメトロノームを作ってみようと思います。
○ステップの見直し
ステップ1. キーボード入力で音を鳴らすを見直し、画面を作成を追加しようと思います。
Before
- キーボード入力で音を鳴らす
1−1.1つの音を鳴らす
1-2.音階を鳴らす
1-3.音の長さを自由に変える
1-4.テンポを決める曲を作る
After
- キーボード入力で音を鳴らす
1−1.1つの音を鳴らす
1-2.音階を鳴らす
1-3.音の長さを自由に変える
1-4.画面をつくる(New)
1-5.メトロノーム機能をつくる(New)
【序章②】アプリ画面をつくる要素を調べる
メトロノーム機能をつくる前に画面をつくることにしましたので、メトロノームの他にどんな機能が欲しいか考えてみました。
まずは今までキープレスに応じてsin波をつくっていましたので、それを視覚的に見えるようにしたいと思います。
完成イメージに近いのはTkinterでSynthesizerアプリをつくるです。
► Code: https://github.com/denczo/EardrumBlas…
上記のYoutubeには丁寧にGithubのリンクも載っていたためソースコードを閲覧することができました。
まずは、このコードを解読し、見様見真似でアプリ画面を作って見ようと思いました。
が、、、解読するのは難しい。。。詳細をとてもブログに残せそうにありません。。。
やり方としては、「#」を使ってひたすらいらなそうなところを削除し、コードをシンプル化し、機能とコードの対応を確認していくというものです。
色々試してみた結果、画面を加えるにあたり、以下の要素が必要かなと思いました。
必要な要素
- 機能を分ける
- クラスを使う
>機能を分ける
参考コードを見てまずわかったことが、アプリケーションを構成しているPythonファイルが階層構造になっていることでした。
「EardrumBlaster/synthlogic」のファイル直下には、“main”や“interfaces”といったフォルダがあり、さらに階層を進むと“synth.py”や“synth_gui.py”、“touchpad.py”といったPythonファイルが格納されています。
このうち、「EardrumBlaster/synthlogic/main/synth_gui.py 」を見てみると以下のようなコードがありました。
# === basic window configuration
from synthlogic.main.synth import Synth
また、「EardrumBlaster/synthlogic/main/synth.py 」を見てみるとSynthというクラスが定義されていました。
class Synth:
def __init__(self, rate=44100, chunk_size=256, gain=0.25, fade_seq=256, gui_enabled=False):
ここからわかったのが、機能ごとにPythonファイルをつくった後、以下で呼び出すことができるということです。
from パス import クラス名機能ごとに小分けにPythonファイルをつくる利点は色々あると思いますが、機能の追加や修正をするときに対象の特定をしやすくなることが挙げられます。
サービス開発の手法の1つであるマイクロサービスと同じような考え方なのかなと思います。
(参考)マイクロサービスアーキテクチャの特徴を学ぼう! メリット …
>クラスを使う
いままで、キープレスに応じて音がなる機能を作ってきました。
これにGUIやその他の機能を足していくに際し、上記のような機能の階層分けが有効のようです。そして、そのためには機能ごとにクラスで定義していく方がベターなようです。
なのでこれからはクラスで定義してみようと思います。
class クラス名:
必要な材料:GUI部分の作成
○ライブラリ
今回使用するライブラリは以下です。
使用したライブラリ
- numpy:数値演算用ライブラリ → sin波を作る
- PyAudio:オーディオ関連ライブラリ → 音を鳴らす
- pygame:ゲーム関連ライブラリ → キープレスを検出する
- matplotlib:グラフ作成ライブラリ(New) → 波形をプロットする(レシピ①、②)
主なメソッド
- numpy.ones() → 全要素の配列を1で初期化する(レシピ①)
- numpy.linspace() → 等差数列をつくる(レシピ①)
- numpy.concatenate() → 要素を足し合わせる(レシピ①)
○新たに加えるPython基礎要素
- クラス:Sinwave_synth → sin波の作成、プロット、音声出力(レシピ②)
レシピ①:波形をプロットする
画面を作成するにあたり、まずはわかりやすいsin波のグラフをつくってみたいと思います。
まずは「EardrumBlaster/tests/FM_Test.py」をそのまま実行したところ、以下のグラフが現れました。なんだこの形は、、、
FM_Test.pyの実行結果
プロットされた式はy = np.sin(2*np.pi*fc*x+2*np.pi*beta*lfo)みたいなので、“beta”や“lfo”なるものが波の式に加わりこのような形になっていると推察されます。
次に「EardrumBlaster/tests/EnvelopeTest.py」を実行してみました。
すると、また新しい形のグラフが作成されました。
EnvelopeTest.pyの実行結果
参考にしたYoutubeを見てみると、どうやらsin波のゲイン?を表しているようです。エフェクトとかをかけると音の鳴り始めや鳴り終わりの長さが変わると思いますが、おそらくそれです。
なので、ステップ2. 音を制御するで使いそうだなと推察できます。
上記から、 波を表すグラフとして、sin波とゲインの2つプロットしていきたいと思います。
○結果
参考コードを取捨選択して、波形をプロットしてみました。
上:ゲイン(変化なし) 下:サイン波(ド)
上:ゲイン(変化あり) 下:サイン波(ド)
以下、メモです。
○エラーと解決方法
releaseの要素数をphaseRangeにし、実行すると”ValueError”というエラーが出てきました。
# ゲインの定義
attack = np.linspace(0, 1, phaseRange) # linspaceで等差数列を生成(0→1へ要素数phaseRangeで変化)
decay = np.linspace(1, 0.5, phaseRange) # linspaceで等差数列を生成(1→0.5へ要素数phaseRangeで変化)
sustain = np.empty(phaseRange)
sustain.fill(0.5)
release = np.linspace(0.5, 0, phaseRange) # linspaceで等差数列を生成(0.5→0へ要素数phaseRangeで変化)
envelope = np.concatenate((attack, decay, sustain, release)) # concatenateで要素を結合
これは先に上げたとおり要素数があっていないからです。phaseRangeは元の変数enRangeの要素を1/4していますが割り切れないため、✕4をしても元に戻りません。
エラー画面から、要素数enRangeは22050であるのに対し、要素数envelopeは22048です。よって、envRange *= envelopで要素数のミスマッチが起こっています。
解消方法として、先にあげたようにreleaseの要素数を調整しました。
なお、要素数の考え方は以下を参考にしました。
(参考)覚えておくべきNumPy配列のブロードキャストのルール
○まとめ
sin波およびゲインのプロット方法がわかりました。ゲインの設定については、ステップ2. 音を制御するで詳しく考えたいとため、ステップ1では一旦ゲインの変化はなしで進めていきたいと思います。
レシピ②:波形を動的に変化させる
グラフのプロット方法がわかりましたので、キープレスに応じて波形が変化するか確かめてみました。
○結果
以下のコードでキープレスに応じてグラフが作成できました。
以下、メモです。
○エラーと解決方法
durationをglobal関数で指定しないと”NameError”が出ました。
NameError: name 'duration' is not defined
変数には「グローバル変数」「インスタンス変数」「ローカル変数」の3種類あり、それぞれで扱えるスコープが異なります。
- グローバル変数:プログラムのどこからでも使える変数
- インスタンス変数:クラス内でのみ使える変数
- ローカル変数:メソッド内でのみ使える変数
(参考)インスタンス変数、ローカル変数のスコープをざっくり理解する
今回、ローカル変数として定義されてしまっていたため、クラス内のstart_up()関数で定義されたduration = DURATION[‘L4’]が初期値として代入されずエラーになりました。
したがって、durationをグローバル変数として定義することで解消しました。
global duration # 初期値のためにglobal変数が必要
○問題点
キープレスによって波形を変化させることができましたが、プログラムの終了後に履歴のように波形がまとめて表示されてしまっています。なので、リアルタイムで波形を変化させたいです。
まとめ&次回予告
○まとめ
PCのキープレスによってsin波の周波数を変えたのち、グラフにプロットすることができました(レシピ②)。
一方、キーを押した瞬間にグラフは出現せず、リアルタイム性がない状況です。
使用したライブラリ(レシピ②)
- numpy
- PyAudio
- pygame
- matplotlib
○今日のエラーと解決方法
エラー
- ValueError: operands could not be broadcast together with shaps (22050,) (22048,) (22050,)
- NameError: name ‘duration’ is not defined
解決方法
- 配列内で要素数のミスマッチが生じて起こったエラーでした。要素数を揃えることで解消しました。
- classを用いたことで、初期値を設定する際に変数durationをglobal変数にする必要がありました。
○次回予告
リアルタイムに波形が変わる画面を作成したいと思います。
- キーボード入力で音を鳴らす
☑1−1.1つの音を鳴らす
☑1-2.音階を鳴らす
☑1-3.音の長さを自由に変える
1-4. 画面をつくる(New)
1-5.メトロノーム機能をつくる(New)
lteru
最新記事 by lteru (全て見る)
- 【ノーコード・ローコード】OutSystemsでアプリづくり#04 ~マッチングアプリをつくる①~ - 2022年8月13日
- 【ノーコード・ローコード】OutSystemsでアプリづくり#03 ~簡単なリスト型アプリをつくる③~ - 2022年7月24日
- 【ノーコード・ローコード】OutSystemsでアプリづくり#02 ~簡単なリスト型アプリをつくる②~ - 2022年7月22日