1package core
2
3import (
4 "errors"
5 "testing"
6 "unsafe"
7)
8
9// Named constants for SPA data type and flag values (from <spa/buffer/buffer.h>).
10const (
11 spaDataTypeMemPtr uint32 = 2 // SPA_DATA_MemPtr
12 spaDataFlagRW uint32 = 3 // SPA_DATA_FLAG_READABLE | SPA_DATA_FLAG_WRITABLE
13)
14
15// helper to build a channels×frames buffer view for tests.
16// Returns the view, the chunks slice, and the pwBuffer pointer.
17func newTestBufferView(t *testing.T, channels, frames, maxsizeFrames int) (*pwBufferView, []spaChunk, *pwBuffer) {
18 t.Helper()
19 channelData := make([][]float32, channels)
20 channelPtrs := make([]unsafe.Pointer, channels)
21 for ch := 0; ch < channels; ch++ {
22 channelData[ch] = make([]float32, maxsizeFrames)
23 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
24 }
25
26 chunks := make([]spaChunk, channels)
27 datas := make([]spaData, channels)
28 for ch := 0; ch < channels; ch++ {
29 chunks[ch] = spaChunk{}
30 datas[ch] = spaData{
31 Type: spaDataTypeMemPtr,
32 Flags: spaDataFlagRW,
33 Maxsize: uint32(maxsizeFrames * 4),
34 Data: channelPtrs[ch],
35 Chunk: &chunks[ch],
36 }
37 }
38
39 buf := spaBuffer{
40 NDatas: uint32(channels),
41 Datas: unsafe.Pointer(&datas[0]),
42 }
43 pwBuf := pwBuffer{
44 Buffer: &buf,
45 }
46
47 view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
48 if err != nil {
49 t.Fatalf("unexpected error building test buffer: %v", err)
50 }
51 return view, chunks, &pwBuf
52}
53
54// TestNewPWBufferViewWrapsWritablePlanarFloatData verifies that newPWBufferView
55// correctly interprets a C-layout pwBuffer as planar float32 channel data.
56func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
57 const channels = 2
58 const frames = 64
59
60 view, _, _ := newTestBufferView(t, channels, frames, frames)
61
62 pcm := view.PCM()
63 if pcm.Channels != channels {
64 t.Errorf("expected %d channels, got %d", channels, pcm.Channels)
65 }
66 if pcm.Frames != frames {
67 t.Errorf("expected %d frames, got %d", frames, pcm.Frames)
68 }
69 if pcm.Stride != 4 {
70 t.Errorf("expected Stride=4 for planar float32, got %d", pcm.Stride)
71 }
72 if len(pcm.Samples) != channels {
73 t.Fatalf("expected %d sample slices, got %d", channels, len(pcm.Samples))
74 }
75 for ch := 0; ch < channels; ch++ {
76 if len(pcm.Samples[ch]) != frames {
77 t.Errorf("channel %d: expected %d frames, got %d", ch, frames, len(pcm.Samples[ch]))
78 }
79 }
80}
81
82// TestNewPWBufferViewPCMStrideIsSet verifies that PCM() returns a buffer
83// with Stride set to 4 (bytes per float32 for planar layout).
84func TestNewPWBufferViewPCMStrideIsSet(t *testing.T) {
85 const channels = 1
86 const frames = 32
87
88 view, _, _ := newTestBufferView(t, channels, frames, frames)
89
90 pcm := view.PCM()
91 if pcm.Stride != 4 {
92 t.Errorf("PCMBuffer.Stride = %d, want 4 (sizeof float32)", pcm.Stride)
93 }
94}
95
96// TestNewPWBufferViewSharedMemory verifies that writes to the PCMBuffer
97// propagate to the underlying channel data (not a copy).
98func TestNewPWBufferViewSharedMemory(t *testing.T) {
99 const channels = 2
100 const frames = 64
101
102 channelData := make([][]float32, channels)
103 channelPtrs := make([]unsafe.Pointer, channels)
104 for ch := 0; ch < channels; ch++ {
105 channelData[ch] = make([]float32, frames)
106 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
107 }
108
109 chunks := make([]spaChunk, channels)
110 datas := make([]spaData, channels)
111 for ch := 0; ch < channels; ch++ {
112 chunks[ch] = spaChunk{}
113 datas[ch] = spaData{
114 Type: spaDataTypeMemPtr,
115 Flags: spaDataFlagRW,
116 Maxsize: uint32(frames * 4),
117 Data: channelPtrs[ch],
118 Chunk: &chunks[ch],
119 }
120 }
121
122 buf := spaBuffer{
123 NDatas: uint32(channels),
124 Datas: unsafe.Pointer(&datas[0]),
125 }
126 pwBuf := pwBuffer{Buffer: &buf}
127
128 view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
129 if err != nil {
130 t.Fatalf("unexpected error: %v", err)
131 }
132
133 pcm := view.PCM()
134 pcm.Samples[0][0] = 1.0
135 if channelData[0][0] != 1.0 {
136 t.Error("PCM buffer does not share memory with underlying channel data — write did not propagate")
137 }
138
139 // Commit and verify chunk updates.
140 const commitFrames = 32
141 view.Commit(commitFrames)
142
143 if pwBuf.Size != uint64(commitFrames) {
144 t.Errorf("pwBuffer.Size = %d, want %d", pwBuf.Size, commitFrames)
145 }
146 for ch := 0; ch < channels; ch++ {
147 if chunks[ch].Size != uint32(commitFrames*4) {
148 t.Errorf("chunk[%d].Size = %d, want %d", ch, chunks[ch].Size, commitFrames*4)
149 }
150 if chunks[ch].Stride != 4 {
151 t.Errorf("chunk[%d].Stride = %d, want 4", ch, chunks[ch].Stride)
152 }
153 }
154}
155
156// TestNewPWBufferViewRejectsNilPointer verifies that a nil buffer pointer
157// returns ErrNilBufferPointer.
158func TestNewPWBufferViewRejectsNilPointer(t *testing.T) {
159 _, err := newPWBufferView(nil, 2, 64)
160 if !errors.Is(err, ErrNilBufferPointer) {
161 t.Fatalf("expected ErrNilBufferPointer, got %v", err)
162 }
163}
164
165// TestNewPWBufferViewRejectsChannelMismatch verifies that a buffer whose
166// NDatas does not match the requested channel count returns ErrChannelMismatch.
167func TestNewPWBufferViewRejectsChannelMismatch(t *testing.T) {
168 singleData := spaData{
169 Type: spaDataTypeMemPtr,
170 Maxsize: 256,
171 Data: unsafe.Pointer(new(float32)),
172 Chunk: &spaChunk{},
173 }
174 buf := spaBuffer{
175 NDatas: 1, // only 1 channel
176 Datas: unsafe.Pointer(&singleData),
177 }
178 pwBuf := pwBuffer{Buffer: &buf}
179
180 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64) // requesting 2 channels
181 if !errors.Is(err, ErrChannelMismatch) {
182 t.Fatalf("expected ErrChannelMismatch, got %v", err)
183 }
184}
185
186// TestNewPWBufferViewRejectsUnmappedData verifies that a nil data pointer
187// in a spaData slot returns ErrUnmappedData.
188func TestNewPWBufferViewRejectsUnmappedData(t *testing.T) {
189 datas := make([]spaData, 2)
190 datas[0] = spaData{
191 Type: spaDataTypeMemPtr,
192 Maxsize: 256,
193 Data: nil, // nil — should trigger ErrUnmappedData
194 Chunk: &spaChunk{},
195 }
196 datas[1] = spaData{
197 Type: spaDataTypeMemPtr,
198 Maxsize: 256,
199 Data: unsafe.Pointer(new(float32)),
200 Chunk: &spaChunk{},
201 }
202
203 buf := spaBuffer{
204 NDatas: 2,
205 Datas: unsafe.Pointer(&datas[0]),
206 }
207 pwBuf := pwBuffer{Buffer: &buf}
208
209 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)
210 if !errors.Is(err, ErrUnmappedData) {
211 t.Fatalf("expected ErrUnmappedData, got %v", err)
212 }
213}
214
215// TestNewPWBufferViewRejectsNilSpaBuffer verifies that a nil spa_buffer
216// pointer inside the pwBuffer returns ErrNilBufferPointer.
217func TestNewPWBufferViewRejectsNilSpaBuffer(t *testing.T) {
218 pwBuf := pwBuffer{
219 Buffer: nil,
220 }
221
222 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)
223 if !errors.Is(err, ErrNilBufferPointer) {
224 t.Fatalf("expected ErrNilBufferPointer, got %v", err)
225 }
226}
227
228// TestNewPWBufferViewRejectsOversizedFrames verifies that requesting more
229// frames than the buffer can hold (based on Maxsize) returns ErrFrameOverflow.
230func TestNewPWBufferViewRejectsOversizedFrames(t *testing.T) {
231 const channels = 2
232 const maxFrames = 32
233 const requestedFrames = 64 // exceeds maxFrames
234
235 channelData := make([][]float32, channels)
236 channelPtrs := make([]unsafe.Pointer, channels)
237 for ch := 0; ch < channels; ch++ {
238 channelData[ch] = make([]float32, maxFrames)
239 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
240 }
241
242 datas := make([]spaData, channels)
243 for ch := 0; ch < channels; ch++ {
244 datas[ch] = spaData{
245 Type: spaDataTypeMemPtr,
246 Maxsize: uint32(maxFrames * 4), // only room for 32 frames
247 Data: channelPtrs[ch],
248 Chunk: &spaChunk{},
249 }
250 }
251
252 buf := spaBuffer{
253 NDatas: uint32(channels),
254 Datas: unsafe.Pointer(&datas[0]),
255 }
256 pwBuf := pwBuffer{Buffer: &buf}
257
258 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, requestedFrames)
259 if !errors.Is(err, ErrFrameOverflow) {
260 t.Fatalf("expected ErrFrameOverflow, got %v", err)
261 }
262}
263
264// TestCommitUpdatesSizeAndStride verifies that Commit writes the correct
265// byte sizes and strides for planar float32 data.
266func TestCommitUpdatesSizeAndStride(t *testing.T) {
267 const channels = 2
268 const frames = 128
269
270 view, chunks, pwBuf := newTestBufferView(t, channels, frames, frames)
271
272 // Write some audio data to the PCM buffer.
273 const writtenFrames = 96
274 pcm := view.PCM()
275 for ch := 0; ch < channels; ch++ {
276 for f := 0; f < writtenFrames; f++ {
277 pcm.Samples[ch][f] = float32(f) * 0.5
278 }
279 }
280
281 view.Commit(writtenFrames)
282
283 // Verify byte size = frames × sizeof(float32) per chunk.
284 for ch := 0; ch < channels; ch++ {
285 wantSize := uint32(writtenFrames * 4)
286 if chunks[ch].Size != wantSize {
287 t.Errorf("chunk[%d].Size = %d, want %d", ch, chunks[ch].Size, wantSize)
288 }
289 if chunks[ch].Stride != 4 {
290 t.Errorf("chunk[%d].Stride = %d, want 4", ch, chunks[ch].Stride)
291 }
292 }
293
294 // Verify pwBuffer.Size reflects frame count (not bytes).
295 if pwBuf.Size != uint64(writtenFrames) {
296 t.Errorf("pwBuffer.Size = %d, want %d", pwBuf.Size, writtenFrames)
297 }
298}