-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from pipelined/generics
Generic signal types
- Loading branch information
Showing
49 changed files
with
745 additions
and
4,421 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package signal | ||
|
||
import ( | ||
"strconv" | ||
) | ||
|
||
type ( | ||
// Allocator defines allocation parameters for signal buffers. | ||
Allocator struct { | ||
Channels int | ||
Length int | ||
Capacity int | ||
} | ||
) | ||
|
||
// Alloc acllocates signal buffers based on provided type parameter. Type parameter also determines | ||
// bit depth of the buffer, ie int8 will be 8 bit depth and int64 is a 64 bit depth. | ||
func Alloc[T SignalTypes](a Allocator) *Buffer[T] { | ||
return &Buffer[T]{ | ||
data: make([]T, a.Channels*a.Length, a.Channels*a.Capacity), | ||
channels: channels(a.Channels), | ||
bitDepth: bitDepth(getBitDepth[T]()), | ||
} | ||
} | ||
|
||
// maxBitDebth returns a maximum bit debth for a given type, ie. 64 bits for int64 and uint64. | ||
func getBitDepth[T SignalTypes]() BitDepth { | ||
switch any(new(T)).(type) { | ||
case *int8, *uint8: | ||
return BitDepth8 | ||
case *int16, *uint16: | ||
return BitDepth16 | ||
case *int32, *uint32, *float32: | ||
return BitDepth32 | ||
default: | ||
return strconv.IntSize | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package signal_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"pipelined.dev/signal" | ||
) | ||
|
||
func TestAlloc(t *testing.T) { | ||
var ( | ||
_ *signal.Buffer[float64] = signal.Alloc[float64](signal.Allocator{}) | ||
_ *signal.Buffer[float32] = signal.Alloc[float32](signal.Allocator{}) | ||
_ *signal.Buffer[int64] = signal.Alloc[int64](signal.Allocator{}) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package signal | ||
|
||
import ( | ||
"math" | ||
) | ||
|
||
// Buffer is a buffer that contains digital signal of given type. | ||
// Each type is associated with certain bit depth, ie: int8 is 8 bits, float32 is 32 bits. | ||
type Buffer[T SignalTypes] struct { | ||
channels | ||
data []T | ||
bitDepth | ||
} | ||
|
||
// Slice the Buffer with respect to channels. | ||
func (b *Buffer[T]) Slice(start, end int) *Buffer[T] { | ||
start = b.BufferIndex(0, start) | ||
end = b.BufferIndex(0, end) | ||
return &Buffer[T]{ | ||
channels: b.channels, | ||
data: b.data[start:end], | ||
bitDepth: b.bitDepth, | ||
} | ||
} | ||
|
||
// AppendSample appends sample at the end of the Buffer. | ||
// Sample is not appended if Buffer capacity is reached. | ||
func (b *Buffer[T]) AppendSample(v T) { | ||
if len(b.data) == cap(b.data) { | ||
return | ||
} | ||
b.data = append(b.data, v) | ||
} | ||
|
||
// SetSample sets sample value for provided index. | ||
func (b *Buffer[T]) SetSample(i int, v T) { | ||
b.data[i] = v | ||
} | ||
|
||
// Capacity returns capacity of a single channel. | ||
func (b *Buffer[T]) Capacity() int { | ||
if b.channels == 0 { | ||
return 0 | ||
} | ||
return cap(b.data) / int(b.channels) | ||
} | ||
|
||
// Length returns length of a single channel. | ||
func (b *Buffer[T]) Length() int { | ||
if b.channels == 0 { | ||
return 0 | ||
} | ||
return int(math.Ceil(float64(len(b.data)) / float64(b.channels))) | ||
} | ||
|
||
// Cap returns capacity of whole Buffer. | ||
func (b *Buffer[T]) Cap() int { | ||
return cap(b.data) | ||
} | ||
|
||
// Len returns length of whole Buffer. | ||
func (b *Buffer[T]) Len() int { | ||
return len(b.data) | ||
} | ||
|
||
// Sample returns signal value for provided sample index. | ||
func (b *Buffer[T]) Sample(i int) T { | ||
return b.data[i] | ||
} | ||
|
||
// Append appends [0:Length] samples from src to current Buffer. | ||
// Both buffers must have same number of channels and | ||
// bit depth, otherwise function will panic. | ||
func (dst *Buffer[D]) Append(src *Buffer[D]) { | ||
mustSame(dst.Channels(), src.Channels(), diffChannels) | ||
offset := dst.Len() | ||
if dst.Cap() < dst.Len()+src.Len() { | ||
dst.data = append(dst.data, make([]D, src.Len())...) | ||
} else { | ||
dst.data = dst.data[:dst.Len()+src.Len()] | ||
} | ||
for i := 0; i < src.Len(); i++ { | ||
dst.SetSample(i+offset, src.Sample(i)) | ||
} | ||
alignCapacity(&dst.data, dst.Channels(), dst.Cap()) | ||
} | ||
|
||
// Channel returns a single channel of signal Buffer. It behaves exactly as | ||
// Floating Buffer, but Append and AppendSample cause panic. | ||
func (b *Buffer[T]) Channel(c int) C[T] { | ||
return C[T]{ | ||
Buffer: b, | ||
channel: c, | ||
} | ||
} | ||
|
||
func (b *Buffer[T]) clear() { | ||
for i := range b.data { | ||
b.data[i] = 0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package signal_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"golang.org/x/exp/constraints" | ||
"pipelined.dev/signal" | ||
) | ||
|
||
func TestFloat(t *testing.T) { | ||
t.Run("float64", testGenericFloat[float64]()) | ||
t.Run("float32", testGenericFloat[float32]()) | ||
} | ||
|
||
type expectedGeneric[T constraints.Float] struct { | ||
length int | ||
capacity int | ||
data [][]T | ||
} | ||
|
||
func testGenericFloat[T constraints.Float]() func(t *testing.T) { | ||
input := signal.Alloc[T](signal.Allocator{ | ||
Channels: 3, | ||
Capacity: 3, | ||
Length: 3, | ||
}) | ||
signal.WriteStriped( | ||
[][]T{ | ||
{}, | ||
{1, 2, 3}, | ||
{11, 12, 13, 14}, | ||
}, | ||
input, | ||
) | ||
r := signal.Alloc[T](signal.Allocator{ | ||
Channels: 3, | ||
Capacity: 2, | ||
}) | ||
r.Append(input.Slice(1, 3)) | ||
ex := expectedGeneric[T]{ | ||
length: 2, | ||
capacity: 2, | ||
data: [][]T{ | ||
{0, 0}, | ||
{2, 3}, | ||
{12, 13}, | ||
}, | ||
} | ||
return func(t *testing.T) { | ||
t.Helper() | ||
if ex.capacity != 0 { | ||
assertEqual(t, "capacity", r.Capacity(), ex.capacity) | ||
} | ||
if ex.length != 0 { | ||
assertEqual(t, "length", r.Length(), ex.length) | ||
} | ||
assertEqual(t, "slices", resultGeneric[T](r), ex.data) | ||
} | ||
} | ||
|
||
func resultGeneric[T constraints.Float](src *signal.Buffer[T]) [][]T { | ||
result := make([][]T, src.Channels()) | ||
for i := range result { | ||
result[i] = make([]T, src.Length()) | ||
} | ||
signal.ReadStriped(src, result) | ||
return result | ||
} | ||
|
||
func TestSlice(t *testing.T) { | ||
alloc := signal.Allocator{ | ||
Channels: 3, | ||
Capacity: 3, | ||
Length: 3, | ||
} | ||
t.Run("floating", func() func(t *testing.T) { | ||
input := signal.Alloc[float64](alloc) | ||
signal.WriteStriped[float64, float64]( | ||
[][]float64{ | ||
{}, | ||
{1, 2, 3}, | ||
{11, 12, 13, 14}, | ||
}, | ||
input, | ||
) | ||
return testOk[float64]( | ||
input.Slice(1, 3), | ||
expected{ | ||
length: 2, | ||
capacity: 2, | ||
data: [][]float64{ | ||
{0, 0}, | ||
{2, 3}, | ||
{12, 13}, | ||
}, | ||
}, | ||
) | ||
}()) | ||
|
||
t.Run("slice same size", func() func(t *testing.T) { | ||
input := signal.Alloc[float64](alloc) | ||
signal.WriteStriped[float64, float64]( | ||
[][]float64{ | ||
{}, | ||
{1, 2}, | ||
{11, 12, 13}, | ||
}, | ||
input, | ||
) | ||
return testOk[float64]( | ||
input.Slice(0, 3), | ||
expected{ | ||
length: 3, | ||
capacity: 3, | ||
data: [][]float64{ | ||
{0, 0, 0}, | ||
{1, 2, 0}, | ||
{11, 12, 13}, | ||
}, | ||
}, | ||
) | ||
}()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package signal | ||
|
||
type C[T SignalTypes] struct { | ||
Buffer *Buffer[T] | ||
channel int | ||
} | ||
|
||
// BufferIndex returns sample index in the channel of signal Buffer. | ||
func (c C[T]) BufferIndex(channel int, index int) int { | ||
return c.channel * index | ||
} | ||
|
||
// Channels always returns 1. | ||
func (c C[T]) Channels() int { | ||
return 1 | ||
} | ||
|
||
// Capacity returns capacity of the channel. | ||
func (c C[T]) Capacity() int { | ||
return c.Buffer.Capacity() | ||
} | ||
|
||
// Length returns length of the channel. | ||
func (c C[T]) Length() int { | ||
return c.Buffer.Length() | ||
} | ||
|
||
// Sample returns signal value for provided channel and index. | ||
func (c C[T]) Sample(index int) T { | ||
return c.Buffer.Sample(index * c.channel) | ||
} | ||
|
||
// SetSample sets sample value for provided index. | ||
func (c C[T]) SetSample(index int, s T) { | ||
c.Buffer.SetSample(c.Buffer.BufferIndex(c.channel, index), s) | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.