v0.2.0 4c97002

pwbuffer_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close