Skip to content

Commit ea60614

Browse files
committed
significant refactoring of the data structures
1 parent f65e09d commit ea60614

11 files changed

+179
-160
lines changed

Project.toml

+2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ version = "0.1.0"
55

66
[deps]
77
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
8+
CSVFiles = "5d742f6a-9f54-50ce-8119-2520741973ca"
89
DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2"
10+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
911
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
1012
FFTViews = "4f61f5a4-77b1-5117-aa51-3ab5ef4ef0cd"
1113
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"

src/SampleArrays.jl

+3-7
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ using Interpolations
1212
using Unitful
1313
using Unitful: ns, ms, µs, s, Hz, kHz, MHz, GHz, THz
1414

15-
include("magphase.jl")
15+
include("units.jl")
1616

17-
include("freqtime.jl")
17+
include("magphase.jl")
1818

1919
include("abstracttypes.jl")
2020

@@ -30,11 +30,7 @@ include("impexp.jl")
3030
FFTW.rfft(x::SampleArray) = RFFTSpectrumArray(FFTW.rfft(data(x), 1), rate(x), nframes(x), names(x))
3131
FFTW.irfft(X::RFFTSpectrumArray{<:Complex}, d::Int) = SampleArray(FFTW.irfft(data(X), d, 1), rate(X), names(X))
3232
FFTW.irfft(X::RFFTSpectrumArray{MagPhase{E}}, d::Int) where E = SampleArray(FFTW.irfft(Complex{E}.(data(X)), d, 1), rate(X), names(X))
33-
function FFTW.irfft(X::RFFTSpectrumArray)
34-
d = ntimeframes(X)
35-
isnothing(d) && throw(ArgumentError("the number of original frames d not known, use irfft(a, d)"))
36-
irfft(X, d)
37-
end
33+
FFTW.irfft(X::RFFTSpectrumArray) = irfft(X, ntimeframes(X))
3834

3935
# ----- UTILS ---------------------------
4036
function DSP.unwrap!(Y::AbstractArray{T}, X::AbstractArray{T}; range=2E(pi), kwargs...) where {E, T <: MagPhase{E}}

src/abstracttypes.jl

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export AbstractSampleArray, nframes, nchannels, data, rate, rateHz, domain
22
export names, names!
3-
export AbstractSpectrumArray, nfreqs, data_no0, domain_no0, slice, zerophase, zerophase!
3+
export AbstractSpectrumArray, nfreqs, data_no0, domain_no0, slice, zerophase, zerophase!, delay, delay!
44

55
import Base: BroadcastStyle, IndexStyle, getindex, setindex!, show, similar, to_index, to_indices, ==
66
import Base: cat, hcat, vcat, names
@@ -120,7 +120,7 @@ function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{T}}, ::Type
120120
a = saslist[imax]
121121
sim = similar(a, ElType, dims)
122122
for o in saslist
123-
if o.names != a.names[1:nchannels(o)]
123+
if names(o) != names(a)[1:nchannels(o)]
124124
names!(sim, _default_channel_names(nchannels(a)))
125125
break
126126
end
@@ -170,4 +170,12 @@ function zerophase!(X::AbstractSpectrumArray{<:Complex})
170170
X
171171
end
172172

173-
zerophase(a) = zerophase!(deepcopy(a))
173+
zerophase(a) = zerophase!(deepcopy(a))
174+
175+
function delay(X::AbstractSpectrumArray{T}, Δt::Time) where T
176+
T.(MagPhase.(abs.(X), angle.(X) .- 2π .* domain(X) .* tos(Δt)))
177+
end
178+
179+
function delay!(X::AbstractSpectrumArray, shift)
180+
X .= delay(X, shift)
181+
end

src/freqtime.jl

-13
This file was deleted.

src/impexp.jl

+19-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
using CSVFiles
2+
using DataFrames
3+
using FileIO
14
using FLAC
25
using WAV
36

4-
export normalize, normalize!, readwav, readflac, readwav_rfft, writewav
7+
export normalize, normalize!, readwav, readflac, readwav_rfft, writewav, readfrd
58

69
function normalize!(a::SampleArray)
710
a ./= maximum(abs.(a))
@@ -10,30 +13,37 @@ end
1013

1114
normalize(a::SampleArray) = normalize!(similar(a))
1215

13-
function readwav(fname; format="double")
16+
function readwav(fname; format="double", names=nothing)
1417
y, Fs, nbits, opt = wavread(fname; format=format)
15-
SampleArray(y, Fs)
18+
SampleArray(y, Fs, names)
1619
end
1720

18-
function readflac(fname)
21+
function readflac(fname; names=nothing)
1922
y, Fs = load(fname)
20-
SampleArray(y, Fs)
23+
SampleArray(y, Fs, names)
2124
end
2225

23-
function readwav_rfft(fname; normalized=false, format="double")
24-
x = readwav(fname; format=format)
26+
function readwav_rfft(fname; normalized=false, format="double", names=nothing)
27+
x = readwav(fname; format=format, names=names)
2528
if normalized
2629
normalize!(x)
2730
end
2831
x, rfft(x)
2932
end
3033

31-
function readflac_rfft(fname; normalized=false)
32-
x = readflac(fname)
34+
function readflac_rfft(fname; normalized=false, names=nothing)
35+
x = readflac(fname; names=names)
3336
if normalized
3437
normalize!(x)
3538
end
3639
x, rfft(x)
3740
end
3841

3942
writewav(x::SampleArray, fname::String; kwargs...) = wavwrite(data(x), fname; Fs=rate(x), kwargs...)
43+
44+
function readfrd(fname, rate; name=nothing, magf=db2amp, phasef=deg2rad)
45+
df = DataFrame(load(File(format"TSV", fname); header_exists=false, colnames=[:freq, :mag, :phase]))
46+
sort!(df, [:freq])
47+
df = df[df.freq .<= toHz(rate)/2, :]
48+
SpectrumArray(Complex.(MagPhase.(magf.(df.mag), phasef.(df.phase))), rate, df.freq, isnothing(name) ? nothing : [name])
49+
end

src/rfftspectrumarray.jl

+19-21
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,19 @@ export RFFTSpectrumArray, resamplefreq, ntimeframes
44
struct RFFTSpectrumArray{T} <: AbstractSpectrumArray{T}
55
data::Matrix{T} # non-interleaving frequencies x channels
66
rate::Float64 # sampling frequency in Hz
7-
ntimeframes::Union{Int,Nothing} # number of original signal frames or nothing if not known
7+
ntimeframes::Int # number of original signal frames or nothing if not known
88
names::Vector{Symbol}
99

10-
function RFFTSpectrumArray{T}(X::Matrix{T}, rate::Float64, ntimeframes::Union{Int,Nothing}, names::Vector{Symbol}) where T
11-
isnothing(ntimeframes) || (ntimeframes >> 1 + 1 == size(X, 1) || throw(DimensionMismatch("ntimeframes >> 1 + 1 = $(ntimeframes >> 1 + 1) != size(X, 1) = $(size(X, 1))")))
10+
function RFFTSpectrumArray{T}(X::Matrix{T}, rate::Float64, ntimeframes::Int, names::Vector{Symbol}) where T
11+
ntimeframes >> 1 + 1 == size(X, 1) || throw(DimensionMismatch("ntimeframes >> 1 + 1 = $(ntimeframes >> 1 + 1) != size(X, 1) = $(size(X, 1))"))
1212
_check_channel_names(names)
1313
length(names) == size(X, 2) || throw(DimensionMismatch("the number of names ($(length(names))) does not match the number of channels ($(size(X, 2)))!"))
14-
new(X, rate, ntimeframes, names)
14+
new{T}(X, rate, ntimeframes, names)
1515
end
1616
end
1717

18-
RFFTSpectrumArray(X::AbstractMatrix{T}, rate::Frequency, ntimeframes::Union{Int,Nothing}, names::Vector{Symbol}) where T =
19-
RFFTSpectrumArray{T}(X, toHz(rate), ntimeframes, names)
20-
RFFTSpectrumArray(X::AbstractMatrix{T}, rate::Frequency, ntimeframes::Union{Int,Nothing}) where T =
21-
RFFTSpectrumArray{T}(X, toHz(rate), ntimeframes, _default_channel_names(size(X, 2)))
22-
RFFTSpectrumArray(X::AbstractMatrix{T}, rate::Frequency, names::Vector{Symbol}) where T =
23-
RFFTSpectrumArray{T}(X, toHz(rate), nothing, names)
24-
RFFTSpectrumArray(X::AbstractMatrix{T}, rate::Frequency) where T =
25-
RFFTSpectrumArray{T}(X, toHz(rate), nothing, _default_channel_names(size(X, 2)))
18+
RFFTSpectrumArray(X::AbstractMatrix{T}, rate::Frequency, ntimeframes::Integer, names::Union{Nothing,Vector{Symbol}}=nothing) where T =
19+
RFFTSpectrumArray{T}(X, toHz(rate), Int(ntimeframes), isnothing(names) ? _default_channel_names(size(X, 2)) : names)
2620

2721
ntimeframes(X::RFFTSpectrumArray) = X.ntimeframes
2822
domain(X::RFFTSpectrumArray) = range(0, rate(X) / 2; length=nframes(X))
@@ -32,28 +26,32 @@ data_no0(X::RFFTSpectrumArray) = @view data(X)[2:end, :]
3226
domain_no0(X::RFFTSpectrumArray) = @view domain(X)[2:end]
3327

3428

35-
function Base.similar(X::RFFTSpectrumArray, t::Type{T}, dims::Dims, ntimeframes::Union{Int,Nothing}=nothing) where T
29+
function Base.similar(X::RFFTSpectrumArray, t::Type{T}, dims::Dims) where T
3630
# tries to copy channel names
3731
# if there are fever names in the source array use default ones
3832
# changing number of frames (frequencies) looses information on number of original timeframes.
3933
ns = dims[2] nchannels(X) ? X.names[1:dims[2]] : _default_channel_names(dims[2])
40-
ntimeframes_ = dims[1] != nframes(X) ? nothing : ntimeframes
41-
RFFTSpectrumArray(similar(data(X), t, dims), rate(X), ntimeframes_, ns)
34+
if dims[1] != nframes(X)
35+
throw(ArgumentError("can not change the number of frequencies!"))
36+
else
37+
return RFFTSpectrumArray(similar(data(X), t, dims), rate(X), ntimeframes(X), ns)
38+
end
4239
end
4340

4441
# redefined so frequencies & channel names are treated
45-
function Base.getindex(X::RFFTSpectrumArray{T}, I::R, J::S) where {T, R <: FrameIndex, S <: ChannelIndex}
46-
I2 = toframeidx(X, I)
42+
Base.getindex(X::RFFTSpectrumArray{T}, I::R, J::S) where {T, R <: FrameIndex, S <: ChannelIndex} = throw(ArgumentError("frame index ($R) not allowed, use Colon!"))
43+
44+
function Base.getindex(X::RFFTSpectrumArray{T}, ::Colon, J::S) where {T, S <: ChannelIndex}
4745
J2 = tochannelidx(X, J)
4846
names_ = names(X)[J2]
49-
data_ = data(X)[I2, J2]
50-
ntimeframes_ = size(data_, 1) != nframes(X) ? nothing : ntimeframes(X)
51-
RFFTSpectrumArray{T}(data_, rate(X), ntimeframes_, names_)
47+
data_ = data(X)[:, J2]
48+
return RFFTSpectrumArray(data_, rate(X), ntimeframes(X), names_)
5249
end
5350

5451
function Base.hcat(X::RFFTSpectrumArray...)
5552
length(unique(rate.(X))) == 1 || throw(ArgumentError("hcat: non-unique rates!"))
5653
length(unique(nframes.(X))) == 1 || throw(DimensionMismatch("hcat: non-unique number of frames/frequencies!"))
54+
length(unique(ntimeframes.(X))) == 1 || throw(DimensionMismatch("hcat: non-unique number of original frames!"))
5755
newnames = _unique_channel_names(X...)
5856
data_ = hcat(map(data, X)...)
5957
return eltype(X)(data_, rate(X[1]), ntimeframes(X[1]), newnames) # eltype gives common supertype
@@ -66,7 +64,7 @@ end
6664
function Base.show(io::IO, ::MIME"text/plain", X::RFFTSpectrumArray{T}) where T
6765
d = domain(X)
6866
step = d[2] - d[1]
69-
nframesstr = isnothing(ntimeframes(X)) ? "" : " from $(ntimeframes(X)) frames"
67+
nframesstr = " from $(ntimeframes(X)) frames"
7068
clist = join([":$(n)" for n in names(X)], ", ")
7169
print(io, "RFFTSpectrumArray{$T}: $(nchannels(X)) channels: $(clist)\n $(nframes(X)) freqs $(first(d)) - $(last(d)) Hz, step ≈ $(step) Hz sampled at $(rate(X)) Hz$(nframesstr):\n ")
7270
print(io, data(X))

src/samplearray.jl

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ----- SampleArray ---------------------
22

3-
export SampleArray, getduration
3+
export SampleArray, getduration, toindexdelta
44

55
struct SampleArray{T} <: AbstractSampleArray{T}
66
# data::AbstractMatrix{T} # MUCH SLOWER! non-interleaving frames x channels
@@ -15,11 +15,11 @@ struct SampleArray{T} <: AbstractSampleArray{T}
1515
end
1616
end
1717

18-
SampleArray(x::AbstractMatrix{T}, rate::Frequency, names::Vector{Symbol}) where T = SampleArray{T}(x, toHz(rate), names)
19-
SampleArray(x::AbstractMatrix{T}, rate::Frequency) where T = SampleArray{T}(x, toHz(rate), _default_channel_names(size(x, 2)))
18+
SampleArray(x::AbstractMatrix{T}, rate::Frequency, names::Union{Nothing,Vector{Symbol}}=nothing) where T =
19+
SampleArray{T}(x, toHz(rate), isnothing(names) ? _default_channel_names(size(x, 2)) : names)
2020

21-
SampleArray(x::AbstractVector{T}, rate::Frequency, names::Vector{Symbol}) where T = SampleArray{T}(reshape(x, :, 1), toHz(rate), names)
22-
SampleArray(x::AbstractVector{T}, rate::Frequency) where T = SampleArray{T}(reshape(x, :, 1), toHz(rate), _default_channel_names(1))
21+
SampleArray(x::AbstractVector{T}, rate::Frequency, names::Union{Nothing,Vector{Symbol}}=nothing) where T =
22+
SampleArray{T}(reshape(x, :, 1), toHz(rate), isnothing(names) ? _default_channel_names(size(x, 2)) : names)
2323

2424
domain(x::SampleArray) = range(0, ((nframes(x)-1)/rate(x)); length=nframes(x))
2525

src/spectrumarray.jl

+21-18
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
1-
export SpectrumArray, tologfreq
1+
export SpectrumArray, SpectrumDomain, tologfreq
22

33
# ----- SpectrumArray ---------------------------
4+
const SpectrumDomain = Union{AbstractVector{<:Frequency}, AbstractRange{<:Frequency}}
5+
const SpectrumDomainReal = Union{AbstractVector{<:Real}, AbstractRange{<:Real}}
46

5-
struct SpectrumArray{T} <: AbstractSpectrumArray{T} # TODO complete implemetation!
7+
struct SpectrumArray{T,F<:SpectrumDomainReal} <: AbstractSpectrumArray{T}
68
data::Matrix{T} # non-interleaving frequencies x channels
79
rate::Float64 # original sampling rate
8-
freqs::Vector{Float64}
10+
freqs::F # in Hz
911
names::Vector{Symbol}
1012

11-
function SpectrumArray{T}(data::Matrix{T}, rate::Float64, freqs::Vector{Float64}, names::Vector{Symbol}) where T
13+
function SpectrumArray{T,F}(data::Matrix{T}, rate::Float64, freqs::F, names::Vector{Symbol}) where {T,F}
1214
if length(freqs) != size(data, 1)
1315
throw(DimensionMismatch("data size $(size(data, 1)) does not match the number of frequencies $(length(freqs))"))
1416
end
1517
_check_channel_names(names)
1618
length(names) == size(data, 2) || throw(DimensionMismatch("the number of names ($(length(names))) does not match the number of channels ($(size(data, 2)))!"))
1719
length(unique(freqs)) == length(freqs) || throw(ArgumentError("non-unique freqs!"))
18-
new(data, rate, freqs, copy(names))
20+
new{T,F}(data, rate, freqs, copy(names))
1921
end
2022
end
2123

22-
SpectrumArray(X::AbstractMatrix{T}, rate::Frequency, freqs::AbstractVector{<:Frequency}, names::Vector{Symbol}) where T =
23-
SpectrumArray{T}(X, toHz(rate), Float64.(toHz.(freqs)), names)
24-
SpectrumArray(X::AbstractMatrix{T}, rate::Frequency, freqs::AbstractVector{<:Frequency}) where T =
25-
SpectrumArray{T}(X, toHz(rate), Float64.(toHz.(freqs)), _default_channel_names(size(X, 2)))
24+
function SpectrumArray(X::AbstractMatrix{T}, rate::Frequency, freqs::SpectrumDomain, names::Union{Nothing,Vector{Symbol}}=nothing) where {T}
25+
freqs_ = toHz(freqs)
26+
F = typeof(freqs_)
27+
SpectrumArray{T,F}(X, toHz(rate), freqs_, isnothing(names) ? _default_channel_names(size(X, 2)) : names)
28+
end
2629

27-
SpectrumArray(X::AbstractVector{T}, rate::Frequency, freqs::AbstractVector{<:Frequency}, names::Vector{Symbol}) where T =
28-
SpectrumArray{T}(reshape(X, :, 1), toHz(rate), Float64.(toHz.(freqs)), names)
29-
SpectrumArray(X::AbstractVector{T}, rate::Frequency, freqs::AbstractVector{<:Frequency}) where T =
30-
SpectrumArray{T}(reshape(X, :, 1), toHz(rate), Float64.(toHz.(freqs)), _default_channel_names(1))
30+
SpectrumArray(X::AbstractVector{T}, rate::Frequency, freqs::F, names::Union{Nothing,Vector{Symbol}}=nothing) where {T, F<:SpectrumDomain} =
31+
SpectrumArray(reshape(X, :, 1), rate, freqs, names)
3132

3233
domain(X::SpectrumArray) = X.freqs
3334
_findno0freqs(X::SpectrumArray) = findall(!iszero, domain(X))
@@ -39,7 +40,7 @@ function toindex(X::SpectrumArray{T}, t::Frequency) where T
3940
findmin(diffs)[2]
4041
end
4142

42-
function Base.similar(X::SpectrumArray, t::Type{T}, dims::Dims, freqs::AbstractVector{<:Frequency}) where T
43+
function Base.similar(X::SpectrumArray, t::Type{T}, dims::Dims, freqs::SpectrumDomain) where T
4344
# tries to copy channel names
4445
# if there are fever names in the source array use default ones
4546
# TODO: move to abstracttypes.jl?
@@ -55,13 +56,13 @@ function Base.similar(X::SpectrumArray, t::Type{T}, dims::Dims) where T
5556
end
5657

5758
# redefined so frequencies & channel names are treated
58-
function Base.getindex(X::SpectrumArray{T}, I::R, J::S) where {T, R <: FrameIndex, S <: ChannelIndex}
59+
function Base.getindex(X::SpectrumArray{T,F}, I::R, J::S) where {T, F, R <: FrameIndex, S <: ChannelIndex}
5960
I2 = toframeidx(X, I)
6061
J2 = tochannelidx(X, J)
6162
freqs_ = domain(X)[I2]
6263
names_ = names(X)[J2]
6364
data_ = data(X)[I2, J2]
64-
SpectrumArray{T}(data_, rate(X), freqs_, names_)
65+
SpectrumArray{T,F}(data_, rate(X), freqs_, names_)
6566
end
6667

6768
Base.getindex(X::SpectrumArray{T}, I::R) where {T, R <: FrameIndex} = X[I, :]
@@ -72,7 +73,9 @@ function Base.hcat(X::SpectrumArray...)
7273
length(unique(domain.(X))) == 1 || throw(ArgumentError("hcat: non-unique domains!"))
7374
newnames = _unique_channel_names(X...)
7475
data_ = hcat(map(data, X)...)
75-
return eltype(X)(data_, rate(X[1]), domain(X[1]), newnames) # eltype gives common supertype
76+
rate_ = rate(X[1])
77+
domain_ = domain(X[1])
78+
return SpectrumArray{eltype(data_),typeof(domain_)}(data_, rate_, domain_, newnames) # eltype gives common supertype
7679
end
7780

7881
function Base.vcat(X::SpectrumArray...)
@@ -82,7 +85,7 @@ function Base.vcat(X::SpectrumArray...)
8285
length(unique(namelists)) == 1 || throw(ArgumentError("vcat: non-unique channel names!"))
8386
data_ = vcat(map(data, X)...)
8487
freqs_ = vcat(map(domain, X)...)
85-
return eltype(X)(data_, rate(X[1]), freqs_, namelists[1])
88+
return SpectrumArray{eltype(data_),typeof(freqs_)}(data_, rate(X[1]), freqs_, namelists[1])
8689
end
8790

8891
function Base.show(io::IO, ::MIME"text/plain", X::SpectrumArray{T}) where T

src/units.jl

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export Frequency, Time, Distance, Velocity
2+
export toHz, tos, tom, tomps
3+
4+
# ----- Frequency and Time ----------------------
5+
const Frequency = Union{Real, Unitful.Frequency}
6+
7+
toHz(f::Real) = Float64(f)
8+
toHz(f::Unitful.Frequency) = ustrip(Float64, Hz, f)
9+
toHz(fa::AbstractArray{<:Frequency}) = toHz.(fa)
10+
toHz(fr::AbstractRange{<:Unitful.Frequency}) = toHz(minimum(fr)):toHz(step(fr)):toHz(maximum(fr))
11+
12+
const Time = Union{Real, Unitful.Time}
13+
14+
tos(t::Real) = Float64(t)
15+
tos(t::Unitful.Time) = ustrip(Float64, s, t)
16+
tos(ta::AbstractArray{<:Time}) = tos.(ta)
17+
tos(tr::AbstractRange{<:Unitful.Time}) = tos(minimum(tr)):tos(step(tr)):tos(maximum(tr))
18+
19+
const Distance = Union{Real, Unitful.Length}
20+
21+
tom(s::Real) = Float64(s)
22+
tom(s::Unitful.Length) = ustrip(Float64, u"m", s)
23+
tom(sa::AbstractArray{<:Distance}) = tom.(sa)
24+
tom(sr::AbstractRange{<:Unitful.Length}) = tom(minimum(sr)):tom(step(sr)):tom(maximum(sr))
25+
26+
const Velocity = Union{Real, Unitful.Velocity}
27+
28+
tomps(v::Velocity) = Float64(v)
29+
tomps(v::Unitful.Velocity) = ustrip(Float64, u"m/s", v)
30+
tomps(va::AbstractArray{<:Velocity}) = tomps.(va)
31+
tomps(vr::AbstractRange{<:Unitful.Velocity}) = tomps(minimum(vr)):tomps(step(vr)):tomps(maximum(vr))

0 commit comments

Comments
 (0)