🎸

ARギターエフェクター Apple Vision Pro ver.

2025/03/09に公開

1 はじめに

この記事は、2025年3月1日に東京ポートシティ竹芝で開催された「第16回VRフェス」にて展示した、"AR Guitar Effector【Apple Vision Pro ver.】"の開発記録をまとめたものです。

https://youtu.be/IuJFRhzgsK8

1.1 AR Guitar Effector (ARギターエフェクター) とは

「AR Guitar Effector」は、AR技術を活用した新しいギター演奏体験を提案するプロジェクトです。


AR Guitar Effectorのイメージ

具体的な仕組みとしては、空中に浮かぶARのエフェクタースイッチに向かってギターの先端からレイを飛ばすと、ギターの音にエフェクトがかかります。色々なスイッチにギターを向けながら演奏することで、多様に変化する音を楽しむことができます。

本プロジェクトの第1弾として昨年2024年末に、Meta Quest 3を使用したAR Guitar Effectorシステムの実装について記事にまとめました。システムの詳細については、以下の記事をご覧ください。

https://zenn.dev/ekito_station/articles/ar-guitar-effector-intro

1.2 Apple Vision Pro ver.

本プロジェクトの第2弾となる今回は、Apple Vision Proを使用したAR Guitar Effectorシステムを実装しました。


AR Guitar Effector 【Apple Vision Pro ver.】

1.2.1 Object Tracking機能

システムに使用するHMDをMeta Quest 3からApple Vision Proに変更したことによって生じる大きな違いは、ギターの先端のトラッキング手法です。Meta Quest 3バージョンではギターのヘッドにQuest 3のコントローラーを取り付けていましたが、Apple Vision Proには公式のコントローラーがありません。そこで、代わりに使用した機能はObject Trackingです。

https://developer.apple.com/documentation/visionos/implementing-object-tracking-in-your-visionos-app/

Object Trackingは、Apple Vision Proが実空間上の特定の物理オブジェクトを認識すると、その物理オブジェクトの位置や方向にバーチャルオブジェクトを追従させることを可能にします。この機能を活用することで、Apple Vision Proがギターのヘッドを認識すると、その位置を起点としてギターの向いている方向にレイを飛ばす仕組みを実装することができます。

1.2.2 デメリット・メリット

Meta Quest 3バージョンと比較した際のApple Vision Proバージョンのデメリットは、Object Trackingのトラッキングレートつまりトラッキングの更新頻度が、(自分の試した限りでは) Quest 3のコントローラーより小さかったことです (AR FoundationのImage Trackingよりは大きかった)。

一方でメリットは、ギター自体に何かを取り付ける必要がないことです。Meta Quest 3バージョンでは、コントローラーがギターから外れたらシステム全体が機能しなくなってしまいますが、Apple Vision Proバージョンではその心配がありません。

1.2.3 その他追加機能

以上のようなApple Vision Proならではの変更点のほかに、以下の機能を実装しました。

  • エフェクタースイッチの種類を追加した
  • エフェクタースイッチを手で掴んで移動できるようにした

今回エフェクタースイッチを3種類 (Distortion, Redux, Volume Swells) 用意し、それぞれの位置を手で掴んで動かして調整できるようにしました。そのため、エフェクタースイッチを近くに並べて、それらを狙ってレイを飛ばすことで、同時に複数のエフェクトをかけることも可能になりました。実際の操作の様子は、以下の動画をご覧ください。

https://www.youtube.com/watch?v=IuJFRhzgsK8&t=28s

2 実装

2.1 システム全体像

システム構成図は以下のようになります。ギターの先端から飛ばされたレイがエフェクタースイッチに当たったり外れたりすると、Apple Vision ProからPCにOSCメッセージが送信されます。受信したOSCメッセージに基づいてPC上のAbleton Liveでギター音のエフェクトが制御されます。


システム構成図

2.2 使用ソフト・ライブラリ

使用ソフト・ライブラリは以下のとおりです。PCはM1搭載MacBook Proを使用しました。

  • Xcode (Version 16.2)
    • Reality Composer Pro
    • Create ML
    • OSCKit
  • Ableton Live (Suite 12.1.10)
    • Max for Live

以下のセクションでは、Xcodeでの実装について解説していきます。Ableton Liveでの実装については、Meta Quest 3バージョンとほぼ変わらないので、そちらの解説記事を参照してください。

https://zenn.dev/ekito_station/articles/ar-guitar-effector-intro

2.3 Xcodeでの実装の詳細

2.3.1 ギターのヘッドの3Dモデル作成

まず、Object Tracking機能を使って、Apple Vision Proのカメラがギターのヘッドを認識したらその位置にアンカーが配置される処理を実装していきます。

2.3.1.1 ギターのヘッドを3Dスキャン

Object Captureが組み込まれたiOSアプリを使用して、ギターのヘッドを3Dスキャンして3Dモデルを作成します (Object Captureとは、フォトグラメトリを使用して3Dモデルを作成できるAPIです)。私は、「KIRI Engine:3D Scanner & LiDAR」というアプリを使用しました。


ギターのヘッドの3Dモデル

2.3.1.2 Create MLでレファレンスオブジェクト生成

Create MLというXcode内のアプリで機械学習を行うことで、3Dモデルからレファレンスオブジェクトを生成します。このレファレンスオブジェクトは、後でReality Composer Proにインポートするために使用します。

Create MLは、XcodeからXcode > Open Developer Tool > Create MLで開くことができます。


Create MLの画面

ビューポートに3Dモデルをドラッグ&ドロップしてから、Viewing anglesを設定します。Viewing Anglesとは物理オブジェクトをどの角度から認識し得るかの設定で、All Angles (全角度から)、Upright (下方以外から)、Front (下方・背面以外から) の中から選択できます。私の場合は、ギターのヘッドは色々な角度から見え得るので、All Anglesを選択しました。

そして、Trainボタンをクリックして機械学習を開始させます。私の場合は、完了までに22時間半ほどかかりました。

機械学習が完了したら、Outputタブに移動しGetボタンをクリックして、レファレンスオブジェクトを保存します。

2.3.2 Object Trackingのアンカー作成

2.3.2.1 Xcodeプロジェクト作成

ここで、Xcodeで新規プロジェクトを作成します。テンプレートは、visionOS > Application > Appを選択します。


Xcodeの画面

続いて、Reality Composer Proを開きます。Xcode画面左のナビゲーターエリアからPackages > RealityKitContent > Packageを開き、ビューポート右上のOpen in Reality Composer Proボタンをクリックします。

2.3.2.2 Reality Composer Proでアンカー設定


Reailty Composer Proの画面

Immersive.usdaというシーンを開き、シーン内に空のTransformエンティティを配置します (シーンに元からあるSphere_LeftやSphere_Rightは必要なければ削除)。私の場合は、このTransformエンティティの名称をGuitarAnchorに変更しました。このTransformエンティティを選択し、画面右のインスペクターでAnchoringコンポーネントを追加します。

Anchoringコンポーネントにおいて、以下のように各項目を設定します。

  • Target: Object
  • Anchor Object: 先ほど生成したレファレンスオブジェクト

これで、レファレンスオブジェクトと同じ見た目の物理オブジェクトがApple Vision Proのカメラに認識されたら、その位置にアンカーが配置されるようになります。

2.3.3 エフェクタースイッチ作成

今度は、エフェクタースイッチとなるキューブを作成していきます。シーン内にPrimitive ShapeのCubeエンティティを配置していき、それぞれ適切な名称に変更します (私の場合はDistortionSwitchReduxSwitchViolinSwitch)。

また、各キューブに以下のコンポーネントを追加します。

  • Input Targetコンポーネント (手で掴んで移動できるように)
  • Collisionコンポーネント (手で掴んで移動できるように、かつレイが当たったことを検知できるように)

2.3.3.1 Shader Graph

各キューブの見た目を調整するために、Reality Composer ProのShader Graphを活用しました。

たとえば、ReduxSwitchに適用したシェーダーは、Shader Graphで以下のように構築しました。ReduxとはAbleton Liveに内蔵されているエフェクトの一種で、オーディオ信号のサンプリングレート・ビット解像度を下げることができます。このエフェクトを視覚的に表現するために、グラデーションの解像度を落とす、すなわちグラデーションをモザイク調にするシェーダーを作成しました。


Shader Graphの画面

2.3.3.2 キューブを手で掴んで移動できるように

各キューブを手で掴んで移動できるようにする処理を実装するために、Xcodeに戻りスクリプトを編集します。

ImmersiveView.swiftを開くと、先ほどReality Composer Proで編集したImmersiveシーンをRealityView内のエンティティとして読み込む処理が元から書かれています。ここに、ドラッグジェスチャーを定義してRealityViewに適用する処理を追加します。

ImmersiveView.swift
import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {

    var body: some View {
        RealityView { content in
            // ImmersiveシーンをRealityViewに読み込む
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)
            }
        }
        .gesture(dragGesture) // ドラッグジェスチャーをRealityViewに適用
    }
    // ドラッグジェスチャーを定義
    var dragGesture: some Gesture {
        DragGesture()
            .targetedToAnyEntity()
            .onChanged { value in
                value.entity.position = value.convert(value.location3D, from: .local, to: value.entity.parent!)
            }
    }
}

これによって、親指と人差し指でキューブをピンチして保持した状態で手を動かすことで、キューブをドラッグして移動させることができるようになります。

2.3.4 アンカーの位置からレイキャスト

アンカーの位置からレイを飛ばす処理を実装していきます。

2.3.4.1 アンカーの位置や方向を取得

アンカーが配置された際にそのアンカーに追従させるためのエンティティ (以下、RayOrigin) を、Reality Composer ProのImmersiveシーン内に配置しておきます。私の場合は、RayOriginという名称でCubeエンティティを配置しました。


RayOrigin

アンカーのTransformを取得するためには、あらかじめXcodeのImmersiveView.swiftにおいて、SpatialTrackingSession.Configurationを実行する必要があります。

そのうえで、transformMatrixを用いてアンカーのTransformを取得します。そして、setTransformMatrixを用いてRayOriginのTransformをアンカーのTransformと揃えます。この処理を毎フレーム繰り返すことで、RayOriginがアンカーに追従するようになります。

スクリプトは以下のようになります (説明に直接関係する箇所のみ抜粋しています)。

ImmersiveView.swift
struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)

                // SpatialTrackingSession.Configurationを実行する
                let session = SpatialTrackingSession()
                let configuration = SpatialTrackingSession.Configuration(tracking: [.object, .world])
                let _ = await session.run(configuration)

                // Updateイベントをsubscribeする
                _ = content.subscribe(to: SceneEvents.Update.self) { event in
                    updateRaycast() // この関数が毎フレーム実行される
                }
            }
        }
    }
    
    func updateRaycast() {
        // アンカーのTransformを取得する
        let anchorTransform = guitarAnchor?.transformMatrix(relativeTo: nil)
        // guitarAnchorはReality Composer ProのImmersiveシーン内のGuitarAnchorを参照する変数
        
        // RayOriginのTransformをアンカーのTransformと揃える
        rayOrigin?.setTransformMatrix(anchorTransform ?? matrix_identity_float4x4, relativeTo: nil)
        // rayOriginはReality Composer ProのImmersiveシーン内のRayOriginを参照する変数
    }
}

2.3.4.2 レイキャストの実行

raycastを用いて、RayOriginの位置からRayOriginの上方に向かってレイキャストを実行します。

ImmersiveView.swift
// RayOriginの位置を取得
let originPosition = rayOrigin?.convert(transform: Transform(), to: nil).translation
// RayOriginの上方を取得
let upDirection = rayOrigin?.convert(direction: SIMD3<Float>(0, 1, 0), to: nil)

// レイキャストの実行
guard let results = rayOrigin?.scene?.raycast(origin: originPosition ?? SIMD3<Float>(0, 0, 0), direction: upDirection ?? SIMD3<Float>(0, 1, 0), length: 10) else {
    print("Error: results is nil.")
    return
}

raycastによって飛ばされるレイ自体はシーン内に表示されません。そこで、レイが飛んでいる方向が視覚的に分かりやすいように、Reality Composer Proにおいて、アンカーの子として空のTransformエンティティを配置し、そこにParticle Emitterコンポーネントを追加します。これによって、アンカーの位置からレイが飛ぶのと同じ方向にパーティクルを飛ばします。


パーティクルを飛ばす

2.3.5 レイがキューブに当たったらOSCメッセージを送信

最後に、レイがキューブに当たったらOSCメッセージを送信する処理を実装していきます。

2.3.5.1 OSCKitの追加

OSCメッセージを送信するためのライブラリとして、OSCKitをXcodeのプロジェクトに追加します。

File > Add Package Dependencies...をクリックして表示されたウィンドウの検索バーに、https://github.com/orchetect/OSCKitと入力します。OSCKitが表示されたら選択してAdd Packageボタンをクリックします。


OSCKitの追加 ①

次に表示されるウィンドウのAdd to Targetの列で、プロジェクト名と同じ名称のTarget (「プロジェクト名+Tests」ではないほう) を選択してAdd Packageボタンをクリックします。


OSCKitの追加 ②

2.3.5.2 OSCメッセージの送信

ImmersiveView.swiftにおいて、先に述べたようにレイキャストを実行し、その結果を取り出します。レイが当たったエンティティの名称がキューブの名称と一致する場合、OSCメッセージを送信する処理を実装します。

スクリプトは以下のようになります (説明に直接関係する箇所のみ抜粋しています)。この例では、レイがDistortionSwitchと当たった場合、OSCアドレス/arguitarに値1を送信しています。

ImmersiveView.swift
import OSCKit

struct ImmersiveView: View {
    private let OSC_IP_ADDRESS = "000.000.000.000" // Ableton Liveを起動させているPCのIPアドレス
    private let OSC_PORT  = 0000 // ポート番号
    private let oscClient  = OSCClient()
    
    func updateRaycast() {
        (中略)
        // レイキャストの実行
        guard let results = rayOrigin?.scene?.raycast(origin: originPosition ?? SIMD3<Float>(0, 0, 0), direction: upDirection ?? SIMD3<Float>(0, 1, 0), length: 10) else {
            print("Error: results is nil.")
            return
        }

        // レイキャスト結果の処理
        for result in results {
            if (result.entity.name == "DistortionSwitch") {
                // レイと当たったエンティティの名称が"DistortionSwitch"だったらOSCメッセージを送信
                let msg = OSCMessage("/arguitar", values: [1])
                do {
                    try oscClient.send(msg, to: OSC_IP_ADDRESS, port: UInt16(OSC_PORT))
                } catch {
                    print("Failed to send OSC message: \(error.localizedDescription)")
                }
            }
        }
    }
}

おわりに

以上、"AR Guitar Effector【Apple Vision Pro ver.】"の開発記録をまとめました。第16回VRフェスでは来場者の皆さんに体験してもらったり私が演奏している様子を見てもらったりして、面白いという声をたくさんいただけて嬉しかったです。また、第16回Vアカオーディションではありがたいことに、技術的観点で高い評価だった作品に贈られる「技術賞」と、受講生間で高い評価だった作品に贈られる「Student賞」を受賞することができました!


第16回Vアカオーディションにて技術賞受賞

今後も本プロジェクトは発展させていきたいと思います。そして、AR Guitar Effectorを使ったライブパフォーマンスの実践として、2025/3/25にBuzzFront Yokohamaで開催される「Iwaken Lab. Performers Fest」に出演します!「Immersive Live with AR Guitar」という演目で、MR技術を使った没入型演出と組み合わせたギターパフォーマンスを披露する予定です。XRなどのテクノロジーを活用した音楽表現・ライブ演出にご興味がある方はぜひお越しください!

https://lu.ma/9o0wuz9n?locale=ja&tk=dSOAms

参考記事

https://1planet.co.jp/tech-blog/applevisionpro-oneplanet-visionos2-object-tracking

https://1planet.co.jp/tech-blog/applevisionpro-realityview-model-drag

https://1planet.co.jp/tech-blog/applevisionpro-oneplanet-24062601-spatial-tracking

https://zenn.dev/meson_tech_blog/articles/visionos-collision-detection?redirected=1

GitHubで編集を提案

Discussion