Version: swift-tools-version: 6.0
定義
DSPComplex
DSPComplexの定義は以下です。
public struct DSPComplex {
public init()
public init(real: Float, imag: Float)
public var real: Float
public var imag: Float
}構造体として、実部と虚部の値がメンバ変数として定義がされています。
つまり、配列で表した場合では、[Real_0, Imag_0, Real, 1, Imag_1, …, Real_N-1, Imag_N-1]
のように実部と虚部が交互に並んでいる構造です。このように、性質が異なるデータを交互に並ぶような構造をインターリーブされたデータと呼びます。
一方で、非インターリーブのデータというのは、この複素数の場合では、[Real_0, …, Real_N-1, Imag_0, …, Imag_N-1]のように、 同じ性質を持つデータが全て連続して並んでから、また別の性質を持つデータが連続して並ぶような形式です。
オーディオ信号などを取り扱うとき、インターリーブ・非インターリーブされたデータという概念はよく出ます。例えば、オーディオデータだと[1ch, 2ch, 3ch, 4ch]のように1つの同時刻で取得されたサンプルが全てのチャンネルがひとまとまりのパケットで送信されることがよくあります。これを[1ch_0, 1ch_1, …, 1ch_N-1], [2ch_0, 2ch_1, …, 2ch_N-1], [4ch_0, 4ch_1, …, 4ch_N-1]のようにチャンネルごとにまとめて処理をするということがよくします。
DSPSplictComplex
DSPSplitComplexの定義は以下です。
public struct DSPSplitComplex {
public init(realp: UnsafeMutablePointer<Float>, imagp: UnsafeMutablePointer<Float>)
public var realp: UnsafeMutablePointer<Float>
public var imagp: UnsafeMutablePointer<Float>
}定義を見ると、UnsafeMutablePointer<Flaot>となっているため、配列のポインタを構造体のメンバ変数として、それぞれ実部と虚部で持っていることがわかります。
これは先ほどの非インターリーブの構造を取るためにこのような構成になっています。vDSPでは、SIMD演算を利用した高速化のため、この構造を関数の引数として求めるものがあります。
サンプルコード
// MARK: - DSPComplex と DSPSplitComplex の動作確認
// 実数部・虚数部を持つ複素数列を作成
var complexValues: [DSPComplex] = [
DSPComplex(real: 1.0, imag: 0.5),
DSPComplex(real: 2.0, imag: 1.0),
DSPComplex(real: 3.0, imag: 1.5),
DSPComplex(real: 4.0, imag: 2.0)
]
print("=== DSPComplex 配列 ===")
for (i, c) in complexValues.enumerated() {
print("Index \(i): real=\(c.real), imag=\(c.imag)")
}
var real: [Float] = [1, 2, 3, 4]
var imag: [Float] = [10, 20, 30, 40]
print("=== DSPSplitComplex 配列 ===")
real.withUnsafeMutableBufferPointer { realPtr in
imag.withUnsafeMutableBufferPointer { imagPtr in
// DSPSplitComplexを作成
var splitComplex = DSPSplitComplex(realp: realPtr.baseAddress!, imagp: imagPtr.baseAddress!)
// real, imagの内容を確認
for i in 0..<realPtr.count {
print("Index \(i): real=\(splitComplex.realp[i]), imag=\(splitComplex.imagp[i])")
}
}
}
出力結果
=== DSPComplex 配列 ===
Index 0: real=1.0, imag=0.5
Index 1: real=2.0, imag=1.0
Index 2: real=3.0, imag=1.5
Index 3: real=4.0, imag=2.0
=== DSPSplitComplex 配列 ===
Index 0: real=1.0, imag=10.0
Index 1: real=2.0, imag=20.0
Index 2: real=3.0, imag=30.0
Index 3: real=4.0, imag=40.0
変換
DSPCompex(インターリーブ)→ DSPSplictComplex(非インターリーブ)
extern void vDSP_ctoz(const DSPComplex * __C,
vDSP_Stride __IC,
const DSPSplitComplex * __Z,
vDSP_Stride __IZ,
vDSP_Length __N);
- * __C: 変換対象のDSPComplex型のポインタで入力ベクトル
- __IC: 入力ベクトルの実部の要素間の距離、基本的には2を設定する
- * __Z: 変換後のDSPSplitComplex型のポインタで出力ベクトル
- _IZ: 出力ベクトルの要素間の距離
- __N: 変換する複素数の要素数
__ICはDSPComplex型の配列は一般的に、[(real_0, imag_0), (real_1, image_1), …, (real_N-1, imag_N-1) ]のように並んでいるので、2を設定すればいいです。それを
[real_0, …, real_N-1], [imag_0, imag_N-1]のように2つの配列に分割するのがDSPSplitComplexなので、_IZも基本的には1を使います。
間引きをしたい場合や、出力先を飛び飛びにしたい場合など特別な理由がない限り__ICは2, __IZは1になります。
サンプルコード DSPCompex → DSPSplictComplex
// MARK: - DSPComplex → DSPSplitComplex 変換
// DSPSplitComplex用のバッファを準備
var complexValues2: [DSPComplex] = [
DSPComplex(real: 1.0, imag: 0.5),
DSPComplex(real: 2.0, imag: 1.0),
DSPComplex(real: 3.0, imag: 1.5),
DSPComplex(real: 4.0, imag: 2.0)
]
var realParts = [Float](repeating: 0, count: complexValues2.count)
var imagParts = [Float](repeating: 0, count: complexValues2.count)
print("=== DSPComplex → DSPSplitComplex 変換前 ===")
print("realPart = \(realParts)")
print("imagParts = \(imagParts)")
complexValues2.withUnsafeBufferPointer { complexPtr in
realParts.withUnsafeMutableBufferPointer { realPtr in
imagParts.withUnsafeMutableBufferPointer { imagPtr in
var splitComplex = DSPSplitComplex(realp: realPtr.baseAddress!, imagp: imagPtr.baseAddress!)
vDSP_ctoz(complexPtr.baseAddress!,
2,
&splitComplex,
1,
4)
}
}
}
print("=== DSPComplex → DSPSplitComplex 変換後 ===")
print("realPart = \(realParts)")
print("imagParts = \(imagParts)")
出力結果
=== DSPComplex → DSPSplitComplex 変換前 ===
realPart = [0.0, 0.0, 0.0, 0.0]
imagParts = [0.0, 0.0, 0.0, 0.0]
=== DSPComplex → DSPSplitComplex 変換後 ===
realPart = [1.0, 2.0, 3.0, 4.0]
imagParts = [0.5, 1.0, 1.5, 2.0]
DSPSplictComplex → DSPCompex
extern void vDSP_ztoc(const DSPSplitComplex * __Z,
vDSP_Stride __IZ,
DSPComplex * __C,
vDSP_Stride __IC,
vDSP_Length __N);- * __Z: 変換対象のDSPSplitComplex型のポインタで入力ベクトル
- __IZ: 入力ベクターの要素間の距離、基本的には1を設定する
- * __C: 変換後のDSPComplex型のポインタで出力ベクトル
- _IC: 出力ベクトルの実部の要素間の距離、基本的には2を設定する
- __N: 変換する複素数の要素数
DSPSplictComplex → DSPCompex サンプルコード
// MARK: - DSPSplitComplex → DSPComplex 変換
var realParts:[Float] = [1.0, 2.0, 3.0, 4.0]
var imagParts:[Float] = [5.0, 6.0, 7.0, 8.0]
var restoredComplex = [DSPComplex](repeating: DSPComplex(), count: realParts.count)
print("=== DSPSplitComplex → DSPComplex変換前 ===")
print("restoredComplex = \(restoredComplex)")
restoredComplex.withUnsafeMutableBufferPointer { complexPtr in
realParts.withUnsafeMutableBufferPointer { realPtr in
imagParts.withUnsafeMutableBufferPointer { imagPtr in
var splitComplex = DSPSplitComplex(realp: realPtr.baseAddress!, imagp: imagPtr.baseAddress!)
vDSP_ztoc(&splitComplex, 1, complexPtr.baseAddress!, 2, 4)
}
}
}
print("=== DSPSplitComplex → DSPComplex変換後 ===")
print("restoredComplex = \(restoredComplex)")出力結果
=== DSPSplitComplex → DSPComplex変換前 ===
restoredComplex = [__C.DSPComplex(real: 0.0, imag: 0.0), __C.DSPComplex(real: 0.0, imag: 0.0), __C.DSPComplex(real: 0.0, imag: 0.0), __C.DSPComplex(real: 0.0, imag: 0.0)]
=== DSPSplitComplex → DSPComplex変換後 ===
restoredComplex = [__C.DSPComplex(real: 1.0, imag: 5.0), __C.DSPComplex(real: 2.0, imag: 6.0), __C.DSPComplex(real: 3.0, imag: 7.0), __C.DSPComplex(real: 4.0, imag: 8.0)]
コメント