質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.92%

BPMカウントアプリのコードを修正したいです。

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 185

starry_sky

score 2

閲覧ありがとうございます。
曲に合わせて自分でタップし、そのBPMを表示するアプリを作成したいと考えています。
タップするとランダムに数字が表示されるだけで肝心のBPMカウント機能が実装できません。おそらく計算式に問題があるんだと思います。不慣れで恐縮なのですが、何かアドバイス頂けないでしょうか。

該当のソースコード

import UIKit
class ViewController: UIViewController {

var count: Double = 0.00

var timer: Timer!
var BPM: Double!

@IBOutlet var timerLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func start() {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
if count > 0.00{
count = 60/count
timerLabel.text = String(format: "%.2f", count)
}

}
@IBAction func stop() {
timer.invalidate()
}

@objc func update() {
count = count + 0.01

イメージ説明        
}
}

試したこと

Githubやcocoa podsでソースコードを探し見比べたりしたのですが、原因を見つけられませんでした。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

オリジナルのコードに基づく修正結果は後半に追記しました。


BPM = Beats per Minute なので、一分間の拍数(タップ回数)を計測すれば良いことになります。

作られたプログラムだと、割り込みを 0.01秒ごとにかけてタップ回数を計測するような流れになっていますが(それ以上深く読んでいませんので、根本的な原因まではみていませんが)、上記の理屈を素直に解釈すれば、1分後に起きる割り込みをかけて、その間に実行されたタップ回数を計測すれば良いということになります。

もっとも、1分間ずっとタップするのもしんどいので、たとえば10秒間タップし、タップされた回数を6倍することで、BPM を推測することも可能かと思います。

整理すれば

  • 割り込み周期が10秒のタイマを作る
  • 割り込み周期内に計測用のボタンが押されたら、その回数を記録する
  • 10秒後、割り込みが発生した段階で計測用のボタンが押された回数を6倍し、表示する

という流れにすれば、とりあえず BPM を計算するようなアプリは作れます。

ただし、このアプリには次のような問題も含まれています。

  • 現状では、スタートボタンを押した瞬間から計測を開始している。したがって、スタートボタンを押した後、計測用のタップボタンを押すまでの空白時間も計測に含まれるため、より正確に計測するのであればタイマ割り込みを開始するタイミングを考慮する必要がある。
  • 10秒間に押されたボタンの回数から BPM を推測する方法を採用しているが、実は厳密に BPM を計算することはできない。この方法だと拍数が遅くなればなるほど誤差が含まれてしまう。より正確に計測するのであれば、拍ごとの経過時間を計測し、そこから BPM を計算する必要がある(この場合は 0.1 秒周期などの割り込みを掛けて計算する必要もあるでしょう)。
  • あと何秒で計測が終了するのか分からないので、使い慣れていないと不安になる。
  • リアルタイムで BPM を表示するのであれば、やはり割り込み方法を変更する必要がある。

上記の内容をクリアすることこそ、独自の BPM カウントアプリを作る醍醐味だとおもいますので、ぜひ挑戦していただければと思います。

import UIKit

class ViewController: UIViewController {
    // MARK: BPM 表示用のボタン
    @IBOutlet weak var bpmLabel: UILabel!
    // MARK: 計測開始用のボタン
    @IBOutlet weak var startButton: UIButton!
    // MARK: 計測用(タップ開始)のボタン
    @IBOutlet weak var tapButton: UIButton!

    // MARK: タイマのインスタンスを代入するための変数
    // オプショナル型にする案もあるが、とりあえずはインスタンスを代入しておく。
    var timer: Timer = Timer()
    // MARK: タップ回数
    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        // タップ用のボタンは隠しておく
        tapButton.isHidden = true
        bpmLabel.text = "0"
    }

    // MARK: スタートボタンを押したときの処理
    @IBAction func startButtonPressed(_ sender: Any) {
        // スタートボタンを押したら、スタートボタンは隠し、タップボタンを表示する
        tapButton.isHidden = false
        startButton.isHidden = true

        // ラベルに「計測中」と表示する
        bpmLabel.text = "計測中"

        // カウント回数を初期化
        count = 0

        // タイマのインスタンスを作成。
        // 今回は10秒間計測し、計測終了したら calcBPM() を呼び出す。呼び出しは一回のみ(リピートしない)
        // TODO: いまの作りだと、スタートボタンを押したらすぐに計測を開始してしまう。タップボタンを押した瞬間から計測したいのであれば、適切な場所に移動させる。
        timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(calcBPM), userInfo: nil, repeats: false)
    }

    // MARK: タップボタンを押すたびに count をカウントアップする
    @IBAction func tapButton(_ sender: Any) {
        count += 1
    }

    // MARK: BPM を計算する処理
    @objc func calcBPM() {
        // TODO: リピートしないタイマであれば、自分で invalidate() を呼ぶ必要はない
        // timer.invalidate()

        // 隠していたボタンを表示し、表示していたボタンを隠す
        startButton.isHidden = false
        tapButton.isHidden = true

        // 10秒間のタップ回数を6倍することで BPM を見積もる
        let estimatedBPM = count * 6

        bpmLabel.text = "BPMは \(estimatedBPM) です"
    }
}

追記

思ったように動かない、というコードをきちんとみてみました。

タップ間の経過時間を計算した値で 60 を割ることによってリアルタイムで BPM を表示したいのだと理解しました(上で指摘した項目のうち、リアルタイムで計測することを実現しようとしている)。

思うように動かない理由は

  • 初回だけで良い Timer のインスタンスを毎回作っている(作っても多分動くが意味がない)
  • BPM に入れるべき計算結果を count に入れて上書きしている
  • タップごとに count をリセットしなければいけないのにリセットしていない

などがあります。

これらを修正すれば、概ね元の構造を残したまま動くようにすることは可能です。

import UIKit

class ViewController: UIViewController {

    var count: Double = 0.00

    var timer: Timer!
    var BPM: Double!

    @IBOutlet var timerLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func start() {
        // MARK: タイマは初回だけ起動
        if timer == nil {
            timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
        } else {
            // 秒数を count で割った値を BMP に代入
            BPM = 60 / count
            timerLabel.text = String(format: "%.2f", BPM)
            // count はゼロにする
            count = 0
        }
    }

    @IBAction func stop() {
        timer.invalidate()
    }

    @objc func update() {
        count = count + 0.01
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/07 16:10 編集

    こんなに丁寧なご回答本当にありがとうございます。とても参考になりました。考え方なども教えて頂いたので別のアプリ作成でも活かしたいと思います。

    キャンセル

0

丸投げ回答です。

import UIKit

class ViewController: UIViewController {

    //BPM表示用ラベル
    @IBOutlet var labelBpm: UILabel!

    //開始時間
    var startDate = Date.distantPast

    //タップ回数
    var count = -1

    //タップ処理
    @IBAction func tapped(_ sender: UIButton) {

        count += 1

        if count == 0 {
            //最初のタップ

            //開始時間を保存
            startDate = Date()

            //ラベル更新用のタイマーを起動
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
                //この中は1秒毎に呼ばれる

                //経過時間 (60で割って分単位にしている)
                let t = timer.fireDate.timeIntervalSince(self.startDate) / 60

                //BPM = タップ数(回) / 経過時間(分)
                let bpm = Double(self.count) / t

                //ラベル更新
                self.labelBpm.text = String(format: "%.1fBPM", bpm)
            }
        }
    }

}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/07 16:12 編集

    コード付きのご回答本当にありがとうございます。とても分かりやすく大いに参考にさせていただきました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る