signal

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 16, 2020 License: MIT Imports: 4 Imported by: 12

README

Signal

GoDoc Go Report Card Test codecov

This package provides functionality to manipulate digital signals and its attributes.

It contains structures for various signal types and allows conversions from one to another:

  • Fixed-point signed
  • Fixed-point unsigned
  • Floating-point

Signal types have semantics of golang slices - they can be appended or sliced with respect to channels layout.

On top of that, this package was desinged to simplify control on allocations. Check godoc for examples.

Documentation

Overview

Package signal provides primitives for digital signal manipulations. Floating-point, fixed signed and unsigned signal types are supported. This package focuses on allocation optimisations as it's one of the most important aspects for DSP applications.

Basics

Digital signal is a representation of a physical signal that is a sampled and quantized. It is discrete in time and amplitude. When analog signal is converted to digital, it goes through two steps: discretization and quantization. Discretization means that the signal is divided into equal intervals of time, and each interval is represented by a single measurement of amplitude. Quantization means each amplitude measurement is approximated by a value from a finite set.

The finite set of quantization values determines the digital signal representation. It differs from type to type:

floating:   [-1, 1]
signed      [-2^(bitDepth-1), 2^(bitDepth-1)-1]
unsigned    [0, 2^bitDepth-1]

Floating-point signal can exceed this range without loosing signal data, but fixed-point signals will be clipped and meaningful signal values will be lost.

Signal buffer types

In order to allocate any of signal buffers, an allocator should be used. Allocator defines what number of channels and capacity per channel allocated buffers will have:

alloc := signal.Allocator{Channels: 2, Capacity: 512}

This package offers types that represent floating-point and both signed/unsigned fixed-point signal buffers. They implement Floating, Signed and Unsigned interfaces respectively. Internally, signal buffers use a slice of built-in type to hold the data.

Fixed-point buffers require a bit depth to be provided at allocation time. It allows to use the same type to hold values of various bit depths:

alloc := signal.Allocator{Channels: 2, Capacity: 512}
_ = alloc.Int64(signal.BitDepth32)      // int-64 buffer with 32-bits per sample
_ = alloc.Uint32(signal.BitDepth24)     // uint-32 buffer with 24-bits per sample
_ = alloc.Float64()                     // float-64 buffer

Signal buffers have semantics of golang-slices - they can be sliced or apended one to another. All operations respect number of channels within buffer, so slicing and appending always happens for all channels.

Write/Read values

There are multiple ways to write/read values from the signal buffers.

WriteT/ReadT functions allows to write or read the data in the format of single slice, where samples for different channels are interleaved:

[]T{R, L, R, L}

WriteStripedT/ReadStripedT functions, on the other hand, can use slice of slices, where each nested slice represents a single channel of signal:

[][]T{{R, R}, {L, L}}

It's possible to append samples to the buffers using AppendSample fucntion. However, in order to have more control over allocations, this function won't let the buffer grow beyond it's initial capacity. To achieve this, another buffer needs to be explicitly allocated.

The one can also iterate over signal buffers. Please, refer to examples for more details.

Pooling

This package also provides a pool-backed allocator. It contains a sync.Pool per signal type. It serves the purpose of decreasing a GC during the runtime when many buffers are allocated. The pool operates with the interface types (Signed, Unsigned, Floating) for convenience and alignment with other functions of this package. It is safe for concurrent use by multiple goroutines.

Example (Iterate)

This example demonstrates how to iterate over the buffer.

package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	// allocate int64 buffer with 2 channels and capacity of 8 samples per channel
	buf := signal.Allocator{
		Channels: 2,
		Capacity: 8,
		Length:   4,
	}.Int64(signal.BitDepth64)

	// write striped data
	signal.WriteStripedInt8([][]int8{{1, 1, 1, 1}, {2, 2, 2, 2}}, buf)

	// iterate over buffer interleaved data
	for i := 0; i < buf.Len(); i++ {
		fmt.Printf("%d", buf.Sample(i))
	}

	for c := 0; c < buf.Channels(); c++ {
		fmt.Println()
		for i := 0; i < buf.Length(); i++ {
			fmt.Printf("%d", buf.Sample(buf.BufferIndex(c, i)))
		}
	}

}
Output:

12121212
1111
2222
Example (Pool)

This example demonstrates how to use pool to allocate buffers.

package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	pool := signal.GetPoolAllocator(2, 0, 512)

	// producer allocates new buffers
	produceFunc := func(allocs int, p *signal.PoolAllocator, c chan<- signal.Floating) {
		for i := 0; i < allocs; i++ {
			buf := p.Float64()
			buf.AppendSample(1.0)
			c <- buf
		}
		close(c)
	}
	// consumer processes buffers and puts them back to the pool
	consumeFunc := func(p *signal.PoolAllocator, c <-chan signal.Floating, done chan struct{}) {
		for s := range c {
			fmt.Printf("Length: %d Capacity: %d\n", s.Length(), s.Capacity())
		}
		close(done)
	}

	c := make(chan signal.Floating)
	done := make(chan struct{})
	go produceFunc(10, pool, c)
	go consumeFunc(pool, c, done)
	<-done
}
Output:

Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Length: 1 Capacity: 512
Example (ReadWrite)

This example demonstrates how read and write data to the buffer.

package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	var output []int

	// allocate int64 buffer with 2 channels and capacity of 8 samples per channel
	buf := signal.Allocator{
		Channels: 2,
		Capacity: 8,
		Length:   8,
	}.Int64(signal.BitDepth64)

	// write striped data
	signal.WriteStripedInt8([][]int8{{1, 1, 1, 1}, {2, 2, 2, 2}}, buf.Slice(0, 4))
	// write interleaved data
	signal.WriteInt16([]int16{11, 22, 11, 22, 11, 22, 11, 22}, buf.Slice(4, 8))

	output = make([]int, 16)    // reset output
	signal.ReadInt(buf, output) // read data into output
	fmt.Println(output)

	output = make([]int, 16)                // reset output
	signal.ReadInt(buf.Slice(0, 0), output) // reset buffer length to 0 and read data into output
	fmt.Println(output)

}
Output:

[1 2 1 2 1 2 1 2 11 22 11 22 11 22 11 22]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsFloating added in v0.8.0

func AsFloating(s Signal, dst Floating) (read int)

AsFloating allows to convert arbitrary signal type to floating.

func AsSigned added in v0.8.0

func AsSigned(s Signal, dst Signed) (read int)

AsSigned allows to convert arbitrary signal type to signed.

func AsUnsigned added in v0.8.0

func AsUnsigned(s Signal, dst Unsigned) (read int)

AsUnsigned allows to convert arbitrary signal type to unsigned.

func ChannelLength added in v0.7.0

func ChannelLength(sliceLen, channels int) int

ChannelLength calculates a channel length for provided buffer length and number of channels.

func ClearPoolAllocatorCache added in v0.9.0

func ClearPoolAllocatorCache()

ClearPoolAllocatorCache resets internal cache of pools and makes existing pools available for GC. One good use case might be when the application changes global buffer size.

func FloatingAsFloating added in v0.6.0

func FloatingAsFloating(src, dst Floating) int

FloatingAsFloating appends floating-point samples to the floating-point destination buffer. Both buffers must have the same number of channels, otherwise function will panic. Returns a number of samples written per channel.

Example
package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}

	// 64-bit floating to 32-bit floating signal
	f64, f32 := alloc.Float64(), alloc.Float32()
	// write float32 values to input
	signal.WriteFloat64(
		[]float64{
			1.5,
			1,
			0,
			-1,
			-1.5,
		},
		f64,
	)
	// convert float32 input to float64 output
	signal.FloatingAsFloating(f64, f32)

	result := make([]float32, values)
	// read result
	signal.ReadFloat32(f32, result)
	fmt.Println(result)
}
Output:

[1.5 1 0 -1 -1.5]

func FloatingAsSigned added in v0.6.0

func FloatingAsSigned(src Floating, dst Signed) int

FloatingAsSigned converts floating-point samples into signed fixed-point and appends them to the destination buffer. The floating sample range [-1,1] is mapped to signed [-2^(bitDepth-1), 2^(bitDepth-1)-1]. Floating values beyond the range will be clipped. Buffers must have the same number of channels, otherwise function will panic. Returns a number of samples written per channel.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 7
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}

	// 64-bit floating to 64-bit signed signal
	f64, i64 := alloc.Float64(), alloc.Int64(signal.BitDepth64)
	// write float64 values to input
	signal.WriteFloat64(
		[]float64{
			math.Nextafter(1, 2),
			1,
			math.Nextafter(1, 0),
			0,
			math.Nextafter(-1, 0),
			-1,
			math.Nextafter(-1, -2),
		},
		f64,
	)
	// convert floating input to signed output
	signal.FloatingAsSigned(f64, i64)

	result := make([]int64, values)
	// read result
	signal.ReadInt64(i64, result)
	fmt.Println(result)
}
Output:

[9223372036854775807 9223372036854775807 9223372036854774784 0 -9223372036854774784 -9223372036854775808 -9223372036854775808]

func FloatingAsUnsigned added in v0.6.0

func FloatingAsUnsigned(src Floating, dst Unsigned) int

FloatingAsUnsigned converts floating-point samples into unsigned fixed-point and appends them to the destination buffer. The floating sample range [-1,1] is mapped to unsigned [0, 2^bitDepth-1]. Floating values beyond the range will be clipped. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 7
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}

	// 64-bit floating to 64-bit unsigned signal
	f64, u64 := alloc.Float64(), alloc.Uint64(signal.BitDepth64)
	// write float64 values to input
	signal.WriteFloat64(
		[]float64{
			math.Nextafter(1, 2),
			1,
			math.Nextafter(1, 0),
			0,
			math.Nextafter(-1, 0),
			-1,
			math.Nextafter(-1, -2),
		},
		f64,
	)
	// convert floating input to unsigned output
	signal.FloatingAsUnsigned(f64, u64)

	result := make([]uint64, values)
	// read result
	signal.ReadUint64(u64, result)
	fmt.Println(result)
}
Output:

[18446744073709551615 18446744073709551615 18446744073709550592 9223372036854775808 1024 0 0]

func ReadFloat32 added in v0.6.0

func ReadFloat32(src Floating, dst []float32) int

ReadFloat32 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadFloat64 added in v0.6.0

func ReadFloat64(src Floating, dst []float64) int

ReadFloat64 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadInt added in v0.6.0

func ReadInt(src Signed, dst []int) int

ReadInt reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadInt16 added in v0.6.0

func ReadInt16(src Signed, dst []int16) int

ReadInt16 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadInt32 added in v0.6.0

func ReadInt32(src Signed, dst []int32) int

ReadInt32 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadInt64 added in v0.6.0

func ReadInt64(src Signed, dst []int64) int

ReadInt64 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadInt8 added in v0.6.0

func ReadInt8(src Signed, dst []int8) int

ReadInt8 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadStripedFloat32 added in v0.6.0

func ReadStripedFloat32(src Floating, dst [][]float32) (read int)

ReadStripedFloat32 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedFloat64 added in v0.6.0

func ReadStripedFloat64(src Floating, dst [][]float64) (read int)

ReadStripedFloat64 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedInt added in v0.6.0

func ReadStripedInt(src Signed, dst [][]int) (read int)

ReadStripedInt reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedInt16 added in v0.6.0

func ReadStripedInt16(src Signed, dst [][]int16) (read int)

ReadStripedInt16 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedInt32 added in v0.6.0

func ReadStripedInt32(src Signed, dst [][]int32) (read int)

ReadStripedInt32 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedInt64 added in v0.6.0

func ReadStripedInt64(src Signed, dst [][]int64) (read int)

ReadStripedInt64 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedInt8 added in v0.6.0

func ReadStripedInt8(src Signed, dst [][]int8) (read int)

ReadStripedInt8 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedUint added in v0.6.0

func ReadStripedUint(src Unsigned, dst [][]uint) (read int)

ReadStripedUint reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedUint16 added in v0.6.0

func ReadStripedUint16(src Unsigned, dst [][]uint16) (read int)

ReadStripedUint16 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedUint32 added in v0.6.0

func ReadStripedUint32(src Unsigned, dst [][]uint32) (read int)

ReadStripedUint32 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedUint64 added in v0.6.0

func ReadStripedUint64(src Unsigned, dst [][]uint64) (read int)

ReadStripedUint64 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadStripedUint8 added in v0.6.0

func ReadStripedUint8(src Unsigned, dst [][]uint8) (read int)

ReadStripedUint8 reads values from the buffer into provided slice. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, no values for that channel will be read. Returns a number of samples read for the longest channel.

func ReadUint added in v0.6.0

func ReadUint(src Unsigned, dst []uint) int

ReadUint reads values from the buffer into provided slice.

func ReadUint16 added in v0.6.0

func ReadUint16(src Unsigned, dst []uint16) int

ReadUint16 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadUint32 added in v0.6.0

func ReadUint32(src Unsigned, dst []uint32) int

ReadUint32 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadUint64 added in v0.6.0

func ReadUint64(src Unsigned, dst []uint64) int

ReadUint64 reads values from the buffer into provided slice. Returns number of samples read per channel.

func ReadUint8 added in v0.6.0

func ReadUint8(src Unsigned, dst []uint8) int

ReadUint8 reads values from the buffer into provided slice. Returns number of samples read per channel.

func Scale added in v0.6.0

func Scale(high, low BitDepth) int64

Scale returns scale for bit depth requantization.

func SignedAsFloating added in v0.6.0

func SignedAsFloating(src Signed, dst Floating) int

SignedAsFloating converts signed fixed-point samples into floating-point and appends them to the destination buffer. The signed sample range [-2^(bitDepth-1), 2^(bitDepth-1)-1] is mapped to floating [-1,1]. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}

	// 8-bit signed to 64-bit floating signal
	i8, f64 := alloc.Int8(signal.BitDepth8), alloc.Float64()
	// write int8 values to input
	signal.WriteInt8(
		[]int8{
			math.MaxInt8,
			math.MaxInt8 - 1,
			0,
			math.MinInt8 + 1,
			math.MinInt8,
		},
		i8,
	)
	// convert signed input to signed output
	signal.SignedAsFloating(i8, f64)

	result := make([]float64, values)
	// read output to the result
	signal.ReadFloat64(f64, result)
	fmt.Println(result)
}
Output:

[1 0.9921259842519685 0 -0.9921875 -1]

func SignedAsSigned added in v0.6.0

func SignedAsSigned(src, dst Signed) int

SignedAsSigned appends signed fixed-point samples to the signed fixed-point destination buffer. The samples are quantized to the destination bit depth. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}
	result := make([]int64, values)

	// downscale 64-bit signed to 8-bit signed
	{
		i64, i8 := alloc.Int64(signal.BitDepth64), alloc.Int8(signal.BitDepth8)
		// write int64 values to input
		signal.WriteInt64(
			[]int64{
				math.MaxInt64,
				math.MaxInt64 / 2,
				0,
				math.MinInt64 / 2,
				math.MinInt64,
			},
			i64,
		)
		// convert signed input to signed output
		signal.SignedAsSigned(i64, i8)
		// read output to the result
		signal.ReadInt64(i8, result)
		fmt.Println(result)
	}

	// upscale signed 8-bit to signed 16-bit
	{
		i8, i64 := alloc.Int8(signal.BitDepth8), alloc.Int64(signal.BitDepth16)
		// write int8 values to input
		signal.WriteInt8([]int8{math.MaxInt8, math.MaxInt8 / 2, 0, math.MinInt8 / 2, math.MinInt8}, i8)
		// convert signed input to signed output
		signal.SignedAsSigned(i8, i64)
		// read output to the result
		signal.ReadInt64(i64, result)
		fmt.Println(result)
	}
}
Output:

[127 63 0 -64 -128]
[32767 16383 0 -16384 -32768]

func SignedAsUnsigned added in v0.6.0

func SignedAsUnsigned(src Signed, dst Unsigned) int

SignedAsUnsigned converts signed fixed-point samples into unsigned fixed-point and appends them to the destination buffer. The samples are quantized to the destination bit depth. The signed sample range [-2^(bitDepth-1), 2^(bitDepth-1)-1] is mapped to unsigned [0, 2^bitDepth-1]. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}
	result := make([]uint64, values)

	// downscale 64-bit signed to 8-bit unsigned
	{
		i64, u64 := alloc.Int64(signal.BitDepth64), alloc.Uint64(signal.BitDepth8)
		// write int values to input
		signal.WriteInt64(
			[]int64{
				math.MaxInt64,
				math.MaxInt64 / 2,
				0,
				math.MinInt64 / 2,
				math.MinInt64,
			},
			i64,
		)
		// convert signed input to unsigned output
		signal.SignedAsUnsigned(i64, u64)
		// read output to the result
		signal.ReadUint64(u64, result)
		fmt.Println(result)
	}

	// upscale 8-bit signed to 16-bit unsigned
	{
		i64, u64 := alloc.Int64(signal.BitDepth8), alloc.Uint64(signal.BitDepth16)
		// write int values to input
		signal.WriteInt64(
			[]int64{
				math.MaxInt8,
				math.MaxInt8 / 2,
				0,
				math.MinInt8 / 2,
				math.MinInt8,
			},
			i64,
		)
		// convert signed input to unsigned output
		signal.SignedAsUnsigned(i64, u64)
		// read output to the result
		signal.ReadUint64(u64, result)
		fmt.Println(result)
	}
}
Output:

[255 191 128 64 0]
[65535 49151 32768 16384 0]

func UnsignedAsFloating added in v0.6.0

func UnsignedAsFloating(src Unsigned, dst Floating) int

UnsignedAsFloating converts unsigned fixed-point samples into floating-point and appends them to the destination buffer. The unsigned sample range [0, 2^bitDepth-1] is mapped to floating [-1,1]. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}
	result := make([]float64, values)

	// unsigned 8-bit to 64-bit floating signal
	u64, f64 := alloc.Uint64(signal.BitDepth8), alloc.Float64()
	// write uint values to input
	signal.WriteUint64(
		[]uint64{
			math.MaxUint8,
			math.MaxUint8 - (math.MaxInt8+1)/2,
			math.MaxInt8 + 1,
			(math.MaxInt8 + 1) / 2,
			0,
		},
		u64,
	)
	// convert unsigned input to floating output
	signal.UnsignedAsFloating(u64, f64)
	// read output to the result
	signal.ReadFloat64(f64, result)
	fmt.Println(result)
}
Output:

[1 0.49606299212598426 0 -0.5039370078740157 -1]

func UnsignedAsSigned added in v0.6.0

func UnsignedAsSigned(src Unsigned, dst Signed) int

UnsignedAsSigned converts unsigned fixed-point samples into signed fixed-point and appends them to the destination buffer. The samples are quantized to the destination bit depth. The unsigned sample range [0, 2^bitDepth-1] is mapped to signed [-2^(bitDepth-1), 2^(bitDepth-1)-1]. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}
	result := make([]int64, values)

	// downscale 64-bit unsigned to 8-bit signed
	{
		u64, i64 := alloc.Uint64(signal.BitDepth64), alloc.Int64(signal.BitDepth8)
		// write uint values to input
		signal.WriteUint64(
			[]uint64{
				math.MaxUint64,
				math.MaxUint64 - (math.MaxInt64+1)/2,
				math.MaxInt64 + 1,
				(math.MaxInt64 + 1) / 2,
				0,
			},
			u64,
		)
		// convert unsigned input to signed output
		signal.UnsignedAsSigned(u64, i64)
		// read output to the result
		signal.ReadInt64(i64, result)
		fmt.Println(result)
	}

	// upscale unsigned 8-bit to signed 16-bit
	{
		u64, i64 := alloc.Uint64(signal.BitDepth8), alloc.Int64(signal.BitDepth16)
		// write uint values to input
		signal.WriteUint64(
			[]uint64{
				math.MaxUint8,
				math.MaxUint8 - (math.MaxInt8+1)/2,
				math.MaxInt8 + 1,
				(math.MaxInt8 + 1) / 2,
				0,
			},
			u64,
		)
		// convert unsigned input to signed output
		signal.UnsignedAsSigned(u64, i64)
		// read output to the result
		signal.ReadInt64(i64, result)
		fmt.Println(result)
	}
}
Output:

[127 63 0 -64 -128]
[32767 16383 0 -16384 -32768]

func UnsignedAsUnsigned added in v0.6.0

func UnsignedAsUnsigned(src, dst Unsigned) int

UnsignedAsUnsigned appends unsigned fixed-point samples to the unsigned fixed-point destination buffer. The samples are quantized to the destination bit depth. Buffers must have the same number of channels, otherwise function will panic.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	values := 5
	alloc := signal.Allocator{
		Channels: 1,
		Capacity: values,
		Length:   values,
	}
	result := make([]uint64, values)

	// downscale 64-bit unsigned to 8-bit unsigned
	{
		u64, u8 := alloc.Uint64(signal.BitDepth64), alloc.Uint8(signal.BitDepth8)
		// write uint values to input
		signal.WriteUint64(
			[]uint64{
				math.MaxUint64,
				math.MaxUint64 - (math.MaxInt64+1)/2,
				math.MaxInt64 + 1,
				(math.MaxInt64 + 1) / 2,
				0,
			},
			u64,
		)
		// convert unsigned input to unsigned output
		signal.UnsignedAsUnsigned(u64, u8)
		// read output to the result
		signal.ReadUint64(u8, result)
		fmt.Println(result)
	}

	// upscale 8-bit unsigned to 16-bit unsigned
	{
		u8, u16 := alloc.Uint8(signal.BitDepth8), alloc.Uint16(signal.BitDepth16)
		// write uint values to input
		signal.WriteUint64(
			[]uint64{
				math.MaxUint8,
				math.MaxUint8 - (math.MaxInt8+1)/2,
				math.MaxInt8 + 1,
				(math.MaxInt8 + 1) / 2,
				0,
			},
			u8,
		)
		// convert unsigned input to unsigned output
		signal.UnsignedAsUnsigned(u8, u16)
		// read output to the result
		signal.ReadUint64(u16, result)
		fmt.Println(result)
	}
}
Output:

[255 191 128 64 0]
[65535 49151 32768 16384 0]

func WriteFloat32 added in v0.6.0

func WriteFloat32(src []float32, dst Floating) int

WriteFloat32 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteFloat64 added in v0.6.0

func WriteFloat64(src []float64, dst Floating) int

WriteFloat64 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteInt added in v0.6.0

func WriteInt(src []int, dst Signed) int

WriteInt writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteInt16 added in v0.6.0

func WriteInt16(src []int16, dst Signed) int

WriteInt16 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteInt32 added in v0.6.0

func WriteInt32(src []int32, dst Signed) int

WriteInt32 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteInt64 added in v0.6.0

func WriteInt64(src []int64, dst Signed) int

WriteInt64 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteInt8 added in v0.6.0

func WriteInt8(src []int8, dst Signed) int

WriteInt8 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteStripedFloat32 added in v0.6.0

func WriteStripedFloat32(src [][]float32, dst Floating) (written int)

WriteStripedFloat32 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedFloat64 added in v0.6.0

func WriteStripedFloat64(src [][]float64, dst Floating) (written int)

WriteStripedFloat64 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedInt added in v0.6.0

func WriteStripedInt(src [][]int, dst Signed) (written int)

WriteStripedInt writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedInt16 added in v0.6.0

func WriteStripedInt16(src [][]int16, dst Signed) (written int)

WriteStripedInt16 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedInt32 added in v0.6.0

func WriteStripedInt32(src [][]int32, dst Signed) (written int)

WriteStripedInt32 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedInt64 added in v0.6.0

func WriteStripedInt64(src [][]int64, dst Signed) (written int)

WriteStripedInt64 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedInt8 added in v0.6.0

func WriteStripedInt8(src [][]int8, dst Signed) (written int)

WriteStripedInt8 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedUint added in v0.6.0

func WriteStripedUint(src [][]uint, dst Unsigned) (written int)

WriteStripedUint writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedUint16 added in v0.6.0

func WriteStripedUint16(src [][]uint16, dst Unsigned) (written int)

WriteStripedUint16 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedUint32 added in v0.6.0

func WriteStripedUint32(src [][]uint32, dst Unsigned) (written int)

WriteStripedUint32 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedUint64 added in v0.6.0

func WriteStripedUint64(src [][]uint64, dst Unsigned) (written int)

WriteStripedUint64 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteStripedUint8 added in v0.6.0

func WriteStripedUint8(src [][]uint8, dst Unsigned) (written int)

WriteStripedUint8 writes values from provided slice into the buffer. The length of provided slice must be equal to the number of channels, otherwise function will panic. Nested slices can be nil, zero values for that channel will be written. Returns a number of samples written for the longest channel.

func WriteUint added in v0.6.0

func WriteUint(src []uint, dst Unsigned) int

WriteUint writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteUint16 added in v0.6.0

func WriteUint16(src []uint16, dst Unsigned) int

WriteUint16 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteUint32 added in v0.6.0

func WriteUint32(src []uint32, dst Unsigned) int

WriteUint32 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteUint64 added in v0.6.0

func WriteUint64(src []uint64, dst Unsigned) int

WriteUint64 writes values from provided slice into the buffer. Returns a number of samples written per channel.

func WriteUint8 added in v0.6.0

func WriteUint8(src []uint8, dst Unsigned) int

WriteUint8 writes values from provided slice into the buffer. Returns a number of samples written per channel.

Types

type Allocator added in v0.6.0

type Allocator struct {
	Channels int
	Length   int
	Capacity int
}

Allocator provides allocation of various signal buffers.

func (Allocator) Float32 added in v0.6.0

func (a Allocator) Float32() Floating

Float32 allocates a new sequential float32 signal buffer.

func (Allocator) Float64 added in v0.6.0

func (a Allocator) Float64() Floating

Float64 allocates a new sequential float64 signal buffer.

func (Allocator) Int16 added in v0.6.0

func (a Allocator) Int16(bd BitDepth) Signed

Int16 allocates a new sequential int16 signal buffer.

func (Allocator) Int32 added in v0.6.0

func (a Allocator) Int32(bd BitDepth) Signed

Int32 allocates a new sequential int32 signal buffer.

func (Allocator) Int64 added in v0.6.0

func (a Allocator) Int64(bd BitDepth) Signed

Int64 allocates a new sequential int64 signal buffer.

func (Allocator) Int8 added in v0.6.0

func (a Allocator) Int8(bd BitDepth) Signed

Int8 allocates a new sequential int8 signal buffer.

func (Allocator) Uint16 added in v0.6.0

func (a Allocator) Uint16(bd BitDepth) Unsigned

Uint16 allocates a new sequential uint16 signal buffer.

func (Allocator) Uint32 added in v0.6.0

func (a Allocator) Uint32(bd BitDepth) Unsigned

Uint32 allocates a new sequential uint32 signal buffer.

func (Allocator) Uint64 added in v0.6.0

func (a Allocator) Uint64(bd BitDepth) Unsigned

Uint64 allocates a new sequential uint64 signal buffer.

func (Allocator) Uint8 added in v0.6.0

func (a Allocator) Uint8(bd BitDepth) Unsigned

Uint8 allocates a new sequential uint8 signal buffer.

type BitDepth

type BitDepth uint8

BitDepth is the number of bits of information in each sample.

const (
	// BitDepth4 is 4 bit depth.
	BitDepth4 BitDepth = 1 << (iota + 2)
	// BitDepth8 is 8 bit depth.
	BitDepth8
	// BitDepth16 is 16 bit depth.
	BitDepth16
	// BitDepth32 is 32 bit depth.
	BitDepth32
	// BitDepth64 is 64 bit depth.
	BitDepth64
	// BitDepth24 is 24 bit depth.
	BitDepth24 BitDepth = 24
	// MaxBitDepth is a maximum supported bit depth.
	MaxBitDepth BitDepth = BitDepth64
)

func (BitDepth) MaxSignedValue added in v0.6.0

func (b BitDepth) MaxSignedValue() int64

MaxSignedValue returns the maximum signed value for the bit depth.

Example
package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.BitDepth8.MaxSignedValue())
}
Output:

127

func (BitDepth) MaxUnsignedValue added in v0.6.0

func (b BitDepth) MaxUnsignedValue() uint64

MaxUnsignedValue returns the maximum unsigned value for the bit depth.

Example
package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.BitDepth8.MaxUnsignedValue())
}
Output:

255

func (BitDepth) MinSignedValue added in v0.6.0

func (b BitDepth) MinSignedValue() int64

MinSignedValue returns the minimum signed value for the bit depth.

Example
package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.BitDepth8.MinSignedValue())
}
Output:

-128

func (BitDepth) SignedValue added in v0.6.0

func (b BitDepth) SignedValue(val int64) int64

SignedValue clips the signed signal value to the given bit depth range.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.BitDepth8.SignedValue(math.MaxInt64))
}
Output:

127

func (BitDepth) UnsignedValue added in v0.6.0

func (b BitDepth) UnsignedValue(val uint64) uint64

UnsignedValue clips the unsigned signal value to the given bit depth range.

Example
package main

import (
	"fmt"
	"math"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.BitDepth8.UnsignedValue(math.MaxUint64))
}
Output:

255

type Fixed added in v0.6.0

type Fixed interface {
	Signal
	BitDepth() BitDepth
}

Fixed is a digital signal represented with fixed-point values.

type Floating added in v0.6.0

type Floating interface {
	Signal
	Slice(start int, end int) Floating
	Channel(channel int) Floating
	Append(Floating)
	AppendSample(value float64)
	Sample(index int) float64
	SetSample(index int, value float64)
}

Floating is a digital signal represented with floating-point values.

type Frequency added in v0.9.0

type Frequency float64

Frequency in Hertz is the number of occurrences of a repeating event per second. It might represent sample rate or pitch.

func (Frequency) Duration added in v0.9.0

func (f Frequency) Duration(events int) time.Duration

Duration returns a total time duration for a number events at this frequency.

Example
package main

import (
	"fmt"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.Frequency(44100).Duration(88200))
}
Output:

2s

func (Frequency) Events added in v0.9.0

func (f Frequency) Events(d time.Duration) int

Events returns a number of events for time duration at this frequency.

Example
package main

import (
	"fmt"
	"time"

	"pipelined.dev/signal"
)

func main() {
	fmt.Println(signal.Frequency(44100).Events(time.Second * 2))
}
Output:

88200

type PoolAllocator added in v0.9.0

type PoolAllocator struct {
	Channels int
	Capacity int
	Length   int
	// contains filtered or unexported fields
}

PoolAllocator allows to decrease a number of allocations at runtime. Internally it relies on sync.PoolAllocator to manage objects in memory. It contains a pool per signal buffer type.

func GetPoolAllocator added in v0.9.0

func GetPoolAllocator(channels, length, capacity int) *PoolAllocator

GetPoolAllocator returns pool for provided buffer dimensions - number of channels and capacity. The length is not taken into account, it's only used to provide desired size when once new buffer is requested. Pools are cached internally, so multiple calls with same dimentions will return the same pool instance.

func (*PoolAllocator) Float32 added in v0.10.0

func (p *PoolAllocator) Float32() Floating

Float32 selects a new sequential float32 signal buffer. from the pool.

func (*PoolAllocator) Float64 added in v0.10.0

func (p *PoolAllocator) Float64() Floating

Float64 selects a new sequential float64 signal buffer. from the pool.

func (*PoolAllocator) Int16 added in v0.10.0

func (p *PoolAllocator) Int16(bd BitDepth) Signed

Int16 selects a new sequential int16 signal buffer. from the pool.

func (*PoolAllocator) Int32 added in v0.10.0

func (p *PoolAllocator) Int32(bd BitDepth) Signed

Int32 selects a new sequential int32 signal buffer. from the pool.

func (*PoolAllocator) Int64 added in v0.10.0

func (p *PoolAllocator) Int64(bd BitDepth) Signed

Int64 selects a new sequential int64 signal buffer. from the pool.

func (*PoolAllocator) Int8 added in v0.10.0

func (p *PoolAllocator) Int8(bd BitDepth) Signed

Int8 selects a new sequential int8 signal buffer. from the pool.

func (*PoolAllocator) Uint16 added in v0.10.0

func (p *PoolAllocator) Uint16(bd BitDepth) Unsigned

Uint16 selects a new sequential uint16 signal buffer. from the pool.

func (*PoolAllocator) Uint32 added in v0.10.0

func (p *PoolAllocator) Uint32(bd BitDepth) Unsigned

Uint32 selects a new sequential uint32 signal buffer. from the pool.

func (*PoolAllocator) Uint64 added in v0.10.0

func (p *PoolAllocator) Uint64(bd BitDepth) Unsigned

Uint64 selects a new sequential uint64 signal buffer. from the pool.

func (*PoolAllocator) Uint8 added in v0.10.0

func (p *PoolAllocator) Uint8(bd BitDepth) Unsigned

Uint8 selects a new sequential uint8 signal buffer. from the pool.

type Signal added in v0.6.0

type Signal interface {
	Capacity() int
	Channels() int
	Length() int
	Len() int
	Cap() int
	BufferIndex(channel int, index int) int
	Free(*PoolAllocator)
}

Signal is a buffer that contains a digital representation of a physical signal that is a sampled and quantized. Signal types have semantics of go slices. They can be sliced and appended to each other.

func Slice added in v0.8.0

func Slice(s Signal, start, end int) Signal

Slice allows to slice arbitrary signal type.

type Signed added in v0.6.0

type Signed interface {
	Fixed
	Slice(start int, end int) Signed
	Channel(channel int) Signed
	Append(Signed)
	AppendSample(value int64)
	Sample(index int) int64
	SetSample(index int, value int64)
}

Signed is a digital signal represented with signed fixed-point values.

type Unsigned added in v0.6.0

type Unsigned interface {
	Fixed
	Slice(start int, end int) Unsigned
	Channel(channel int) Unsigned
	Append(Unsigned)
	AppendSample(value uint64)
	Sample(index int) uint64
	SetSample(index int, value uint64)
}

Unsigned is a digital signal represented with unsigned fixed-point values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL