🐑

[Swift初心者]AirPods Proの加速度センサーで首の動き(ヘドバン)を可視化してみた

に公開

AirPods Proの加速度センサーで首の動き(ヘドバン)を可視化してみた

はじめに

AirPods Proには加速度センサーが搭載されていることをご存じでしょうか?
このセンサーを活用することで、頭の動きや首の振り(いわゆるヘドバン)の勢いをiPhoneでリアルタイムに可視化できます。

本記事では、AirPods Proから取得した加速度データを使って、**「首を振る動きの擬似的な速度(km/h風)」**をSwiftUIで表示するシンプルなプロトタイプアプリを紹介します。

将来的には、動きの激しさに応じてエフェクトを加えたり、簡単なゲームに発展させたりする予定ですが、今回はまず、センサーからの加速度取得と表示までをゴールにしています。

完成イメージ

使用環境

  • iOS 18.5(実機でのみ動作)
  • Swift 5.9
  • SwiftUI
  • AirPods Pro(耳に装着した状態で使用)

アプリ概要とアプローチ

AirPods Proから CMHeadphoneMotionManager を使って加速度データを取得します。
取得できるのは userAcceleration(重力成分を除いた人の動きのみ)で、3軸(x, y, z)の値を使って加速度ベクトルの大きさを計算し、それを元に速度風の数値として表示します。

速度の計算は正確な物理的速度ではなく、擬似的に「爆速感」を演出するためのスケーリングを行っています。

準備

CoreMotionフレームワークが使えるように、Info.plistにNSMotionUsageDescriptionキーを追加し、説明を記載します。(これをしないと加速度データが取得できない)

ViewModelの実装:加速度データの取得と速度換算

この HeadTrackingManager クラスは、AirPods Pro の加速度センサーから取得したデータを用いて、首の動きの勢いを“速度風”の数値としてリアルタイムに表示するためのViewModelです。

全体の処理の流れは以下の通りです:

  1. startTracking() を呼ぶと、AirPods Proからのモーションデータの取得が開始されます。
  2. motion.userAcceleration を使って、3軸の加速度を取得します。
  3. 加速度の大きさをユークリッド距離で計算し、簡易的にスケーリングして「km/h」のような数値に変換します。
  4. 画面表示用の @Published プロパティ(currentSpeedKmh)を更新し、最大記録も同時に保持します。
  5. stopTracking() を呼ぶと、センサーデータの取得を停止します。

以下が実際の実装コードです:

import CoreMotion
import Foundation

class HeadTrackingManager: ObservableObject {
    private var motionManager = CMHeadphoneMotionManager()
    
    @Published var currentSpeedKmh: Double = 0.0
    @Published var maxSpeedKmh: Double = 0.0
    
    func startTracking() {
        // AirPodsのモーションデータが利用可能か確認
        guard motionManager.isDeviceMotionAvailable else {
            print("Headphone motion data is not available.")
            return
        }
        
        motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in
            guard let self = self, let motion = motion else {
                print("Error: \(String(describing: error))")
                return
            }
            
            let acc = motion.userAcceleration
            
            // ユークリッド距離(加速度の大きさ)を計算
            let magnitude = sqrt(acc.x * acc.x + acc.y * acc.y + acc.z * acc.z)
            
            // 擬似的に m/s 扱いして km/h に変換(厳密な計算は大変なため簡略化)
            let speedKmh = magnitude * 30.0
            
            // 最大値を更新
            DispatchQueue.main.async {
                self.currentSpeedKmh = speedKmh
                if speedKmh > self.maxSpeedKmh {
                    self.maxSpeedKmh = speedKmh
                }
            }
        }
    }
    
    func stopTracking() {
        motionManager.stopDeviceMotionUpdates()
    }
}

擬似速度について

本来、加速度から正確な速度を求めるには積分処理が必要です。
しかし、センサーの誤差やデータ間隔のばらつきによって累積誤差が大きくなりやすいため、今回はシンプルに以下の式で代用しています:

            // 擬似的に m/s 扱いして km/h に変換(厳密な計算は大変なため簡略化)
            let speedKmh = magnitude * 30.0

精密な積分処理ではありませんが、「激しく動いたら値が大きくなる」という意図で調整しています。

コード:UI

SwiftUIを使って以下のようなUIで表示しています:

  • 現在の擬似速度(km/h)
  • 最大加速度記録
  • スタート / ストップボタン

今後はこのUIにランク表示やエフェクトを追加予定です。

import SwiftUI

struct ContentView: View {
    @StateObject private var tracker = HeadTrackingManager()
    
    var body: some View {
        VStack(spacing: 24) {
            Text("現在のヘドバン速度")
                .font(.title2)
            Text(String(format: "%.2f km/h", tracker.currentSpeedKmh))
                .font(.largeTitle)
                .bold()
                .foregroundColor(.blue)
            
            Text("最大記録")
                .font(.title3)
            Text(String(format: "%.2f km/h", tracker.maxSpeedKmh))
                .font(.title)
                .foregroundColor(.red)
            
            Button("開始") {
                tracker.startTracking()
            }
            .padding()
            .frame(width: 150)
            .frame(height: 50)
            .background(Color.green)
            .foregroundColor(.white)
            .clipShape(Capsule())
            
            Button("停止") {
                tracker.stopTracking()
            }
            .padding()
            .frame(width: 150)
            .frame(height: 50)
            .background(Color.gray)
            .foregroundColor(.white)
            .clipShape(Capsule())
        }
        .padding()
    }
}


#Preview {
    ContentView()
        
}

実行時の確認ポイント

  • AirPods Proを耳に装着し、アプリを実機で起動

  • 首を振ると、画面上の「km/h」がリアルタイムで変化

  • 記録された最大加速度も保持される

今後の予定

  • 擬似速度に応じて「S級!A級!」などのランクを表示

  • スタート〜測定〜結果表示までのゲーム風フロー(ゲームセンターにある、パンチ力測定マシンのヘッドバンキングの速度版作りたい)

  • 効果音(メタルBGM)やアニメーションエフェクト

  • SwiftUIでのシンプルなゲームUIの追加

終わりに

この記事では、SwiftのApple純正フレームワークであるCoreMotionを用いて、AirPods Proの加速度センサーの情報を取得し可視化するところまでを紹介しました。
普段使用しているデバイスの中に搭載されているセンサーで遊ぶのは非常に面白く、趣味や研究など幅広い用途で活用できると思います。

著者について

地方国立大学で情報学を学んでいる学生です。研究室はデータ分析系のところに所属しています。
個人でiOSアプリの開発を行なって遊んでいます(その場で思いついたようなくだらないものしか作ってないです)
普段はSwiftUIを中心に、遊び感覚で小さなiOSアプリを作っています。この記事もその一環です。

#iOS #SwiftUI #CoreMotion #AirPodsPro #個人開発

Discussion