Pythonで自分だけのメトロノームをつくろう

python
python

Pythonでメトロノームをつくってみた!

オリジナルのメトロノームをつくろう!!

Pythonを使ってメトロノームを作ってみました。

先日、Twitter上でPythonを使った音の鳴らし方を紹介している方がおられました、
(紹介されていた そぞらさん のブログ:【Python初心者にオススメ】Windowsのビープ音を鳴らす(すぐにできます)作曲可能 – そぞらブログ (sozorablog.com)

非常に簡単に音が鳴らせるので、「メトロノームが出来るのでは?」と思い立ちました
(2021年5月15日:学習開始85日目 PyQさんで勉強中!

オンラインPython学習サービス「PyQ™(パイキュー)」


メトロノームといえば、最近注目されている日本のラッパーRin音さんが最近(2020年12月)に「metronome town」という曲を配信していました。
なんといてってもトラックがオシャレ、クール。リリックも心地良い。夜が似合う曲です。
metronome town」は公式でUPされていないので、こちらの曲「swipe sheep」を聞いていきましょう。(曲名から公式YouTubeに飛びます。)
Rin音さん、すでに有名ですが、たぶんもっともっと知れわたる存在になると思ってます。

では、メトロノームを作って遊ぼう!!

本ページのコードのGitHubページ

1.メトロノーム作成!の前に

メトロノームをつくっていきますが、その前にプログラムのイメージをしてみましょう。

メトロノームってどういうモノでしょう?
もちろん、大多数の方はご存じだと思いますが、音のテンポを図るためのものですね?
では、メトロノームに求められる機能は何でしょうか?
機能を分解して考えてみましょう。まとめるとこんな感じです。

では、それぞれの機能を見ていきましょう

機能①:音が出る
これをPythonで実行するには、winsoundというモジュールが使えます。冒頭で紹介した内容です。
ただし、これはWindowsの場合に限ります。
winsoundは標準ライブラリですので、インストールは不要です。
winsound公式ドキュメント

Macの場合、osモジュールを使うことでならせるようです。
(こちらを参考にさせていただきました。[Python] ビープ音を鳴らす(Windows/Mac対応)

私がWindowsユーザーですのでWindows仕様で書いていきますが、まとまったところでMac向けのコードも書いてみます。

機能②:一定時間音が止まる
機能④:②の止まっている時間を設定できる

これをPythonで実行するには、timeモジュールのsleepが使えます。
スクレイピングとかをするときに、一定時間処理を止める場合に使用します。
time.sieep(1)とすると、1秒止まります。
数字を変えることで止まっている時間を設定できますね。

機能③:①(音を出す),②(止まる)を繰り返す
「繰り返す」といえば、for文やwhile文が使えますね。
今回は適当な回数で止めたいので、for文を使います。

2.シンプルメトロノームを作る!①

まずはとにかくシンプルなものを作ってみましょう。

まずは、winsoundモジュールを見ていきましょう。
その中でも、winsound.Beepを使っていきます。
公式ドキュメントを見るとwinsound.Beep(frequency, duration)に記載があり、引数として、
・frequency = 音の周波数 (Hz)
duration = 音の長さ
(ミリ秒)
を設定する必要があります。
まずは、frequency=440(A = ラの基準音)、duration=100としておきます。
(※注意:frequencydurationともにint型つまり整数が指定されています。少数はダメです)

次に音の間隔ですが、いったん1秒にします。
音の間隔1秒=60秒で60回なる=60BPMになります。

あとは、for文を使って10回繰り返してみましょう。

簡単ですね。

import winsound
import time

for i in range(10): #10回繰り返し
    winsound.Beep(440, 100) #440hz, 100ミリ秒の音
    time.sleep(1) #1秒間隔

うまく音が再生されました。
ところが、繰り返してると気づきましたが、1秒に1回から微妙に遅れていくことが分かりました。

これは、音の長さ=durationが影響しているようです。
試しに、duration を100から1000ミリ秒 = 1秒として鳴らすと、
「1秒音が鳴る」→「1秒止まる」が繰り返されます。
つまり2秒周期です。BPMでいうと30BPMです。これではメトロノームとしてダメですね。

修正するには、止める時間を調整する必要があります。
図で書くとこんな感じです。

では、プログラムを修正しましょう

import winsound
import time

for i in range(10): #10回繰り返し
    winsound.Beep(440, 100) #440hz, 100ミリ秒の音
    time.sleep(1-100/1000) #1秒間隔

うまく行きました。おおよそ60bpmでなっています。
これで骨格はほぼ完成です。実質5行!!

あとは、BPMを設定できるようにしましょう。
BPM = 60秒になる音の数です。音の間隔はこの逆数で計算できます

使いやすいように、設定パートと実行パートに分けましょう。
設定できる内容は「BPM」「音の高さ = tone」「音の長さ」「繰り返し回数」とします。
設定を変えたい場合、設定パートのみを変更すればOKなようにします。

ここで少し「音の高さ」について考えましょう。
こちらを参考にさせていただきました。
音階の周波数を計算する方法とMIDIノート番号の関係 当時参考にさせていただいたページがなくなりました。)

周波数はこちらの式で計算できるようです

この式では、
d=0の場合、f = 440 Hz つまりA、ラの音になります。
dが1変われば半音変化します。
中央の C、 ドの音は d = -9になります。
今回は、d = 0でC、 ドの音を0番、440HzのAを9番としたいと思います。
そうすると、0→ド、2→レ、4→ミ、5→ファ、、、、となります。

では、ここまでを踏まえてプログラムは下記です。

import winsound
import time

""" 設定パート """
BPM = 60 #BPMを入力
tone = 0 #音の高さ 整数を入力 0=ド 1変わると半音変わる 2=レ 4=ミ 9=ラ
duration = 50 #1音の長さ:通常 50, 推奨 10~100 長くしすぎるとBPMに影響
cycle = 10 #繰り返し回数
""" 設定パートここまで """

tone_d = tone-9
freq = int(440*2**(tone_d/12)) #周波数
sleep_time = (60/BPM) - (duration/1000) #停止時間
for i in range(cycle): #10回繰り返し
    winsound.Beep(freq, int(duration)) #440hz, 100ミリ秒の音
    time.sleep(sleep_time) #1秒間隔

これで基本は完成です。

※Macの場合 (動作未検証です。ご参考までに)

import os
import time

""" 設定パート """
BPM = 60 #BPMを入力
tone = 0 #音の高さ 整数を入力 0=ド 1変わると半音変わる 2=レ 4=ミ 9=ラ
duration = 50 #1音の長さ:通常 50, 推奨 10~100 長くしすぎるとBPMに影響
cycle = 10 #繰り返し回数
""" 設定パートここまで """

tone_d = tone-9
freq = int(440*2**(tone_d/12)) #周波数
sleep_time = (60/BPM) - (duration/1000) #停止時間
for i in range(cycle): #10回繰り返し
    os.system('play -n synth %s sin %s' % (duration/1000, freq)) #440hz, 100ミリ秒の音
    time.sleep(sleep_time) #1秒間隔

3.シンプルメトロノームを作る!② ~エラー処理~

基本的なプログラムは先ほどで出来ました。
ここで2つ修正したい点があります。

①:途中で停止したい
②:停止時間 < 音の長さとしてしまった場合の処理

一つずつ見ていきましょう。

①:途中で停止したい
cycleに大きい値を入れてしまったり、低すぎるBPMを入れるとなかなか終わりません。
途中で停止するには、「コントロールキー」と「c」を同時押しすれば停止できます
しかしエラーが出て止まるので、表示を適切にしたいと思います。

②:停止時間 < 音の長さとしてしまった場合の処理
停止時間 < 音の長さを入力すると、sleep_timeが負の値になります。
time.sleep(負の値)となるためエラーが出ます。
これも適切な設定に変えるような表示にしたいと思います。

まず、それぞれ実行してみましょう。
①:途中で停止した場合

KeyboardInterrupt

このようなエラーが表示されます。

②:停止時間 < 音の長さとしてしまった場合

ValueError: sleep length must be non-negative

これら二つのエラーを処理しましょう。
(実際は、数字ではなく文字列を設定値に入れたりとかもありますが、ここでは割愛します。)

エラーはTry、Exceptで処理できます。

それぞれExceptでエラーを拾ってメッセージを出します。
Ctrl + cが停止しなくなってしまうので、sys.exit()で停止させます。

こんな感じになりました。ctrl + cで止めれるという情報も表示するようにします。

import winsound
import time
import sys

""" 設定パート """
BPM = 60 #BPMを入力
tone = 0 #音の高さ 整数を入力 0=ド 1変わると半音変わる 2=レ 4=ミ 9=ラ
duration = 50 #1音の長さ:通常 50, 推奨 10~100 長くしすぎるとBPMに影響
cycle = 10 #繰り返し回数
""" 設定パートここまで """

tone_d = tone-9
freq = int(440*2**(tone_d/12)) #周波数
sleep_time = (60/BPM) - (duration/1000) #停止時間
print('途中終了する場合は、「Ctrlキー」と「 c 」同時押し')
try:
    for i in range(cycle): #10回繰り返し
        winsound.Beep(freq, int(duration)) #周波数、間隔設定した音
        time.sleep(sleep_time) #1秒間隔
except KeyboardInterrupt: #ctrl+cのエラー表示
    print('Ctrl + cで停止しました')
    sys.exit() #停止
except ValueError: #停止時間が負の場合のエラー表示
    print('停止時間が負の値です。BPM、duration設定を見直してください')

できました。

では、エラーをあえて出してみましょう。
①:途中で停止した場合
ctrl + cで止めてみます。

途中終了する場合は、「Ctrlキー」と「 c 」同時押し
Ctrl + cで停止しました

うまく出来ています。

②:停止時間 < 音の長さとしてしまった場合
duration = 2000としてみます。

途中終了する場合は、「Ctrlキー」と「 c 」同時押し
停止時間が負の値です。BPM、duration設定を見直してください

いい感じですね!

4、拍子を設定したメトロノームを作る!

メトロノームでよくあるのが、4拍子や3拍子といった「拍子」の設定です。
たとえば4拍子だと、「タッ、トッ、トッ、トッ、タッ、トッ、トッ、トッ」という感じで1拍目に強調が入り、それが4回に1回繰り返されます。
また、これまでのプログラムは「1拍子」といえます。
この「拍子」の設定を加えましょう。

これを実装しましょう。
4拍子で考えると、「タッ、トッ、トッ、トッ」の4拍が1セットになります。
4拍子の場合、始めに「タッ」がながれて、その後「4-1=3回」、「トッ」が流れます。
これは、「4拍子のループ」の中に「弱拍のループ」をつくれば行けそうです。

ではやることを考えましょう。
①:拍を設定する。
②:強拍「タッ」と弱拍「トッ」の音を設定
③:ループを作る。(for文の中に入れ子上にfor文を設定)

では、プログラムに書いてみましょう。

import winsound
import time
import sys

""" 設定パート """
beat = 1 #拍子の設定 1以上の整数、3なら3拍子、4なら4拍子
BPM = 120 #BPMを入力
strong_tone = 4 #強拍の音の高さ 整数を入力 0=ド 1変わると半音変わる 2=レ 4=ミ 9=ラ
week_tone = 0 #弱剥の音の高さ 整数を入力 上に同じ
duration = 50 #1音の長さ:通常 50, 推奨 10~100 長くしすぎるとBPMに影響
cycle = 10 #繰り返し回数
""" 設定パートここまで """

st_tone_d = strong_tone-9
we_tone_d = week_tone-9
st_freq = int(440*2**(st_tone_d/12)) #強拍周波数
we_freq = int(440*2**(we_tone_d/12)) #弱拍周波数
sleep_time = (60/BPM) - (duration/1000) #停止時間
print('途中終了する場合は、「Ctrlキー」と「 c 」同時押し')
try:
    for i in range(cycle): #10回繰り返し
        winsound.Beep(st_freq, int(duration)) #強拍音
        time.sleep(sleep_time) #1秒間隔
        for _ in range(beat-1):
            winsound.Beep(we_freq, int(duration)) #弱拍音
            time.sleep(sleep_time) #1秒間隔
except KeyboardInterrupt: #ctrl+cのエラー表示
    print('Ctrl + cで停止しました')
    sys.exit() #停止
except ValueError: #停止時間が負の場合のエラー表示
    print('停止時間が負の値です。BPM、duration設定を見直してください')

これで実行してみると、キチンと拍子も設定できていました。
これにて完成です!!拍子や音を変えて自分だけのメトロノームで遊んでみましょう!!

※ Mac向の場合(動作未検証です。ご参考までに)

import os
import time
import sys

""" 設定パート """
beat = 1 #拍子の設定 1以上の整数、3なら3拍子、4なら4拍子
BPM = 120 #BPMを入力
strong_tone = 4 #強拍の音の高さ 整数を入力 0=ド 1変わると半音変わる 2=レ 4=ミ 9=ラ
week_tone = 0 #弱剥の音の高さ 整数を入力 上に同じ
duration = 50 #1音の長さ:通常 50, 推奨 10~100 長くしすぎるとBPMに影響
cycle = 10 #繰り返し回数
""" 設定パートここまで """

st_tone_d = strong_tone-9
we_tone_d = week_tone-9
st_freq = int(440*2**(st_tone_d/12)) #強拍周波数
we_freq = int(440*2**(we_tone_d/12)) #弱拍周波数
sleep_time = (60/BPM) - (duration/1000) #停止時間
print('途中終了する場合は、「Ctrlキー」と「 c 」同時押し')
try:
    for i in range(cycle): #10回繰り返し
        os.system('play -n synth %s sin %s' % (duration/1000, st_freq)) #強拍音
        time.sleep(sleep_time) #1秒間隔
        for _ in range(beat-1):
            os.system('play -n synth %s sin %s' % (duration/1000, we_freq)) #強拍音
            time.sleep(sleep_time) #1秒間隔
except KeyboardInterrupt: #ctrl+cのエラー表示
    print('Ctrl + cで停止しました')
    sys.exit() #停止
except ValueError: #停止時間が負の場合のエラー表示
    print('停止時間が負の値です。BPM、duration設定を見直してください')


これでメトロノームが完成しましたね。

FIN.

PyQさんで勉強中!

コメント

タイトルとURLをコピーしました