/
sink.jl
267 lines (214 loc) · 7.85 KB
/
sink.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
"""
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.
"""
sink() = x -> sink(x,refineroot(root(x)))
sink(to::Type) = x -> sink(x,to)
sink(x) = sink(x,refineroot(root(x)))
root(x) = x
refineroot(x::AbstractArray) = refineroot(x,SignalTrait(x))
refineroot(x,::Nothing) = Tuple{<:AbstractArray,<:Number}
refineroot(x,::IsSignal{<:Any,Missing}) = Array
refineroot(x,::IsSignal) = typeof(x)
refineroot(x) = Tuple{<:AbstractArray,<:Number}
refineroot(x::T) where T <: Tuple{<:AbstractArray,<:Number} = T
mergepriority(x) = 0
mergepriority(x::Array) = 1
mergepriority(x::AbstractArray) = mergepriority(x,SignalTrait(x))
mergepriority(x::AbstractArray,::IsSignal) = 2
mergepriority(x::AbstractArray,::Nothing) = 0
function mergeroot(x,y)
if mergepriority(x) ≥ mergepriority(y)
return x
else
return y
end
end
abstract type CutMethod
end
struct DataCut <: CutMethod
end
struct SinkCut <: CutMethod
end
CutMethod(x) = CutMethod(x,EvalTrait(x))
CutMethod(x,::DataSignal) = SinkCut()
CutMethod(x::AbstractArray,::DataSignal) = DataCut()
CutMethod(x::Tuple{<:AbstractArray,<:Number},::DataSignal) = DataCut()
CutMethod(x,::ComputedSignal) = SinkCut()
sink(x,::Type{T}) where T = sink(x,T,CutMethod(x))
function sink(x,::Type{T},::DataCut) where T
x = process_sink_params(x)
data = timeslice(x,:)
if Base.typename(typeof(parent(data))) == Base.typename(T)
data
else # if the sink type is new, we have to copy the data
# because it could be in a different memory layout
result = initsink(x,T)
sink!(result,x)
result
end
end
rawdata(x::SubArray) = x
function rawdata(x::AbstractArray)
p = parent(x)
if p === x
return p
else
return rawdata(p)
end
end
function sink(x,::Type{T},::SinkCut) where T
x = process_sink_params(x)
result = initsink(x,T)
sink!(result,x)
result
end
function process_sink_params(x)
x = Signal(x)
ismissing(nframes(x)) && error("Unknown number of frames in signal.")
isinf(nframes(x)) && error("Cannot store infinite signal.")
x
end
"""
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](@ref custom_sinks) you can
implement this method. You can use [`nchannels`](@ref) and
[`sampletype`](@ref) 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.
"""
function initsink(x,::Type{<:Array})
Array{sampletype(x),2}(undef,nframes(x),nchannels(x))
end
initsink(x,::Type{<:Tuple}) =
(Array{sampletype(x)}(undef,nframes(x),nchannels(x)),framerate(x))
initsink(x,::Type{<:Array},data) = data
initsink(x,::Type{<:Tuple},data) = (data,framerate(x))
Base.Array(x::AbstractSignal) = sink(x,Array)
Base.Tuple(x::AbstractSignal) = sink(x,Tuple)
"""
## 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
- [WAV](https://codecov.io/gh/haberdashPI/SignalOperators.jl/src/master/src/WAV.jl)
- [LibSndFile](https://github.com/JuliaAudio/LibSndFile.jl)
"""
sink(to::String) = x -> sink(x,to)
function sink(x,to::String)
x = process_sink_params(x)
save_signal(filetype(to),to,x)
end
function save_signal(::Val{T},filename,x) where T
error("No backend loaded for file of type $T. Refer to the documentation ",
"of `Signal` to find a list of available backends.")
end
"""
sink!(array,x)
Write `size(array,1)` frames of signal `x` to `array`.
"""
sink!(result::Union{AbstractVector,AbstractMatrix}) =
x -> sink!(result,x)
sink!(result::Tuple{<:AbstractArray,<:Number},x) =
(sink!(result[1],x), result[2])
function sink!(result::Union{AbstractVector,AbstractMatrix},x;
framerate=SignalOperators.framerate(x))
if nframes(x) < size(result,1)
error("Signal is too short to fill buffer of length $(size(result,1)).")
end
x = ToChannels(x,size(result,2))
sink!(result,x,SignalTrait(x))
result
end
"""
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](@ref
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`](@ref)
will never be called on the returned block. The value of `skip` is `true`
when skipping blocks during a call to [`After`](@ref)).
- `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.
"""
function nextblock
end
"""
SignalOperators.timeslice(x::AbstractArray,indices)
Extract the slice of x with the given time indices.
[Custom signals](@ref custom_signals) can implement this method if the signal
is an `AbstractArray` allowing the use of a fallback implementation of
[`SignalOperators.nextblock`](@ref).
"""
function timeslice
end
"""
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](@ref custom_signals).
"""
function frame
end
fold(x) = zip(x,Iterators.drop(x,1))
sink!(result,x,sig::IsSignal) =
sink!(result,x,sig,nextblock(x,size(result,1),false))
function sink!(result,x,::IsSignal,block)
written = 0
while !isnothing(block) && written < size(result,1)
@assert nframes(block) > 0
sink_helper!(result,written,x,block)
written += nframes(block)
maxlen = size(result,1)-written
if maxlen > 0
block = nextblock(x,maxlen,false,block)
end
end
@assert written == nframes(result)
block
end
"""
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`](@ref) and [`SignalOperators.frame`](@ref) on the
block to write the frames. **Do not call `frame` more than once for each
index of the block**.
"""
@noinline function sink_helper!(result,written,x,block)
@inbounds @simd for i in 1:nframes(block)
writesink!(result,i+written,frame(x,block,i))
end
end
@Base.propagate_inbounds function writesink!(result::AbstractArray,i,v)
for ch in 1:length(v)
result[i,ch] = v[ch]
end
v
end