コメントを投稿するにはログインが必要です
ログインページへ記事ができると、章ごとの要約と、聴きどころへ飛べる目次がここに並びます。いまは音声で聴けます。
万歩計ならぬ、ことばの歩数計アプリ開発案をモジモジと語るのみ。誰か一緒にやってくれないかなー? import AVFoundation import Foundation // MARK: - 発話時間計測クラス (Local-only VAD Engine) class SpeechActivityTracker: NSObject, ObservableObject { private let audioEngine = AVAudioEngine() @Published var totalSpeechSeconds: Double = 0.0 @Published var isSpeaking: Bool = false // 音量しきい値 (環境に合わせて調整) private let speechThreshold: Float = -30.0 func startMonitoring() { let inputNode = audioEngine.inputNode let recordingFormat = inputNode.outputFormat(forBus: 0) // 端末ローカルでマイク入力をキャプチャ (録音データは保存・送信せず即時破棄) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in guard let self = self else { return } // 音量(RMS)の計算 guard let channelData = buffer.floatChannelData?[0] else { return } let channelDataValue = Array(UnsafeBufferPointer(start: channelData, count: Int(buffer.frameLength))) var sum: Float = 0.0 for sample in channelDataValue { sum += sample * sample } let rms = sqrt(sum / Float(buffer.frameLength)) let avgPower = 20 * log10(rms) // dB単位 // 判定処理 DispatchQueue.main.async { if avgPower > self.speechThreshold { self.isSpeaking = true // バッファの長さ分(約0.02秒)を発話時間に加算 let duration = Double(buffer.frameLength) / recordingFormat.sampleRate self.totalSpeechSeconds += duration } else { self.isSpeaking = false } } } do { try audioEngine.start() } catch { print("AudioEngine起動エラー: \(error.localizedDescription)") } } func stopMonitoring() { audioEngine.inputNode.removeTap(onBus: 0) audioEngine.stop() } }