haberdashPI / SignalOperators.jl

A handy set of operators for manipulating digital signals (e.g. Sounds)
Other
20 stars 1 forks source link

SignalOperators

Project Status: Active – The project has reached a stable, usable state and is being actively developed. Stable Dev GitHub Actions PkgEval Codecov

SignalOperators is a Julia package that aims to provide a clean interface for generating and manipulating signals: typically sounds, but any signal regularly sampled in time can be manipulated.

NOTE: It's come to my attention that folks have encountered some poor performance with SignalOperators (#65). You may be able to solve some problems by making liberal use of sink. However, I am in the (slow) process of re-implementing this package to avoid those issues. Fair warning, this package is low priority for me, as it is not the thrust of my current work.

using WAV
using SignalOperators
using SignalOperators.Units # allows the use of dB, Hz, s etc... as unitful values

# a pure tone 20 dB below a power 1 signal, with on and off ramps (for
# a smooth onset/offset)
sound1 = Signal(sin,ω=1kHz) |> Until(5s) |> Ramp |> Normpower |> Amplify(-20dB)

# a sound defined by a file, matching the overall power to that of sound1
sound2 = "example.wav" |> Normpower |> Amplify(-20dB)

# a 1kHz sawtooth wave
sound3 = Signal(ϕ -> ϕ-π,ω=1kHz) |> Ramp |> Normpower |> Amplify(-20dB)

# a 5 Hz amplitude modulated noise
sound4 = randn |>
    Amplify(Signal(ϕ -> 0.5sin(ϕ) + 0.5,ω=5Hz)) |>
    Until(5s) |> Normpower |> Amplify(-20dB)

# a 1kHz tone surrounded by a notch noise
SNR = 5dB
x = Signal(sin,ω=1kHz) |> Until(1s) |> Ramp |> Normpower |> Amplify(-20dB + SNR)
y = Signal(randn) |> Until(1s) |> Filt(Bandstop,0.5kHz,2kHz) |> Normpower |>
  Amplify(-20dB)
scene = Mix(x,y)

# write all of the signals to a single file, at 44.1 kHz
Append(sound1,sound2,sound3,sound4,scene) |> ToFramerate(44.1kHz) |> sink("examples.wav")

The interface is relatively generic and can be used to operate on or produce a number of different signal representations, including AxisArrays, DimensionalData and SampleBuf objects from SampledSignals. It should also be straightforward to extend the operators to new signal representations. Operators generally produce signals that match the type input values, when these are uniform.

In many cases, operators are designed to create efficient, lazy representations of signals, and will only generate data on a call to sink; however, there are non-lazy versions of the operators as well, for quick, one-off usage.

using SampledSignals: SampleBuf

a = SampleBuf(rand(100,2),100)
b = SampleBuf(ones(100,2),100)

using SignalOperators

c = mix(a,b)
c == sink(Mix(a,b))

Because of the smarts in the operators, the resulting value c will also be a SampleBuf object.

Read more about how to use the operators in the documentation.

Status

The functions are relatively bug-free and thoroughly documented.

Everything here will run pretty fast. All calls should fall within the same order of magnitude of equivalent "raw" julia code (e.g. loops and broadcasting over arrays).

I'm the only person I know who has made thorough use of this package: it's obviously possible there are still some bugs or performance issues lurking about. (I welcome new issues or PRs!!!)

Acknowledgements

Many thanks to @ssfrr for some great discussions during this PR, and related issues on the SampledSignals package. Those interactions definitely influenced my final design here.