From 18f556feb23573c3e2f347b488764d5be20ac711 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 19 Nov 2019 11:59:35 -0500 Subject: [PATCH 1/5] Removes empty deprecated.jl file --- src/deprecated.jl | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/deprecated.jl diff --git a/src/deprecated.jl b/src/deprecated.jl deleted file mode 100644 index e69de29..0000000 From 67e6e0a441ace8c62934c40d9ee10166beebdbb9 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 20 Nov 2019 21:33:19 -0500 Subject: [PATCH 2/5] Compat cruft cleanup --- Project.toml | 23 +++++++---------------- src/SampleBuf.jl | 4 ++-- src/SampledSignals.jl | 20 ++++---------------- test/DummySampleStream.jl | 3 +-- test/SampleBuf.jl | 9 ++------- test/SampleStream.jl | 3 +-- test/runtests.jl | 2 +- 7 files changed, 18 insertions(+), 46 deletions(-) diff --git a/Project.toml b/Project.toml index 3634edf..1f97579 100644 --- a/Project.toml +++ b/Project.toml @@ -3,43 +3,34 @@ uuid = "bd7594eb-a658-542f-9e75-4c4d8908c167" version = "2.1.0" [deps] -Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" +Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" TreeViews = "a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [compat] -Compat = "2" DSP = "0.6.1" FFTW = "1.1.0" FixedPointNumbers = "0.6.1" IntervalSets = "0.3.2" +TreeViews = "0.3" Unitful = "0.17.0" julia = "1" -TreeViews = "0.3" [extras] DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -# LibSndFile = "b13ce0c6-77b0-50c6-a2db-140568b8d1a5" -Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" -Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" +IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TreeViews = "a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7" -IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["DSP", - "FileIO", - "FixedPointNumbers", - # "LibSndFile", - "Unitful", - "Compat", - "SampledSignals", - "TreeViews", - "IntervalSets"] +test = ["Test", "DSP", "FileIO", "FixedPointNumbers", "Unitful", "SampledSignals", "TreeViews", "IntervalSets"] diff --git a/src/SampleBuf.jl b/src/SampleBuf.jl index 85f22a3..5d547af 100644 --- a/src/SampleBuf.jl +++ b/src/SampleBuf.jl @@ -76,7 +76,7 @@ domain(buf::AbstractSampleBuf) = range(0.0, stop=(nframes(buf)-1)/samplerate(buf import Base: +, -, *, / import Base.broadcast -const ArrayIsh = Union{Array, SubArray, Compat.LinRange, StepRangeLen} +const ArrayIsh = Union{Array, SubArray, LinRange, StepRangeLen} # Broadcasting in Julia 0.7 @@ -185,7 +185,7 @@ function showchannels(io::IO, buf::AbstractSampleBuf, widthchars=80) for blk in 1:nblocks i = (blk-1)*blockwidth + 1 n = min(blockwidth, nframes(buf)-i+1) - peaks = Compat.maximum(abs.(float.(buf[(1:n) .+ i .- 1, :])), dims=1) + peaks = maximum(abs.(float.(buf[(1:n) .+ i .- 1, :])), dims=1) # clamp to -60dB, 0dB peaks = clamp.(20log10.(peaks), -60.0, 0.0) idxs = trunc.(Int, (peaks.+60)/60 * (length(ticks)-1)) .+ 1 diff --git a/src/SampledSignals.jl b/src/SampledSignals.jl index a8a88a6..774ddaa 100644 --- a/src/SampledSignals.jl +++ b/src/SampledSignals.jl @@ -1,6 +1,3 @@ -# precompile is now the default -VERSION < v"0.7.0-rc2" && __precompile__() - module SampledSignals using IntervalSets @@ -25,20 +22,12 @@ using Unitful using Unitful: ns, ms, µs, s, Hz, kHz, MHz, GHz, THz using FixedPointNumbers using DSP -using Compat -using Compat: AbstractRange, undef, range -using Compat.Random: randstring -using Compat.Base64: base64encode +using Random: randstring +using Base64: base64encode using TreeViews: TreeViews -if VERSION >= v"0.7.0-DEV" - using LinearAlgebra: mul! - import FFTW -else - const mul! = A_mul_B! - import Compat.FFTW -end - +using LinearAlgebra: mul! +import FFTW import Base: show const PCM8Sample = Fixed{Int8, 7} @@ -53,7 +42,6 @@ include("SampleBuf.jl") include("SampleStream.jl") include("SignalGen/SinSource.jl") include("WAVDisplay.jl") -include("deprecated.jl") """ metadata(x, key::Symbol) diff --git a/test/DummySampleStream.jl b/test/DummySampleStream.jl index 17a547d..f1d364c 100644 --- a/test/DummySampleStream.jl +++ b/test/DummySampleStream.jl @@ -1,5 +1,4 @@ -using Compat.Test -import Compat: undef +using Test @testset "DummySampleStream Tests" begin DEFAULT_SR = 48000 diff --git a/test/SampleBuf.jl b/test/SampleBuf.jl index 8f58666..02655c4 100644 --- a/test/SampleBuf.jl +++ b/test/SampleBuf.jl @@ -1,13 +1,8 @@ -using Compat.Test -using Compat: undef, range +using Test using SampledSignals using Unitful using DSP -if VERSION >= v"0.7.0-DEV" - import FFTW -else - import Compat.FFTW -end +import FFTW @testset "SampleBuf Tests" begin TEST_SR = 48000 diff --git a/test/SampleStream.jl b/test/SampleStream.jl index 297703b..5351b3f 100644 --- a/test/SampleStream.jl +++ b/test/SampleStream.jl @@ -1,5 +1,4 @@ -using Compat.Test -import Compat: undef +using Test using SampledSignals using DSP include("support/util.jl") diff --git a/test/runtests.jl b/test/runtests.jl index ac11e00..e14845b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,5 @@ using SampledSignals -using Compat.Test +using Test using DSP using FixedPointNumbers using FileIO: File, Stream, @format_str From e57144e9a5b1146800a03f98b6ef812314bd7730 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 21 Nov 2019 12:14:24 -0500 Subject: [PATCH 3/5] Simplifies broadcasting, fixes issue with broadcasted `^` also adds some docstrings --- src/SampleBuf.jl | 137 ++++++++++++++++++---------------------------- test/SampleBuf.jl | 4 ++ 2 files changed, 58 insertions(+), 83 deletions(-) diff --git a/src/SampleBuf.jl b/src/SampleBuf.jl index 5d547af..fb293ce 100644 --- a/src/SampleBuf.jl +++ b/src/SampleBuf.jl @@ -1,3 +1,5 @@ +using Base.Broadcast: Broadcasted, ArrayStyle + abstract type AbstractSampleBuf{T, N} <: AbstractArray{T, N} end """ @@ -48,11 +50,36 @@ SpectrumBuf(T::Type, sr, len::Quantity, ch) = # frame - a collection of samples from each channel that were sampled simultaneously # audio methods +""" + samplerate(x) + +Returns the sampling rate of `x` +""" samplerate(buf::AbstractSampleBuf) = buf.samplerate + +""" + nchannels(x) + +Returns the number of channels in the buffer or stream `x`. +""" nchannels(buf::AbstractSampleBuf{T, 2}) where {T} = size(buf.data, 2) nchannels(buf::AbstractSampleBuf{T, 1}) where {T} = 1 + +""" + nframes(x) + +Returns the length of `x` in frames (time instants). Each frame may have +multiple channels. +""" nframes(buf::AbstractSampleBuf) = size(buf.data, 1) +""" + samplerate!(buf, 44100) + +Set the samplerate of `buf` without modifying the audio. In effect this speeds +up or slows down the signal, assuming it's played back at the original +samplerate. +""" function samplerate!(buf::AbstractSampleBuf, sr) buf.samplerate = sr @@ -67,95 +94,38 @@ nchannels(arr::AbstractArray) = size(arr, 2) # right type, instead of just a bare array Base.similar(buf::SampleBuf, ::Type{T}, dims::Dims) where {T} = SampleBuf(Array{T}(undef, dims), samplerate(buf)) Base.similar(buf::SpectrumBuf, ::Type{T}, dims::Dims) where {T} = SpectrumBuf(Array{T}(undef, dims), samplerate(buf)) -domain(buf::AbstractSampleBuf) = range(0.0, stop=(nframes(buf)-1)/samplerate(buf), length=nframes(buf)) - -# There's got to be a better way to define these functions, but the dispatch -# and broadcast behavior for AbstractArrays is complex and has subtle differences -# between Julia versions, so we basically just override functions here as they -# come up as problems -import Base: +, -, *, / -import Base.broadcast - -const ArrayIsh = Union{Array, SubArray, LinRange, StepRangeLen} - - -# Broadcasting in Julia 0.7 -# `find_buf` has borrowed from https://docs.julialang.org/en/latest/manual/interfaces/#Selecting-an-appropriate-output-array-1 -if VERSION >= v"0.7.0-DEV-4936" # Julia PR 26891 - find_buf(args::Tuple) = find_buf(find_buf(args[1]), Base.tail(args)) - find_buf(x) = x -end # if VERSION +""" + domain(buf) -for btype in (:SampleBuf, :SpectrumBuf) - - if VERSION >= v"0.7.0-DEV-4936" # Julia PR 26891 - - @eval find_buf(bc::Base.Broadcast.Broadcasted{Broadcast.ArrayStyle{$btype{T,N}}}) where {T,N} = find_buf(bc.args) - @eval find_buf(::$btype, args::Tuple{$btype}) = args[1] - @eval find_buf(::Any, args::Tuple{$btype}) = args[1] - @eval find_buf(a::$btype, rest) = a - - @eval Base.BroadcastStyle(::Type{$btype{T,N}}) where {T,N} = Broadcast.ArrayStyle{$btype{T,N}}() - @eval function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{$btype{T,N}}}, ::Type{ElType}) where {T,N,ElType} - A = find_buf(bc) - $btype(Array{ElType}(undef, length.(axes(bc))), samplerate(A)) - end - - else - - # define non-broadcasting arithmetic - for op in (:+, :-) - @eval function $op(A1::$btype, A2::$btype) - if !isapprox(samplerate(A1), samplerate(A2)) - error("samplerate-converting arithmetic not supported yet") - end - $btype($op(A1.data, A2.data), samplerate(A1)) - end - @eval function $op(A1::$btype, A2::ArrayIsh) - $btype($op(A1.data, A2), samplerate(A1)) - end - @eval function $op(A1::ArrayIsh, A2::$btype) - $btype($op(A1, A2.data), samplerate(A2)) - end - end - - # define non-broadcast scalar arithmetic - for op in (:+, :-, :*, :/) - @eval function $op(A1::$btype, a2::Number) - $btype($op(A1.data, a2), samplerate(A1)) - end - @eval function $op(a1::Number, A2::$btype) - $btype($op(a1, A2.data), samplerate(A2)) - end - end +Returns the range of time (for `SampleBuf`s) or frequency (for `SpectrumBuf`s) +corresponding to each sample of `buf`. +""" +domain(buf::AbstractSampleBuf) = range(0.0, stop=(nframes(buf)-1)/samplerate(buf), length=nframes(buf)) - # define broadcasting application - @eval function broadcast(op, A1::$btype, A2::$btype) - if !isapprox(samplerate(A1), samplerate(A2)) - error("samplerate-converting arithmetic not supported yet") - end - $btype(broadcast(op, A1.data, A2.data), samplerate(A1)) - end - @eval function broadcast(op, A1::$btype, A2::ArrayIsh) - $btype(broadcast(op, A1.data, A2), samplerate(A1)) - end - @eval function broadcast(op, A1::ArrayIsh, A2::$btype) - $btype(broadcast(op, A1, A2.data), samplerate(A2)) - end - @eval function broadcast(op, a1::Number, A2::$btype) - $btype(broadcast(op, a1, A2.data), samplerate(A2)) - end - @eval function broadcast(op, A1::$btype, a2::Number) - $btype(broadcast(op, A1.data, a2), samplerate(A1)) - end - @eval function broadcast(op, A1::$btype) - $btype(broadcast(op, A1.data), samplerate(A1)) +# inherit the samplerate from the first argument. TODO: we should probably +# throw an error if there are multiple SampleBufs with different sample rates. +for B in (SampleBuf, SpectrumBuf) + @eval Base.BroadcastStyle(::Type{<:$B}) = ArrayStyle{$B}() + @eval function Base.similar(bc::Broadcasted{ArrayStyle{$B}}, ::Type{T}) where T + srs = find_srs(bc) + @assert length(srs) >= 1 + if any(!=(first(srs)), srs) + throw(ArgumentError("All samplerates in a broadcasting expression must match")) end + $B(similar(Array{T}, axes(bc)), first(srs)) + end +end - end # if VERSION +# to see more about customizing broadcasting see this: +# https://docs.julialang.org/en/latest/manual/interfaces/#Selecting-an-appropriate-output-array-1 -end # for btype +find_srs(bc::Broadcasted) = find_srs(bc.args) +find_srs(args::Tuple) = (find_srs(first(args))..., + find_srs(Base.tail(args))...) +find_srs(buf::AbstractSampleBuf) = (samplerate(buf),) +find_srs(::Tuple{}) = () +find_srs(::Any) = () typename(::SampleBuf{T, N}) where {T, N} = "SampleBuf{$T, $N}" unitname(::SampleBuf) = "s" @@ -166,6 +136,7 @@ srname(::SpectrumBuf) = "s" # from @mbauman's Sparklines.jl package const ticks = ['▁','▂','▃','▄','▅','▆','▇','█'] + # 3-arg version (with explicit mimetype) is needed because we subtype AbstractArray, # and there's a 3-arg version defined in show.jl function show(io::IO, ::MIME"text/plain", buf::AbstractSampleBuf) diff --git a/test/SampleBuf.jl b/test/SampleBuf.jl index 02655c4..45406df 100644 --- a/test/SampleBuf.jl +++ b/test/SampleBuf.jl @@ -262,6 +262,10 @@ import FFTW quot = buf1 ./ buf2 @test quot == SampleBuf(arr1 ./ arr2, TEST_SR) @test typeof(quot) == typeof(buf1) + + sqr = buf1 .^ 2 + @test sqr == SampleBuf(arr1 .^ 2, TEST_SR) + @test typeof(sqr) == typeof(buf1) end @testset "Arithmetic with constants gives SampleBufs" begin From db11ae0ccbe59cce85ccfc159ac8a487c22785a3 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Mon, 23 Dec 2019 14:14:56 -0500 Subject: [PATCH 4/5] fixes `conv` when eltype promotion is needed --- src/SampleBuf.jl | 16 ++++++++-------- test/SampleBuf.jl | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/SampleBuf.jl b/src/SampleBuf.jl index fb293ce..a33986e 100644 --- a/src/SampleBuf.jl +++ b/src/SampleBuf.jl @@ -290,21 +290,21 @@ FFTW.ifft(buf::SpectrumBuf) = SampleBuf(FFTW.ifft(buf.data), nframes(buf)/sample # does a per-channel convolution on SampleBufs for buftype in (:SampleBuf, :SpectrumBuf) - @eval function DSP.conv(b1::$buftype{T, 1}, b2::$buftype{T, 1}) where {T} + @eval function DSP.conv(b1::$buftype{T1, 1}, b2::$buftype{T2, 1}) where {T1, T2} if !isapprox(samplerate(b1), samplerate(b2)) error("Resampling convolution not yet supported") end $buftype(conv(b1.data, b2.data), samplerate(b1)) end - @eval function DSP.conv(b1::$buftype{T, N1}, b2::$buftype{T, N2}) where {T, N1, N2} + @eval function DSP.conv(b1::$buftype{T1, N1}, b2::$buftype{T2, N2}) where {T1, T2, N1, N2} if !isapprox(samplerate(b1), samplerate(b2)) error("Resampling convolution not yet supported") end if nchannels(b1) != nchannels(b2) error("Broadcasting convolution not yet supported") end - out = $buftype(T, samplerate(b1), nframes(b1)+nframes(b2)-1, nchannels(b1)) + out = $buftype(promote_type(T1, T2), samplerate(b1), nframes(b1)+nframes(b2)-1, nchannels(b1)) for ch in 1:nchannels(b1) out[:, ch] = conv(b1.data[:, ch], b2.data[:, ch]) end @@ -312,17 +312,17 @@ for buftype in (:SampleBuf, :SpectrumBuf) out end - @eval function DSP.conv(b1::$buftype{T, 1}, b2::StridedVector{T}) where {T} + @eval function DSP.conv(b1::$buftype{T1, 1}, b2::StridedVector{T2}) where {T1, T2} $buftype(conv(b1.data, b2), samplerate(b1)) end - @eval DSP.conv(b1::StridedVector{T}, b2::$buftype{T, 1}) where {T} = conv(b2, b1) + @eval DSP.conv(b1::StridedVector{T1}, b2::$buftype{T2, 1}) where {T1, T2} = conv(b2, b1) - @eval function DSP.conv(b1::$buftype{T, 2}, b2::StridedMatrix{T}) where {T} + @eval function DSP.conv(b1::$buftype{T1, 2}, b2::StridedMatrix{T2}) where {T1, T2} if nchannels(b1) != nchannels(b2) error("Broadcasting convolution not yet supported") end - out = $buftype(T, samplerate(b1), nframes(b1)+nframes(b2)-1, nchannels(b1)) + out = $buftype(promote_type(T1, T2), samplerate(b1), nframes(b1)+nframes(b2)-1, nchannels(b1)) for ch in 1:nchannels(b1) out[:, ch] = conv(b1.data[:, ch], b2[:, ch]) end @@ -330,5 +330,5 @@ for buftype in (:SampleBuf, :SpectrumBuf) out end - @eval DSP.conv(b1::StridedMatrix{T}, b2::$buftype{T, 2}) where {T} = conv(b2, b1) + @eval DSP.conv(b1::StridedMatrix{T1}, b2::$buftype{T2, 2}) where {T1, T2} = conv(b2, b1) end diff --git a/test/SampleBuf.jl b/test/SampleBuf.jl index 45406df..7eaf872 100644 --- a/test/SampleBuf.jl +++ b/test/SampleBuf.jl @@ -375,11 +375,23 @@ import FFTW @testset "1D SampleBufs and SpectrumBufs can be convolved" begin arr1 = rand(TEST_T, 8) arr2 = rand(TEST_T, 10) + @assert TEST_T == Float32 + arr3 = rand(Float64, 8) + for T in (SampleBuf, SpectrumBuf) - result = T(conv(arr1, arr2), TEST_SR) - @test conv(T(arr1, TEST_SR), T(arr2, TEST_SR)) == result - @test conv(T(arr1, TEST_SR), arr2) == result - @test conv(arr1, T(arr2, TEST_SR)) == result + result1 = T(conv(arr1, arr2), TEST_SR) + @test conv(T(arr1, TEST_SR), T(arr2, TEST_SR)) == result1 + @test conv(T(arr1, TEST_SR), arr2) == result1 + @test conv(arr1, T(arr2, TEST_SR)) == result1 + + # test with eltype conversions + result2 = T(conv(arr1, arr3), TEST_SR) + @test conv(T(arr1, TEST_SR), T(arr3, TEST_SR)) == result2 + @test eltype(conv(T(arr1, TEST_SR), T(arr3, TEST_SR))) == eltype(result2) + @test conv(T(arr1, TEST_SR), arr3) == result2 + @test eltype(conv(T(arr1, TEST_SR), arr3)) == eltype(result2) + @test conv(arr1, T(arr3, TEST_SR)) == result2 + @test eltype(conv(arr1, T(arr3, TEST_SR))) == eltype(result2) end end @@ -518,4 +530,31 @@ import FFTW display(TextDisplay(iobuf), buf) @test String(take!(iobuf)) == expected end + + # it's not clear how promotion should work - usually promotion is done in + # terms of types, but we also want to promote between values with different + # samplerates. Defining promotion rules in terms of AbstractArray seems + # problematic when both arguments are SampleBufs but with different + # sampling rates. + + # for me this seems to come up most often in the context of convolving + # Float32 samplebufs (from loading files) with Float64 filters, which + # triggers promotion, which fails. so for now we just add more `conv` + # methods + # @testset "SampleBufs and Arrays can be combined via promotion" begin + # buf = SampleBuf(rand(10), 48000) + # arr = rand(Int, 10) + # res1, res2 = promote(buf, arr) + # @test samplerate(res1) == samplerate(res2) == 48000 + # @test res1.data == buf.data + # @test res2.data ≈ arr + # @test eltype(res1) == eltype(res2) == Float64 + # + # buf = SampleBuf(rand(Int, 10), 48000) + # arr = rand(10) + # res1, res2 = promote(buf, arr) + # @test res1.data ≈ buf.data + # @test res2.data == arr + # @test eltype(res1) == eltype(res2) == Float64 + # end end From 9a1c7922afd5e58e60a26d19bc76d0e2cc394dd2 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Mon, 30 Dec 2019 14:18:10 -0500 Subject: [PATCH 5/5] use broadcasting more consistently in tests --- test/SampleBuf.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/SampleBuf.jl b/test/SampleBuf.jl index 7eaf872..39a1aff 100644 --- a/test/SampleBuf.jl +++ b/test/SampleBuf.jl @@ -244,8 +244,8 @@ import FFTW buf1 = SampleBuf(arr1, TEST_SR) buf2 = SampleBuf(arr2, TEST_SR) - sum = buf1 + buf2 - @test sum == SampleBuf(arr1 + arr2, TEST_SR) + sum = buf1 .+ buf2 + @test sum == SampleBuf(arr1 .+ arr2, TEST_SR) @test typeof(sum) == typeof(buf1) sum = buf1 .+ buf2 @test sum == SampleBuf(arr1 .+ arr2, TEST_SR) @@ -253,8 +253,8 @@ import FFTW prod = buf1 .* buf2 @test prod == SampleBuf(arr1 .* arr2, TEST_SR) @test typeof(prod) == typeof(buf1) - diff = buf1 - buf2 - @test diff == SampleBuf(arr1 - arr2, TEST_SR) + diff = buf1 .- buf2 + @test diff == SampleBuf(arr1 .- arr2, TEST_SR) @test typeof(diff) == typeof(buf1) diff = buf1 .- buf2 @test diff == SampleBuf(arr1 .- arr2, TEST_SR)