【iOS】System Sound Wheel Picker

ひとつの View だけで

その端末の組み込みのシステム音を

確認できたらいいかも、

ということで。

GIF って音声出ませんでした ?



以下、音が出てます。



確認の範囲として、

SystemSoundID は 1 から 4000 まで。

実際に音を並列で鳴らして 0.0025 秒以下の音は無視してます。

非同期の処理はここでも難しい印象を受けます。

👉 【Swift】SystemSoundID 一覧がないのですが hatena-bookmark


iOS シュミレータは、聴きながら録画 + 録音できないのですか ?

Audio MIDI 複数出力装置と仮想デバイス BlackHole を使って、QuickTime で聴きながらの音声付き録画。

なぜできないのか。

聴きながらができないんです。

シュミレータにも、Audio Output の設定があるので、

そこで、個別に BlackHole に向けてもダメ。

複数音声出力 から BlackHole 向きを外してもダメ。

シュミレータを音声付き録画するなら聴きながらはできない。

そういうものなの?

できても良さそうなのに。

Loopback など有料アプリならできるのですか ?

👉 SoundFlowerからBlackHoleに移行してOBS接続おさらい hatena-bookmark


【Swift】非同期関数の直列実行 - withCheckedContinuation

ビルトイン非同期関数の直列処理って自力でやるにはつらいですよね。

まず、ボタンを押して、音を鳴らします。


Button("Play1") {
  AudioServicesPlaySystemSound(1000)
}

純粋なUIスレッドではないところで再生処理がされています。

いわゆる

「UIスレッドと非同期された処理」

ですね。

続いて、


Button("Play2") {
  AudioServicesPlaySystemSound(SystemSoundID(1000))
}

これも同じ音が再生されます。

この組み込みシステム音声を鳴らす関数


func AudioServicesPlaySystemSound(_ inSystemSoundID: SystemSoundID)

👉 AudioServicesPlaySystemSound(_:) | Apple Developer Documentation hatena-bookmark

の引数の型は SystemSoundID ですので直しておきます。

中を見てみると、


/**
    @typedef        SystemSoundID
    @abstract       SystemSoundIDs are created by the System Sound client application
                    for playback of a provided AudioFile.
*/
public typealias SystemSoundID = UInt32

UInt32 の typealias であることが分かります。

この SystemSouldID は他にもたくさんいろいろあります。

が、端末やOSバージョンによって異なるので注意が必要なようですね。


続いて、11個連続再生してみます。


Button("Play3") {
  for i in 1000...1010 {
    AudioServicesPlaySystemSound(SystemSoundID(i))
  }
}

あれ。

残念なことに音が重なって同時に再生されます。

それぞれ、どんな音が鳴っているか分かりません。

非同期処理の関数が並列で処理されているのです。

 

🤔 非同期関数の直列化

連続で順番に音を再生したい。

言い換えれば、

非同期関数の直列処理

をしたいですよね。

まず、音声再生処理終了のタイミングから処理を実行できる

👉 AudioServicesPlaySystemSoundWithCompletion(_:_:) | Apple Developer Documentation hatena-bookmark

を使って音を1つだけ鳴らしてみます。


Button("Play4") {
  AudioServicesPlaySystemSoundWithCompletion(SystemSoundID(1000)) {
    print("completed.")
  }
}

これで、終了のタイミングを取得できます。

続いて、これを連続で実行します。

今どきの Swift では、以下の関数を使うらしいです。

👉 withCheckedContinuation(function:_:) | Apple Developer Documentation hatena-bookmark
👉 withCheckedThrowingContinuation(function:_:) | Apple Developer Documentation hatena-bookmark

これを使って書き換えます。


Button("Play5") {
  Task {
    for i in 1000...1010 {
      await withCheckedContinuation { continuation in
        AudioServicesPlaySystemSoundWithCompletion(SystemSoundID(i)) {
          continuation.resume()
        }
      }
    }
  }
}

クロージャー内は同期処理となって await が必要となりますので Task をかぶせてます。

音声なので、特にスレッドをメインに指定することなどは不要のようです。

以下、それぞれの実行音声動画です。



 

🤔 まとめ

「非同期処理の直列化」は用意されている関数の

👉 withCheckedContinuation(function:_:) | Apple Developer Documentation hatena-bookmark
👉 withCheckedThrowingContinuation(function:_:) | Apple Developer Documentation hatena-bookmark

を使うと良い。