libpdのサンプルを見ると端末の音声入力・音声出力を使う例しかなく、実際に出力される音声のPCMデータにアクセスする方法がよくわかりませんでした。
調べてみたところ、PdBase.startAudio()
ではなくPdBase.process()
を使うことでlibpd組み込みの音声入出力ではない、任意の音声データを処理させることができそうだってことがわかったので、これを実装するのに必要な手順をメモがてらまとめてみました。
.pdファイルを適当な場所に配置する
assets内などに保存された.pdファイルをストレージ上の適当な場所に保存します。
たとえば、assets/my_patch.pd
に保存されている.pdファイルをアプリのプライベートなファイル領域に保存するなら以下のような感じで保存できます。
private fun preparePatch(): File {
val patchName = "my_patch.pd"
val patchFile = File(context.filesDir, patchName)
if (!patchFile.exists()) {
context.assets.open(patchName).copyTo(patchFile.outputStream())
}
return patchFile
}
.pdファイルを開く
libpdで.pdファイルを開きます。開くのに成功するとハンドルが返されます。ハンドルは、後でlibpdを終了させファイルを閉じる時に使用します。
val patchFile: File = ...
val handle = PdBase.openPatch(patchFile)
// ... 不要になったら
PdBase.closePatch(handle)
オーディオを開く
val inChannels = 2 // 入力を使わないなら0
val outChannels = 2
val sampleRate = 44100 // 48000とか、必要に応じて
val handle = PdBase.openAudio(inChannels, outChannels, sampleRate)
// ... 不要になったら
PdBase.closeAudio()
音声処理を開始
PdBase.computeAudio(true) // これをやらないと`dac~`などの`~`系オブジェクトが動作しない
// 入力音声・出力音声データを入れる配列。長さは64の倍数xチャンネル数にする
val tick = 1 // 毎回のループで処理するtick数。1tick=64サンプルの音声を処理する。
val inputAudio = FloatArray(tick * 64 * inChannels)
val outputAudio = FloatArray(tick * 64 * outChannels)
while (!Thread.interrupted()) {
PdBase.pollMidiQueue()
PdBase.pollPdMessageQueue()
Thread.yield()
// TODO inputAudio にマイクなどからの入力音声データを入れる
if (PdBase.process(tick, inputAudio, outputAudio) != 0) {
// PdBase.process error
break
}
// TODO outputAudio の音声データをスピーカーに出力したりする
}
// TODO inputAudio にマイクなどからの入力音声データを入れる
のところは、AudioRecord.read()
で音声入力データを取得して埋めます。
// TODO outputAudio の音声データをスピーカーに出力したりする
のところは、AudioTrack.write()
で音声をスピーカーに出力したりします。
上記は必ずしもこの方法で処理する必要はなく、ネットワークへ流したりネットワークから読み取ったり、ファイルに書き出したりすることも考えられるでしょう。