また1からこつこつと

最高はひとつじゃないと信じてまたがんばります。

【Swift】タップした間隔からBPMを計算してみた

試験も終わり久しぶりにコード書きたくなったので、かねてから作っていたアプリに新しい機能を実装すべくいろいろ考えていた。

その中で、タップした間隔からそのBPMを取得するっていうのが必要で、実際にそれを作ってみたので紹介します。

そもそもBPMって?

音楽やってたりする人はよく耳にする言葉だと思うけど、もう一度おさらいしておきます。
BPMとは、 Beat Per Minutes の略で、1分間に何回ビートが打たれたかを示す値です。
BPM100っていうのは1分間に100回ビートを打ったときの速さという意味。
簡単に言ってしまえばBPMとは曲の速さを示してるということ。


タップのタイミングを計測する

UIButtonをタップしたときのタイミングをどうやって計測するかが最大の問題だった。
結局のところ、NSTimerを使って、計算する方法が一番だった

var cnt:Float = 0

func viewDidLoad(){
 super.viewDidLoad()
 timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "onUpdate:", userInfo: nil, repeats: true)
}

func onUpdate(timer: NSTimer){
        cnt += 0.01
        print(String(cnt))
}

まずこのコードでcnt変数が0.01秒毎に0.01ずつ増やすことができた。そこにUIButtonと組み合わせてタップした間隔を取得する。

import UIKit

class BPM: UIViewController {
	
	//時間データを格納する配列を用意
	var timeData:[Float] = []
	var difData:[Float] = []
	
	//各種変数を宣言
	var countNumber:Float = 0
	var timer:NSTimer!
	
	//UIパーツ
	let button:UIButton = UIButton()

	
	override func viewDidLoad() {
		super.viewDidLoad()
		
		//ボタンのUI
		button.frame = CGRectMake(0, 0, 100, 100)
		button.setTitle("tap me", forState: .Normal)
		button.titleLabel?.textAlignment = NSTextAlignment.Center
		button.addTarget(self, action: "tapped:", forControlEvents: .TouchDown)
		self.view.addSubview(button)
		
		//タイマーをスタートさせる
		timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "onUpdate:", userInfo: nil, repeats: true)
		
	}
	
	//ボタンが押された時の処理
	func tapped(sender: UIButton){
		print("tapped")
		
		//押された時の時間をtimeDataに格納
		timeData.append(countNumber)
		
		var ave:Float = 0
		
		//2回以上タップされていたら間隔を計算。それをdifData配列に格納する。
		if timeData.count > 1{
			
			difData.append(timeData[timeData.count-1] - timeData[timeData.count-2])
			
			//dif配列に格納されている値の平均値をとる
			//すべての値を足し合わせる
			for var i=0; i<difData.count; i++ {
				ave = ave + difData[i]
			}
			
			//平均値の計算
			ave = ave / Float(difData.count)
			
			//BPMを計算
			let bpm:Float = 60 / ave
			print(String(format: "%.01f", bpm))
			
		}
		
	}
	
	//タイマーが実行されるたびに呼ばれる = 0.01秒ごとに呼ばれる
	func onUpdate(timer: NSTimer){
		countNumber += 0.01
		print(String(countNumber))
	}

	
}

これでBPMが得られます。

BPMを数式化してみる

上記のコードを理解するためにBPMの算出方法を数式にしてみる。
数式と言っても大したことないです。

  • (BPM60)= 1分間に60回のビート = 1秒 / 1回
  • (BPM120) = 1分間に120回のビート = 1秒 / 2回 = 0.5秒 / 1回

このことから、

  • 60 / 間隔の秒数 = BPM

となります

複数の値の平均値から算出する

//dif配列に格納されている値の平均値をとる
//すべての値を足し合わせる
for var i=0; i<difData.count; i++ {
	ave = ave + difData[i]
}

//平均値の計算
ave = ave / Float(difData.count)

//BPMを計算
let bpm:Float = 60 / ave
print(String(format: "%.01f", bpm))

これは上のソースから抜粋したものです
for文で配列に入っている値すべてを足し合わせたあとに、それを配列の個数で割ることで平均値を求めています
この値を使用するメリットは、求めたBPMの値の精度が上がることです
前後2つのデータを比較するだけだと振り幅がけっこう大きくて、かなり大雑把なものになってしまいます

ただし、タップしすぎると要素数が多くなりすぎて比較が困難になるので、定期的にリセットしてあげたほうが効果的でした

うーん、説明下手ですみません
これを実装したアプリは近日中にAppStoreで公開する予定です