Skip to content

Commit

Permalink
Merge pull request #42 from pipelined/generics
Browse files Browse the repository at this point in the history
Generic signal types
  • Loading branch information
dudk committed Oct 29, 2023
2 parents fe620a7 + f97223f commit 989618a
Show file tree
Hide file tree
Showing 49 changed files with 745 additions and 4,421 deletions.
38 changes: 38 additions & 0 deletions allocator.go
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
}
}
15 changes: 15 additions & 0 deletions allocator_test.go
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{})
)
}
101 changes: 101 additions & 0 deletions buffer.go
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
}
}
123 changes: 123 additions & 0 deletions buffer_test.go
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},
},
},
)
}())
}
36 changes: 36 additions & 0 deletions channel.go
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)
}
80 changes: 0 additions & 80 deletions channel_floating.go

This file was deleted.

Loading

0 comments on commit 989618a

Please sign in to comment.