Reference

Signal Generation

SignalOperators.SignalFunction
Signal(x,[framerate])

Coerce x to be a signal, optionally specifying its frame rate (usually in Hz). All signal operators first call Signal(x) for each argument. This means you only need to call Signal when you want to pass additional arguments to it.

Note

If you pipe Signal and pass a frame rate, you must specify the units of the frame rate (e.g. x |> Signal(20Hz)). A unitless number is always interpreted as a constant, infinite-length signal (see below).

Note

If you are implementing Signal for a custom signal, you will need to support the second argument of Signal by specifying fs::Union{Number,Missing}=missing, or equivalent.

The type of objects that can be coerced to signals are as follows.

source

Filenames

A string with a filename ending with an appropriate filetype can be read in as a signal. You will need to call import or using on the backend for reading the file.

Available backends include the following pacakges

source

Existing signals

Any existing signal just returns itself from Signal. If a frame rate is specified it will be set if x has an unknown frame rate. If it has a known frame rate and doesn't match framerate(x) an error will be thrown. If you want to change the frame rate of a signal use ToFramerate.

source

Numbers

Numbers can be treated as infinite length, constant signals of unknown frame rate.

Example

rand(10,2) |> Amplify(20dB) |> nframes == 10

!!! note

    The length of numbers are treated specially when passed to
    [`OperateOn`](@ref): if there are other types of signal passed as input,
    the number signals are considered to be as long as the longest signal.

    ```julia
    nframes(Mix(1,2)) == inflen
    nframes(Mix(1,rand(10,2))) == 10
    ```
source

Arrays

Any array can be interpreted as a signal. By default the first dimension is time, the second channels and their frame rate is a missing value. If you pass a non-missin gframerate, and the array currently has a missing frame rate a Tuple value will be returned (see "Array & Number" below).

If you specify a non-missing frame rate to an array type with a missing frame rate the return value will be a Tuple (see Array & Number section below). Some array types change this default behavior, as follows.

Warning

Arrays of more than two dimensions are not currently supported.

  • AxisArrays, if they have an axis labeled time and one or zero additional axes, can be treated as a signal. The time dimension must be represented using on object with the step function defined (e.g. any AbstractRange object).
  • SampleBuf objects are also properly interpreted as signals, as per the conventions employed for its package.
  • DimensionalArrays can be treated as signals if there is a Time dimension, which must be represented using an object with the step function defined (e.g. AbstractRange) and zero or one additional dimensions (treated as channels)
source

Array & Number

A tuple of an array and a number can be interepted as a signal. The first dimension is time, the second channels, and the number determines the frame rate (in Hertz).

source

Functions

Signal(fn,[framerate];[ω/frequency],[ϕ/phase])

Functions can define infinite length signals of known or unknown frame rate. The function fn can either return a number or, for multi-channel signals, a tuple of values.

The input to fn is either a phase value or a time value. If a frequency is specified (using either the ω or frequency keyword), the input to fn will be a phase value in radians, ranging from 0 to 2π. If no frequency is specified the value passed to fn is the time in seconds. Specifying phase (by the ϕ or phase keyword) will first add that value to the input before passing it to fn. When frequency is specified, the phase is assumed to be in units of radians (but you can also pass degrees by using ° or a unit of time (e.g. s for seconds)). When frequency is not specified the phase is assumed to be in units of seconds.

source

If fn == randn no frequency or phase can be specified. Instead there is a single keyword argument, rng, which allows you to specify the random number generator; rng defaults to Random.GLOBAL_RNG.

source
SignalOperators.sinkFunction
sink(signal,[to])

Creates a given type of object (to) from a signal. By default the type of the resulting sink is determined by the type of the underlying data of the signal: e.g. if x is a SampleBuf object then sink(Mix(x,2)) is also a SampleBuf object. If there is no underlying data (Signal(sin) |> sink) then a Tuple of an array and the framerate is returned.

Warning

Though sink often makes a copy of an input array, it is not guaranteed to do so. For instance sink(Until(rand(10),5frames)) will simply take a view of the first 5 frames of the input.

Values for to

Type

If to is an array type (e.g. Array, DimensionalArray) the signal is written to a value of that type.

If to is a Tuple the result is an Array of samples and a number indicating the sample rate in Hertz.

source

Filename

If to is a string, it is assumed to describe the name of a file to which the signal will be written. You will need to call import or using on an appropriate backend for writing to the given file type.

Available backends include the following pacakges

source

Signal Inspection

SignalOperators.inflenConstant
inflen

Represents an infinite length. Proper overloads are defined to handle arithmetic and ordering for the infinite value.

source
SignalBase.durationFunction
duration(x)

Return the duration of the signal in seconds, if known. May return missing or inflen. The value missing always denotes a finite but unknown length.

Note

A fallback implementation of duration uses nframes(x) / framerate(x). However, if one or both of these is missing and you want duartion to return a non-missing value, you can define a custom method of duration.

SignalBase.nframesFunction
nframes(x)

Returns the number of frames in the signal, if known. May return missing (e.g. for a file stream).

SignalBase.framerateFunction
framerate(x)

Returns the frame rate of the signal (in Hertz). May return missing if the frame rate is unknown.

SignalBase.sampletypeFunction
sampletype(x)

Returns the element type of an individual channel of a signal (e.g. Float64).

Note

The result of sampletype and eltype (when defined) are often the same. They are distinct so that these two can diverge when appropriate.

Signal Operators

Basic Operators

SignalOperators.AfterFunction
After(x,time)

Create a signal of all frames of x after time.

Note

If you use frames as the unit here, keep in mind that because this returns all frames after the given index, the result is effectively zero indexed: i.e. all(sink(After(1:10,1frames)) .== 2:10)

source
SignalOperators.PadFunction
Pad(x,padding)

Create a signal that appends an infinite number of values, padding, to x. The value padding can be:

  • a number
  • a tuple or vector
  • a type function: a one argument function of the sampletype of x
  • a value function: a one argument function of the signal x for which SignalOperators.valuefunction(padding) == true.
  • an indexing function: a three argument function following the same type signature as getindex for two dimensional arrays.

If the signal is already infinitely long (e.g. a previoulsy padded signal), Pad has no effect.

If padding is a number it is used as the value for all samples past the end of x.

If padding is a tuple or vector it is the value for all frames past the end of x.

If padding is a type function it is passed the sampletype of the signal and the resulting value is used as the value for all frames past the end of x. Examples include zero and one

If padding is a value function it is passed the signal x just before padding occurs during a call to sink; it should return a tuple of sampletype(x) values. The return value is repeated for all remaining frames of the signal. For example, lastframe is a value function.

If padding is an indexing function (it accepts 3 arguments) it will be used to retrieve frames from the signal x assuming it conforms to the AbstractArray interface, with the first index being frames and the second channels. If the frame index goes past the bounds of the array, it should be transformed to an index within the range of that array. Note that such padding functions only work on signals that are also AbstractArray objects. You can always generate an array from a given signal by first passing it through sink or sink!.

Info

A indexing function will also work on a signal represented as a tuple of an array and number; it simply passed the array (leaving off the number).

See also

cycle mirror lastframe valuefunction

source
SignalOperators.mirrorFunction
mirror(x,i,j)

An indexing function which mirrors the indices when i > size(x,1). This means that past the end of the signal x, the signal first repeats with frames in reverse order, then repeats in the original order, so on and so forth. It can be passed as the second argument to Pad.

source
SignalOperators.cycleFunction
cycle(x,i,j)

An indexing function which wraps index i using mod, thus repeating the signal when i > size(x,1). It can be passed as the second argument to Pad.

source
SignalOperators.lastframeFunction
lastframe

When passed as an argument to Pad, allows padding using the last frame of a signal. You cannot use this function in other contexts, and it will normally throw an error. See Pad.

source
SignalOperators.valuefunctionFunction
SignalOperators.valuefunction(fn)

Returns true if fn should be treated as a value function. See Pad. If you wish your own function to be a value function, you can do this as follows.

SignalOperators.valuefunction(::typeof(myfun)) = true
source

Mapping Operators

SignalOperators.FiltFunction
Filt(x,::Type{<:FilterType},bounds...;method=Butterworth(order),order=5,
     blocksize=4096)

Apply the given filter type (e.g. Lowpass) using the given method to design the filter coefficients. The type is specified as per the types from DSP

Filt(x,h;[blocksize=4096])

Apply the given digital filter h (from DSP) to signal x.

Blocksize

Blocksize determines the size of the buffer used when computing intermediate values of the filter. It need not normally be adjusted, though changing it can alter how efficient filter application is.

Note

The non-lazy version of Filt is filt from the DSP package. Proper methods have been defined such that it should be possible to call filt on a signal and get a signal back.

The argument order for filt follows a different convention, with x coming after the filter specification. In contrast, Filt uses the convention of keeping x as the first argument to make piping possible.

source
SignalOperators.NormpowerFunction
Normpower(x)

Return a signal with normalized power. That is, divide all frames by the root-mean-squared value of the entire signal.

source
SignalOperators.OperateOnFunction
OperateOn(fn,arguments...;padding=default_pad(fn),bychannel=false)

Apply fn across the samples of the passed signals. The output length is the maximum length of the arguments. Shorter signals are extended using Extend(x,padding).

Note

There is no piped version of OperateOn, use Operate to pipe. The shorter name is used to pipe because it is expected to be the more common use case.

Channel-by-channel functions (default)

When bychannel == false the function fn should treat each of its arguments as a single number and return a single number. This operation is broadcast across all channels of the input. It is expected to be a type stable function.

The signals are first promoted to have the same sample rate and the same number of channels using Uniform.

Cross-channel functions

When bychannel=false, rather than being applied to each channel seperately the function fn is applied to each frame, containing all channels. For example, for a two channel signal, the following would swap these two channels.

x = rand(10,2)
swapped = OperateOn(x,bychannel=false) do val
    val[2],val[1]
end

The signals are first promoted to have the same sample rate, but the number of channels of each input signal remains unchanged.

Padding

Padding determines how frames past the end of shorter signals are reported. If you wish to change the padding for all signals you can set the value of the keyword argument padding. If you wish to specify distinct padding values for some of the inputs, you can first call Extend on those arguments.

The default value for padding is determined by the fn passed. A fallback implementation of default_pad returns zero. The default value for the four basic arithmetic operators is their identity (one for * and zero for +).

To define a new default for a specific function, just create a new method of default_pad(fn)

myfun(x,y) = x + 2y
SignalOperators.default_pad(::typeof(myfun)) = one

sink(OperateOn(myfun,Until(5,2frames),Until(2,4frames))) == [9,9,5,5]
source
SignalOperators.AddChannelFunction
AddChannel(xs...)

Concatenate the channels of all signals into one signal, using OperateOn. This will result in a signal with sum(nchannels,xs) channels. Unlike OperateOn, AddChannels includes a piped version.

source

Ramping Operators

SignalOperators.RampOnFunction
RampOn(x,[len=10ms],[fn=x -> sinpi(0.5x)])

Ramp the onset of a signal, smoothly transitioning from 0 to full amplitude over the course of len seconds.

The function determines the shape of the Ramp and should be non-decreasing with a range of [0,1] over the domain [0,1]. It should map over the entire range: that is fn(0) == 0 and fn(1) == 1.

Both len and fn are optional arguments: either one or both can be specified, though len must occur before fn if present.

source
SignalOperators.RampOffFunction
RampOff(x,[len=10ms],[fn=x -> sinpi(0.5x)])

Ramp the offset of a signal, smoothly transitioning from full amplitude to 0 amplitude over the course of len seconds.

The function determines the shape of the Ramp and should be non-decreasing with a range of [0,1] over the domain [0,1]. It should map over the entire range: that is fn(0) == 0 and fn(1) == 1.

Both len and fn are optional arguments: either one or both can be specified, though len must occur before fn if present.

source
SignalOperators.RampFunction
Ramp(x,[len=10ms],[fn=x -> sinpi(0.5x)])

Ramp the onset and offset of a signal, smoothly transitioning from 0 to full amplitude over the course of len seconds at the start and from full to 0 amplitude over the course of len seconds.

The function determines the shape of the Ramp and should be non-decreasing with a range of [0,1] over the domain [0,1]. It should map over the entire range: that is fn(0) == 0 and fn(1) == 1.

Both len and fn are optional arguments: either one or both can be specified, though len must occur before fn if present.

source
SignalOperators.FadeToFunction
FadeTo(x,y,[len=10ms],[fn=x->sinpi(0.5x)])

Append x to y, with a smooth transition lasting len seconds fading from x to y (so the total length is duration(x) + duration(y) - len).

This fade is accomplished with a RampOff of x and a RampOn for y. fn should be non-decreasing with a range of [0,1] over the domain [0,1]. It should map over the entire range: that is fn(0) == 0 and fn(1) == 1.

Both len and fn are optional arguments: either one or both can be specified, though len must occur before fn if present.

source

Reformatting Operators

SignalOperators.ToFramerateFunction
ToFramerate(x,fs;blocksize)

Change the frame rate of x to the given frame rate fs. The underlying implementation depends on whether the input is a computed or data signal, as determined by EvalTrait.

Computed signals (e.g. Signal(sin)) are resampled exactly: the result is simply computed for more time points or fewer time points, so as to generate the appropriate number of frames.

Data-based signals (Signal(rand(50,2))) are resampled using filtering (akin to DSP.resample). In this case you can use the keyword arugment blocksize to change the analysis window used. See Filt for more details. Setting blocksize for a computed signal will succeed, but different blocksize values have no effect on the underlying implementation.

Implementation

You need only implement this function for custom signals for particular scenarios, described below.

Custom Computed Signals

If you implement a new sigal type that is a computed signal, you must implement ToFramerate with the following type signature.


function ToFramerate(x::MyCustomSignal,s::IsSignal{<:Any,<:Number},
    c::ComputedSignal,framerate;blocksize)

    ## ...
end

The result should be a new version of the computed signal with the given frame rate.

Handling missing frame rates

If you implement a new signal type that can handle missing frame rate values, you will need to implement the following version of ToFramerate so that a known frame rate can be applied to a signal with a missing frame rate.


function ToFramerate(x::MyCustomSignal,s::IsSignal{<:Any,Missing},
    evaltrait,framerate;blocksize)

    ## ...
end

The result should be a new version of the signal with the specified frame rate.

source
SignalOperators.ToChannelsFunction
ToChannels(x,ch)

Force a signal to have ch number of channels, by mixing channels together or broadcasting a single channel over multiple channels.

source
SignalOperators.FormatFunction
Format(x,fs,ch)

Efficiently convert both the framerate (fs) and channels ch of signal x. This selects an optimal ordering for ToFramerate and ToChannels to avoid redundant computations.

source
SignalOperators.UniformFunction
Uniform(xs;channels=false)

Promote the frame rate (and optionally the number of channels) to be the highest frame rate (and optionally highest channel count) of the iterable of signals xs.

Note

Uniform rarely needs to be called directly. It is called implicitly on all passed signals, within the body of operators such as OperateOn.

source

Custom Signals

SignalOperators.SignalTraitFunction
SiganlOperators.SignalTrait(::Type{T}) where T

Returns either nothing if the type T should not be considered a signal (the default) or IsSignal to indicate the signal format for this signal.

source
SignalOperators.IsSignalType
SignalOperators.IsSignal{T,Fs,L}

Represents the Format of a signal type with three type parameters:

  • T - The sampletype of the signal.
  • Fs - The type of the framerate. It should be either Float64 or Missing.
  • L - The type of the length of the signal. It should be either Infinity, Missing or Int.
source
SignalOperators.EvalTraitFunction
SiganlOperators.EvalTrait(x)

Indicates whether the signal is a DataSignal or ComputedSignal. Data signals represent frames concretely as a set of frames. Examples include arrays and numbers. Data signals generally return themselves, or some wrapper type when sink is called on them. Computed signals are any signal that invovles some intermediate computation, in which frames must be computued on the fly. Calls to sink on a computed signal results in some new, data signal. Most signals returned by a signal operator are computed signals.

Computed signals have the extra responsibility of implementing ToFramerate

source
SignalOperators.nextblockFunction
SignalOperators.nextblock(x,maxlength,skip,[last_block])

Retrieve the next block of frames for signal x, or nothing, if no more blocks exist. Analogous to Base.iterate. The returned block must satisfy the interface for signal blocks as described in custom signals.

Arugments

  • x: the signal to retriev blocks from
  • maxlength: The resulting block must have no more than maxlength frames, but may have fewer frames than that; it should not have zero frames unless maxlength == 0.
  • skip: If skip == true, it is guaranted that frame will never be called on the returned block. The value of skip is true when skipping blocks during a call to After).
  • last_block The fourth argument is optional. If included, the block that occurs after this block is returned. If it is left out, nextblock returns the very first block of the signal.
source
SignalOperators.frameFunction
SignalOperators.frame(x,block,i)

Retrieves the frame at index i of the given block of signal x. A frame is one or more channels of sampletype(x) values. The return value should be an indexable object (e.g. a number, tuple or array) of these channel values. This method should be implemented by blocks of custom signals.

source

Custom Sinks

SignalOperators.initsinkFunction
SignalOperators.initsink(x,::Type{T})

Initialize an object of type T so that it can store all frames of signal x.

If you wish an object to serve as a custom sink you can implement this method. You can use nchannels and sampletype of x to determine how to initialize the object for the first method, or you can just use initsink(x,Array) and wrap the return value with your custom type.

source
SignalOperators.sink_helper!Function
SignalOperators.sink_helper!(result,written,x,block)

Write the given block of frames from signal x to result given that a total of written frames have already been written to the result.

This method should be fast: i.e. a for loop using @simd and @inbounds. It should call nframes and SignalOperators.frame on the block to write the frames. Do not call frame more than once for each index of the block.

source