diff --git a/.travis.yml b/.travis.yml index 67188dd..1e3907d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,7 @@ os: - linux - osx julia: - # - release - - 0.4 - - 0.5 + - 0.6 notifications: email: false script: diff --git a/README.md b/README.md index 25dd8dc..4bd00d2 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ write(wrapper, source) The `ResampleSink` wrapper type wraps around a sink. Writing to this wrapper sink will resample the given data and pass it to the original sink. It maintains state between writes so that the interpolation is correct across the boundaries of multiple writes. -Currently `ResampleSink` handles resampling with simple linear interpolation and no lowpass filtering when downsampling. In the future we will likely implement other resampling methods. +`ResampleSink` handles resampling with polyphase FIR resampling filter. ### Channel Conversion diff --git a/REQUIRE b/REQUIRE index 62c30e6..2a654e1 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,5 +1,4 @@ -julia 0.4 +julia 0.6- SIUnits FixedPointNumbers -Compat 0.8.8 DSP diff --git a/appveyor.yml b/appveyor.yml index 13d56d5..724b2e3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,6 @@ environment: matrix: - - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" - - JULIAVERSION: "julialang/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" + - JULIAVERSION: "julialang/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" notifications: - provider: Email diff --git a/runtests.sh b/runtests.sh index 6faaad2..7a5e171 100755 --- a/runtests.sh +++ b/runtests.sh @@ -3,7 +3,7 @@ # Runs the SampledSignals tests including generating an lcov.info file # abort on failure -set -e +# set -e julia -e 'using Coverage; clean_folder(".");' julia --color=yes --inline=no --code-coverage=user test/runtests.jl diff --git a/src/Interval.jl b/src/Interval.jl index 2186dd0..d193236 100644 --- a/src/Interval.jl +++ b/src/Interval.jl @@ -10,7 +10,7 @@ is defined for Intervals of `Number` and `Dates.AbstractTime`. ### Type parameters ```julia -immutable Interval{T} +struct Interval{T} ``` * `T` : the type of the interval's endpoints. Must be a concrete leaf type. @@ -35,16 +35,16 @@ A[0.0 .. 0.5] ``` """ -> -immutable Interval{T} +struct Interval{T} lo::T hi::T - function Interval(lo, hi) + function Interval{T}(lo, hi) where {T} lo <= hi ? new(lo, hi) : throw(ArgumentError("lo must be less than or equal to hi")) end end -Interval{T}(a::T,b::T) = Interval{T}(a,b) +Interval(a::T,b::T) where {T} = Interval{T}(a,b) # Allow promotion during construction, but only if it results in a leaf type -function Interval{T,S}(a::T, b::S) +function Interval(a::T, b::S) where {T, S} (a2, b2) = promote(a, b) typeof(a2) == typeof(b2) || throw(ArgumentError("cannot promote $a and $b to a common type")) Interval(a2, b2) @@ -53,9 +53,9 @@ const .. = Interval Base.print(io::IO, i::Interval) = print(io, "$(i.lo)..$(i.hi)") -Base.convert{T}(::Type{Interval{T}}, x::T) = Interval{T}(x,x) -Base.convert{T,S}(::Type{Interval{T}}, x::S) = (y=convert(T, x); Interval{T}(y,y)) -Base.convert{T}(::Type{Interval{T}}, w::Interval) = Interval{T}(convert(T, w.lo), convert(T, w.hi)) +Base.convert(::Type{Interval{T}}, x::T) where {T} = Interval{T}(x,x) +Base.convert(::Type{Interval{T}}, x::S) where {T, S} = (y=convert(T, x); Interval{T}(y,y)) +Base.convert(::Type{Interval{T}}, w::Interval) where {T} = Interval{T}(convert(T, w.lo), convert(T, w.hi)) # Promotion rules for "promiscuous" types like Intervals and SIUnits, which both # simply wrap any Number, are often ambiguous. That is, which type should "win" @@ -76,10 +76,10 @@ Base.convert{T}(::Type{Interval{T}}, w::Interval) = Interval{T}(convert(T, w.lo) # downside is that Intervals are not as useful as they could be; they really # could be considered as <: Number themselves. We do this in general for any # supported Scalar: -typealias Scalar Union{Number, Dates.AbstractTime} -Base.promote_rule{T<:Scalar}(::Type{Interval{T}}, ::Type{T}) = Interval{T} -Base.promote_rule{T,S<:Scalar}(::Type{Interval{T}}, ::Type{S}) = Interval{promote_type(T,S)} -Base.promote_rule{T,S}(::Type{Interval{T}}, ::Type{Interval{S}}) = Interval{promote_type(T,S)} +const Scalar = Union{Number, Dates.AbstractTime} +Base.promote_rule(::Type{Interval{T}}, ::Type{T}) where {T<:Scalar} = Interval{T} +Base.promote_rule(::Type{Interval{T}}, ::Type{S}) where {T,S<:Scalar} = Interval{promote_type(T,S)} +Base.promote_rule(::Type{Interval{T}}, ::Type{Interval{S}}) where {T,S} = Interval{promote_type(T,S)} import Base: ==, +, -, *, /, ^ ==(a::Interval, b::Interval) = a.lo == b.lo && a.hi == b.hi diff --git a/src/SampleBuf.jl b/src/SampleBuf.jl index e4bc22c..c1180fa 100644 --- a/src/SampleBuf.jl +++ b/src/SampleBuf.jl @@ -1,4 +1,4 @@ -abstract AbstractSampleBuf{T, N} <: AbstractArray{T, N} +abstract type AbstractSampleBuf{T, N} <: AbstractArray{T, N} end """ Represents a multi-channel regularly-sampled buffer that stores its own sample @@ -8,13 +8,13 @@ buffer will be an MxC matrix. So a 1-second stereo audio buffer sampled at 44100Hz with 32-bit floating-point samples in the time domain would have the type SampleBuf{Float32, 2}. """ -type SampleBuf{T, N} <: AbstractSampleBuf{T, N} +mutable struct SampleBuf{T, N} <: AbstractSampleBuf{T, N} data::Array{T, N} samplerate::Float64 end # define constructor so conversion is applied to `sr` -SampleBuf{T, N}(arr::Array{T, N}, sr::Real) = SampleBuf{T, N}(arr, sr) +SampleBuf(arr::Array{T, N}, sr::Real) where {T, N} = SampleBuf{T, N}(arr, sr) """ Represents a multi-channel regularly-sampled buffer representing the frequency- @@ -24,16 +24,16 @@ C-channel buffer will be an MxC matrix. So a 1-second stereo audio buffer sampled at 44100Hz with 32-bit floating-point samples in the time domain would have the type SampleBuf{Float32, 2}. """ -type SpectrumBuf{T, N} <: AbstractSampleBuf{T, N} +mutable struct SpectrumBuf{T, N} <: AbstractSampleBuf{T, N} data::Array{T, N} samplerate::Float64 end # define constructor so conversion is applied to `sr` -SpectrumBuf{T, N}(arr::Array{T, N}, sr::Real) = SpectrumBuf{T, N}(arr, sr) +SpectrumBuf(arr::Array{T, N}, sr::Real) where {T, N} = SpectrumBuf{T, N}(arr, sr) -SampleBuf(T::Type, sr, dims...) = SampleBuf(Array(T, dims...), sr) -SpectrumBuf(T::Type, sr, dims...) = SpectrumBuf(Array(T, dims...), sr) +SampleBuf(T::Type, sr, dims...) = SampleBuf(Array{T}(dims...), sr) +SpectrumBuf(T::Type, sr, dims...) = SpectrumBuf(Array{T}(dims...), sr) SampleBuf(T::Type, sr, len::SecondsQuantity) = SampleBuf(T, sr, round(Int, float(len)*sr)) SampleBuf(T::Type, sr, len::SecondsQuantity, ch) = SampleBuf(T, sr, round(Int, float(len)*sr), ch) SpectrumBuf(T::Type, sr, len::HertzQuantity) = SpectrumBuf(T, sr, round(Int, float(len)*sr)) @@ -46,8 +46,8 @@ SpectrumBuf(T::Type, sr, len::HertzQuantity, ch) = SpectrumBuf(T, sr, round(Int, # audio methods samplerate(buf::AbstractSampleBuf) = buf.samplerate -nchannels{T}(buf::AbstractSampleBuf{T, 2}) = size(buf.data, 2) -nchannels{T}(buf::AbstractSampleBuf{T, 1}) = 1 +nchannels(buf::AbstractSampleBuf{T, 2}) where {T} = size(buf.data, 2) +nchannels(buf::AbstractSampleBuf{T, 1}) where {T} = 1 nframes(buf::AbstractSampleBuf) = size(buf.data, 1) function samplerate!(buf::AbstractSampleBuf, sr) @@ -62,46 +62,74 @@ nchannels(arr::AbstractArray) = size(arr, 2) # it's important to define Base.similar so that range-indexing returns the # right type, instead of just a bare array -Base.similar{T}(buf::SampleBuf, ::Type{T}, dims::Dims) = SampleBuf(Array(T, dims), samplerate(buf)) -Base.similar{T}(buf::SpectrumBuf, ::Type{T}, dims::Dims) = SpectrumBuf(Array(T, dims), samplerate(buf)) +Base.similar(buf::SampleBuf, ::Type{T}, dims::Dims) where {T} = SampleBuf(Array{T}(dims), samplerate(buf)) +Base.similar(buf::SpectrumBuf, ::Type{T}, dims::Dims) where {T} = SpectrumBuf(Array{T}(dims), samplerate(buf)) domain(buf::AbstractSampleBuf) = linspace(0.0, (nframes(buf)-1)/samplerate(buf), 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: +, -, *, / +import Base.broadcast +const ArrayIsh = Union{Array, SubArray, LinSpace, StepRangeLen} for btype in (:SampleBuf, :SpectrumBuf) - for op in (:.*, :+, :./, :-) - @eval function $(op)(A1::$btype, A2::$btype) + # 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)) + $btype($op(A1.data, A2.data), samplerate(A1)) end - @eval function $(op)(A1::$btype, A2::Union{Array, SubArray, LinSpace}) - $btype($(op)(A1.data, A2), samplerate(A1)) + @eval function $op(A1::$btype, A2::ArrayIsh) + $btype($op(A1.data, A2), samplerate(A1)) end - @eval function $(op)(A1::Union{Array, SubArray, LinSpace}, A2::$btype) - $btype($(op)(A1, A2.data), samplerate(A2)) + @eval function $op(A1::ArrayIsh, A2::$btype) + $btype($op(A1, A2.data), samplerate(A2)) end end - for op in (:*, :/) - @eval function $(op)(A1::$btype, a2::Number) - $btype($(op)(A1.data, a2), samplerate(A1)) + # 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 - @eval function $(op)(a1::Number, A2::$btype) - $btype($(op)(a1, A2.data), samplerate(A2)) + $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)) + 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 end -typename{T, N}(::SampleBuf{T, N}) = "SampleBuf{$T, $N}" +typename(::SampleBuf{T, N}) where {T, N} = "SampleBuf{$T, $N}" unitname(::SampleBuf) = "s" srname(::SampleBuf) = "Hz" -typename{T, N}(::SpectrumBuf{T, N}) = "SpectrumBuf{$T, $N}" +typename(::SpectrumBuf{T, N}) where {T, N} = "SpectrumBuf{$T, $N}" unitname(::SpectrumBuf) = "Hz" srname(::SpectrumBuf) = "s" @@ -109,7 +137,7 @@ srname(::SpectrumBuf) = "s" 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 -@compat function show(io::IO, ::MIME"text/plain", buf::AbstractSampleBuf) +function show(io::IO, ::MIME"text/plain", buf::AbstractSampleBuf) println(io, "$(nframes(buf))-frame, $(nchannels(buf))-channel $(typename(buf))") len = nframes(buf) / samplerate(buf) ustring = unitname(buf) @@ -122,19 +150,19 @@ function showchannels(io::IO, buf::AbstractSampleBuf, widthchars=80) # number of samples per block blockwidth = round(Int, nframes(buf)/widthchars, RoundUp) nblocks = round(Int, nframes(buf)/blockwidth, RoundUp) - blocks = Array(Char, nblocks, nchannels(buf)) + blocks = Array{Char}(nblocks, nchannels(buf)) for blk in 1:nblocks i = (blk-1)*blockwidth + 1 n = min(blockwidth, nframes(buf)-i+1) - peaks = maximum(abs(float(buf[(1:n)+i-1, :])), 1) + peaks = maximum(abs.(float(buf[(1:n)+i-1, :])), 1) # clamp to -60dB, 0dB - peaks = clamp(20log10(peaks), -60.0, 0.0) - idxs = trunc(Int, (peaks+60)/60 * (length(ticks)-1)) + 1 + peaks = clamp.(20log10.(peaks), -60.0, 0.0) + idxs = trunc.(Int, (peaks+60)/60 * (length(ticks)-1)) + 1 blocks[blk, :] = ticks[idxs] end for ch in 1:nchannels(buf) println(io) - print(io, convert(UTF8String, blocks[:, ch])) + print(io, convert(String, blocks[:, ch])) end end @@ -221,18 +249,18 @@ end # the index types that Base knows how to handle. Separate out those that index # multiple results -typealias BuiltinMultiIdx Union{Colon, - Vector{Int}, - Vector{Bool}, - Range{Int}} -typealias BuiltinIdx Union{Int, BuiltinMultiIdx} +const BuiltinMultiIdx = Union{Colon, + Vector{Int}, + Vector{Bool}, + Range{Int}} +const BuiltinIdx = Union{Int, BuiltinMultiIdx} # the index types that will need conversion to built-in index types. Each of # these needs a `toindex` method defined for it -typealias ConvertIdx{T1 <: SIQuantity, T2 <: Int} Union{T1, - # Vector{T1}, # not supporting vectors of SIQuantities (yet?) - # Range{T1}, # not supporting ranges (yet?) - Interval{T2}, - Interval{T1}} +const ConvertIdx{T1 <: SIQuantity, T2 <: Int} = Union{T1, + # Vector{T1}, # not supporting vectors of SIQuantities (yet?) + # Range{T1}, # not supporting ranges (yet?) + Interval{T2}, + Interval{T1}} """ toindex(buf::SampleBuf, I) @@ -242,17 +270,17 @@ indexing """ function toindex end -toindex{T <: Number, N}(buf::SampleBuf{T, N}, t::SecondsQuantity) = round(Int, float(t)*samplerate(buf)) + 1 -toindex{T <: Number, N}(buf::SpectrumBuf{T, N}, t::HertzQuantity) = round(Int, float(t)*samplerate(buf)) + 1 +toindex(buf::SampleBuf{T, N}, t::SecondsQuantity) where {T <: Number, N} = round(Int, float(t)*samplerate(buf)) + 1 +toindex(buf::SpectrumBuf{T, N}, t::HertzQuantity) where {T <: Number, N} = round(Int, float(t)*samplerate(buf)) + 1 # indexing by vectors of SIQuantities not yet supported # toindex{T <: SIUnits.SIQuantity}(buf::SampleBuf, I::Vector{T}) = Int[toindex(buf, i) for i in I] toindex(buf::AbstractSampleBuf, I::Interval{Int}) = I.lo:I.hi -toindex{T <: SIQuantity}(buf::AbstractSampleBuf, I::Interval{T}) = toindex(buf, I.lo):toindex(buf, I.hi) +toindex(buf::AbstractSampleBuf, I::Interval{T}) where {T <: SIQuantity} = toindex(buf, I.lo):toindex(buf, I.hi) # AbstractArray interface methods Base.size(buf::AbstractSampleBuf) = size(buf.data) -Base.linearindexing{T <: AbstractSampleBuf}(::Type{T}) = Base.LinearFast() +Base.IndexStyle(::Type{T}) where {T <: AbstractSampleBuf} = Base.IndexLinear() # this is the fundamental indexing operation needed for the AbstractArray interface Base.getindex(buf::AbstractSampleBuf, i::Int) = buf.data[i]; @@ -282,14 +310,14 @@ Base.ifft(buf::SpectrumBuf) = SampleBuf(ifft(buf.data), nframes(buf)/samplerate( # does a per-channel convolution on SampleBufs for buftype in (:SampleBuf, :SpectrumBuf) - @eval function Base.conv{T}(b1::$buftype{T, 1}, b2::$buftype{T, 1}) + @eval function Base.conv(b1::$buftype{T, 1}, b2::$buftype{T, 1}) where {T} if !isapprox(samplerate(b1), samplerate(b2)) error("Resampling convolution not yet supported") end $buftype(conv(b1.data, b2.data), samplerate(b1)) end - @eval function Base.conv{T, N1, N2}(b1::$buftype{T, N1}, b2::$buftype{T, N2}) + @eval function Base.conv(b1::$buftype{T, N1}, b2::$buftype{T, N2}) where {T, N1, N2} if !isapprox(samplerate(b1), samplerate(b2)) error("Resampling convolution not yet supported") end @@ -304,13 +332,13 @@ for buftype in (:SampleBuf, :SpectrumBuf) out end - @eval function Base.conv{T}(b1::$buftype{T, 1}, b2::StridedVector{T}) + @eval function Base.conv(b1::$buftype{T, 1}, b2::StridedVector{T}) where {T} $buftype(conv(b1.data, b2), samplerate(b1)) end - @eval Base.conv{T}(b1::StridedVector{T}, b2::$buftype{T, 1}) = conv(b2, b1) + @eval Base.conv(b1::StridedVector{T}, b2::$buftype{T, 1}) where {T} = conv(b2, b1) - @eval function Base.conv{T}(b1::$buftype{T, 2}, b2::StridedMatrix{T}) + @eval function Base.conv(b1::$buftype{T, 2}, b2::StridedMatrix{T}) where {T} if nchannels(b1) != nchannels(b2) error("Broadcasting convolution not yet supported") end @@ -322,5 +350,5 @@ for buftype in (:SampleBuf, :SpectrumBuf) out end - @eval Base.conv{T}(b1::StridedMatrix{T}, b2::$buftype{T, 2}) = conv(b2, b1) + @eval Base.conv(b1::StridedMatrix{T}, b2::$buftype{T, 2}) where {T} = conv(b2, b1) end diff --git a/src/SampleStream.jl b/src/SampleStream.jl index 1da7b7d..d995b80 100644 --- a/src/SampleStream.jl +++ b/src/SampleStream.jl @@ -6,7 +6,7 @@ Subtypes should implement the `samplerate`, `nchannels`, `eltype`, and `unsafe_read!` methods. `unsafe_read!` can assume that the samplerate, channel count, and element type are all matching. """ -abstract SampleSource +abstract type SampleSource end """ unsafe_read!(source::SampleSource, buf::Array, frameoffset, framecount) @@ -30,7 +30,7 @@ Subtypes should implement the `samplerate`, `nchannels`, `eltype`, and `unsafe_write` methods. `unsafe_write` can assume that the samplerate, channel count, and element type are all matching. """ -abstract SampleSink +abstract type SampleSink end """ unsafe_write(sink::SampleSink, buf::Array, frameoffset, framecount) @@ -51,7 +51,7 @@ function unsafe_write end blocksize(src::SampleSource) = 0 blocksize(src::SampleSink) = 0 -toindex(stream::SampleSource, t::SecondsQuantity) = round(Int, float(t)*compat_samplerate(stream)) + 1 +toindex(stream::SampleSource, t::SecondsQuantity) = round(Int, float(t)*samplerate(stream)) + 1 # subtypes should only have to implement the `unsafe_read!` and `unsafe_write` methods, so # here we implement all the converting wrapper methods @@ -60,7 +60,7 @@ toindex(stream::SampleSource, t::SecondsQuantity) = round(Int, float(t)*compat_s Base.read(stream::SampleSource, t::SecondsQuantity) = read(stream, toindex(stream, t)-1) function Base.read(src::SampleSource, nframes::Integer) - buf = SampleBuf(eltype(src), compat_samplerate(src), nframes, nchannels(src)) + buf = SampleBuf(eltype(src), samplerate(src), nframes, nchannels(src)) n = read!(src, buf) buf[1:n, :] @@ -71,7 +71,7 @@ const DEFAULT_BLOCKSIZE=4096 # handle sink-to-source writing with a duration in seconds function Base.write(sink::SampleSink, source::SampleSource, duration::SecondsQuantity; blocksize=-1) - sr = compat_samplerate(sink) + sr = samplerate(sink) frames = trunc(Int, float(duration) * sr) n = write(sink, source, frames; blocksize=blocksize) @@ -86,18 +86,18 @@ end # TODO: we should be able to add reformatting support to the ResampleSink and # xMixSink types, to avoid an extra buffer copy function wrap_sink(sink::SampleSink, source::SampleSource, blocksize) - if eltype(sink) != eltype(source) && !isapprox(compat_samplerate(sink), compat_samplerate(source)) + if eltype(sink) != eltype(source) && !isapprox(samplerate(sink), samplerate(source)) # we're going to resample AND reformat. We prefer to resample # in the floating-point space because it seems to be about 40% faster if eltype(sink) <: AbstractFloat - wrap_sink(ResampleSink(sink, compat_samplerate(source), blocksize), source, blocksize) + wrap_sink(ResampleSink(sink, samplerate(source), blocksize), source, blocksize) else wrap_sink(ReformatSink(sink, eltype(source), blocksize), source, blocksize) end elseif eltype(sink) != eltype(source) wrap_sink(ReformatSink(sink, eltype(source), blocksize), source, blocksize) - elseif !isapprox(compat_samplerate(sink), compat_samplerate(source)) - wrap_sink(ResampleSink(sink, compat_samplerate(source), blocksize), source, blocksize) + elseif !isapprox(samplerate(sink), samplerate(source)) + wrap_sink(ResampleSink(sink, samplerate(source), blocksize), source, blocksize) elseif nchannels(sink) != nchannels(source) if nchannels(sink) == 1 DownMixSink(sink, nchannels(source), blocksize) @@ -129,7 +129,7 @@ end # sample rate and channel count function unsafe_write(sink::SampleSink, source::SampleSource, frames=-1, blocksize=-1) written::Int = 0 - buf = Array(eltype(source), blocksize, nchannels(source)) + buf = Array{eltype(source)}(blocksize, nchannels(source)) while frames < 0 || written < frames n = frames < 0 ? blocksize : min(blocksize, frames - written) nr = unsafe_read!(source, buf, 0, n) @@ -147,7 +147,7 @@ end function Base.write(sink::SampleSink, buf::SampleBuf, nframes=nframes(buf)) if nchannels(sink) == nchannels(buf) && eltype(sink) == eltype(buf) && - isapprox(compat_samplerate(sink), compat_samplerate(buf)) + isapprox(samplerate(sink), samplerate(buf)) # everything matches, call the sink's low-level write method unsafe_write(sink, buf.data, 0, nframes) else @@ -158,7 +158,13 @@ function Base.write(sink::SampleSink, buf::SampleBuf, nframes=nframes(buf)) end function Base.write(sink::SampleSink, buf::SampleBuf, duration::SecondsQuantity) - write(sink, buf, round(Int, float(duration)*samplerate(buf))) + n = round(Int, float(duration)*samplerate(buf)) + written = write(sink, buf, n) + if written == n + return duration + else + return written / samplerate(buf) * s + end end # treat bare arrays as a buffer with the same samplerate as the sink @@ -167,25 +173,67 @@ function Base.write(sink::SampleSink, arr::Array, dur=nframes(arr)) write(sink, buf, dur) end -# TODO: it seems like read! should support a duration -function Base.read!(source::SampleSource, buf::SampleBuf) +function Base.read!(source::SampleSource, buf::SampleBuf, n::Integer) if nchannels(source) == nchannels(buf) && eltype(source) == eltype(buf) && - isapprox(compat_samplerate(source), compat_samplerate(buf)) - unsafe_read!(source, buf.data, 0, nframes(buf)) + isapprox(samplerate(source), samplerate(buf)) + unsafe_read!(source, buf.data, 0, n) else # some conversion is necessary. Wrap in a sink so we can use the # stream conversion machinery - write(SampleBufSink(buf), source) + write(SampleBufSink(buf), source, n) + end +end + +# when reading into a SampleBuf, calculate frames based on the given buffer, +# which might differ from the source samplerate if there's a samplerate +# conversion involved. +function Base.read!(source::SampleSource, buf::SampleBuf, t::SecondsQuantity) + n = round(Int, float(t)*samplerate(buf)) + written = read!(source, buf, n) + if written == n + return t + else + return written / samplerate(buf) * s + end +end + +function Base.read!(source::SampleSource, buf::Array, t::SecondsQuantity) + n = round(Int, float(t)*samplerate(source)) + written = read!(source, buf, n) + if written == n + return t + else + return written / samplerate(buf) * s end end # treat bare arrays as a buffer with the same samplerate as the source -function Base.read!(source::SampleSource, arr::Array) +function Base.read!(source::SampleSource, arr::Array, n::Integer) buf = SampleBuf(arr, samplerate(source)) - read!(source, buf) + read!(source, buf, n) end +# if no frame count is given default to the number of frames in the destination +Base.read!(source::SampleSource, arr::AbstractArray) = read!(source, arr, nframes(arr)) + +function Base.read(source::SampleSource) + buf = SampleBuf(eltype(source), + samplerate(source), + DEFAULT_BLOCKSIZE, + nchannels(source)) + # during accumulation we keep the channels separate so we can grow the + # arrays without needing to copy data around as much + cumbufs = [Vector{eltype(source)}() for _ in 1:nchannels(source)] + while true + n = read!(source, buf) + for ch in 1:length(cumbufs) + append!(cumbufs[ch], @view buf.data[1:n, ch]) + end + n == nframes(buf) || break + end + SampleBuf(hcat(cumbufs...), samplerate(source)) +end """UpMixSink provides a single-channel sink that wraps a multi-channel sink. Writing to this sink copies the single channel to all the channels in the @@ -198,12 +246,12 @@ end function UpMixSink(wrapped::SampleSink, blocksize=DEFAULT_BLOCKSIZE) N = nchannels(wrapped) T = eltype(wrapped) - buf = Array(T, blocksize, N) + buf = Array{T}(blocksize, N) UpMixSink(wrapped, buf) end -samplerate(sink::UpMixSink) = compat_samplerate(sink.wrapped) +samplerate(sink::UpMixSink) = samplerate(sink.wrapped) nchannels(sink::UpMixSink) = 1 Base.eltype(sink::UpMixSink) = eltype(sink.wrapped) blocksize(sink::UpMixSink) = size(sink.buf, 1) @@ -238,12 +286,12 @@ end function DownMixSink(wrapped::SampleSink, channels, blocksize=DEFAULT_BLOCKSIZE) T = eltype(wrapped) - buf = Array(T, blocksize, 1) + buf = Array{T}(blocksize, 1) DownMixSink(wrapped, buf, channels) end -samplerate(sink::DownMixSink) = compat_samplerate(sink.wrapped) +samplerate(sink::DownMixSink) = samplerate(sink.wrapped) nchannels(sink::DownMixSink) = sink.channels Base.eltype(sink::DownMixSink) = eltype(sink.wrapped) blocksize(sink::DownMixSink) = size(sink.buf, 1) @@ -282,12 +330,12 @@ end function ReformatSink(wrapped::SampleSink, T, blocksize=DEFAULT_BLOCKSIZE) WT = eltype(wrapped) N = nchannels(wrapped) - buf = Array(WT, blocksize, N) + buf = Array{WT}(blocksize, N) ReformatSink(wrapped, buf, T) end -samplerate(sink::ReformatSink) = compat_samplerate(sink.wrapped) +samplerate(sink::ReformatSink) = samplerate(sink.wrapped) nchannels(sink::ReformatSink) = nchannels(sink.wrapped) Base.eltype(sink::ReformatSink) = sink.typ blocksize(sink::ReformatSink) = nframes(sink.buf) @@ -320,10 +368,10 @@ type ResampleSink{W <: SampleSink, B <: Array, F <: FIRFilter} <: SampleSink end function ResampleSink(wrapped::SampleSink, sr, blocksize=DEFAULT_BLOCKSIZE) - wsr = compat_samplerate(wrapped) + wsr = samplerate(wrapped) T = eltype(wrapped) N = nchannels(wrapped) - buf = Array(T, blocksize, N) + buf = Array{T}(blocksize, N) ratio = rationalize(wsr/sr) coefs = resample_filter(ratio) @@ -409,27 +457,3 @@ function unsafe_write(sink::SampleBufSink, buf::Array, frameoffset, framecount) n end - - -# we only need to define this until DSP.jl#133 -# (https://github.com/JuliaDSP/DSP.jl/pull/133) is merged -function DSP.shiftin!{T}(a::AbstractVector{T}, b::AbstractVector{T}) - aLen = length(a) - bLen = length(b) - - if bLen >= aLen - copy!(a, 1, b, bLen - aLen + 1, aLen) - else - - for i in 1:aLen-bLen - @inbounds a[i] = a[i+bLen] - end - bIdx = 1 - for i in aLen-bLen+1:aLen - @inbounds a[i] = b[bIdx] - bIdx += 1 - end - end - - return a -end diff --git a/src/SampledSignals.jl b/src/SampledSignals.jl index 3f22e92..a971ad7 100644 --- a/src/SampledSignals.jl +++ b/src/SampledSignals.jl @@ -2,17 +2,6 @@ __precompile__() module SampledSignals -using SIUnits -using SIUnits.ShortUnits: ns, ms, µs, s, Hz, kHz, MHz, GHz, THz -using SIUnits: SIQuantity -using FixedPointNumbers -using DSP -using Compat -# if/when we drop 0.4 support we can remove UTF8String and just call it "String". -# we'll also be able to use view without importing it from Compat -import Compat: view, UTF8String -@compat import Base.show - export AbstractSampleBuf, SampleBuf, SpectrumBuf export SampleSource, SampleSink export SampleRate @@ -27,13 +16,21 @@ export mix!, mix, mono!, mono export ns, ms, µs, s, Hz, kHz, MHz, GHz, THz export PCM8Sample, PCM16Sample, PCM24Sample, PCM32Sample -typealias HertzQuantity{T} SIUnits.SIQuantity{T,0,0,-1,0,0,0,0,0,0} -typealias SecondsQuantity{T} SIUnits.SIQuantity{T,0,0,1,0,0,0,0,0,0} +using SIUnits +using SIUnits.ShortUnits: ns, ms, µs, s, Hz, kHz, MHz, GHz, THz +using SIUnits: SIQuantity +using FixedPointNumbers +using DSP + +import Base: show + +const HertzQuantity{T} = SIUnits.SIQuantity{T,0,0,-1,0,0,0,0,0,0} +const SecondsQuantity{T} = SIUnits.SIQuantity{T,0,0,1,0,0,0,0,0,0} -typealias PCM8Sample Fixed{Int8, 7} -typealias PCM16Sample Fixed{Int16, 15} -typealias PCM24Sample Fixed{Int32, 23} -typealias PCM32Sample Fixed{Int32, 31} +const PCM8Sample = Fixed{Int8, 7} +const PCM16Sample = Fixed{Int16, 15} +const PCM24Sample = Fixed{Int32, 23} +const PCM32Sample = Fixed{Int32, 31} include("Interval.jl") include("SampleBuf.jl") diff --git a/src/SignalGen/SinSource.jl b/src/SignalGen/SinSource.jl index 36d3c1e..4113778 100644 --- a/src/SignalGen/SinSource.jl +++ b/src/SignalGen/SinSource.jl @@ -26,7 +26,7 @@ function unsafe_read!(source::SinSource, buf::Array, frameoffset, framecount) f = source.freqs[ch] ph = source.phases[ch] for i in 1:framecount - buf[i+frameoffset, ch] = sin(ph) + buf[i+frameoffset, ch] = sin.(ph) ph += f end source.phases[ch] = ph diff --git a/src/WAVDisplay.jl b/src/WAVDisplay.jl index 7fb87a5..9028df8 100644 --- a/src/WAVDisplay.jl +++ b/src/WAVDisplay.jl @@ -19,10 +19,10 @@ function embed_javascript() end # need to specify that T <: Number to avoid a method ambiguity with AbstractArray{Method} on 0.4 -@compat function show{T <: Number, N}(io::IO, ::MIME"text/html", buf::SampleBuf{T, N}) +function show(io::IO, ::MIME"text/html", buf::SampleBuf{T, N}) where {T <: Number, N} tempio = IOBuffer() wavwrite(tempio, buf) - data = base64encode(takebuf_array(tempio)) + data = base64encode(take!(tempio)) # we want the divID to start with a letter divid = string("a", randstring(10)) # include an error message that will get cleared if javascript loads correctly diff --git a/src/deprecated.jl b/src/deprecated.jl index 7f4e7f5..e69de29 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -1,97 +0,0 @@ -function unsafe_read! end -function unsafe_write end - -# apparently stacktrace doesn't exist on 0.4, so too bad, 0.4 users don't get -# stack traces on their deprecation warnings -if !isdefined(:stacktrace) - stacktrace() = [] -end - -# fallback function for libraries using the old unsafe_read! function -const read_depwarned = Set{Type}() -function unsafe_read!{T <: SampleSource}(src::T, buf::Array, frameoffset, framecount) - if !(T in read_depwarned) - push!(read_depwarned, T) - warn("""`unsafe_read!(src::$T, buf::Array, frameoffset, framecount)` not defined, - falling back to deprecated `unsafe_read!(src::$T, buf::SampleBuf)`. Please - check the SampledSignals README for the new API""") - map(println, stacktrace()) - end - - tmp = SampleBuf(Array(eltype(src), framecount, nchannels(src)), samplerate(src)) - n = unsafe_read!(src, tmp) - buf[(1:framecount)+frameoffset, :] = view(tmp.data, :, :) - - n -end - -# fallback function for libraries using the old unsafe_write function -const write_depwarned = Set{Type}() -function unsafe_write{T <: SampleSink}(sink::T, buf::Array, frameoffset, framecount) - if !(T in write_depwarned) - push!(write_depwarned, T) - warn("""`unsafe_write(src::$T, buf::Array, frameoffset, framecount)` not defined, - falling back to deprecated `unsafe_write(src::$T, buf::SampleBuf)`. Please - check the SampledSignals README for the new API""") - map(println, stacktrace()) - end - - tmp = SampleBuf(buf[(1:framecount)+frameoffset, :], samplerate(sink)) - unsafe_write(sink, tmp) -end - -const unit_depwarned = Ref(false) -function SampleBuf(arr::Array, sr::HertzQuantity) - if !unit_depwarned[] - warn("Samplerates with units are deprecated. Switch to plain floats") - map(println, stacktrace()) - unit_depwarned[] = true - end - SampleBuf(arr, float(sr)) -end - -function SampleBuf(T::Type, sr::HertzQuantity, len::SecondsQuantity) - if !unit_depwarned[] - warn("Samplerates with units are deprecated. Switch to plain floats") - map(println, stacktrace()) - unit_depwarned[] = true - end - SampleBuf(T, float(sr), len) -end - -function SampleBuf(T::Type, sr::HertzQuantity, args...) - if !unit_depwarned[] - warn("Samplerates with units are deprecated. Switch to plain floats") - map(println, stacktrace()) - unit_depwarned[] = true - end - SampleBuf(T, float(sr), args...) -end - -# wrapper for samplerate that converts to floating point from unitful values -# and prints a depwarn. We use this internally to keep backwards compatibility, -# but it can be removed and replaced with normal `samplerate` calls when we -# remove compatibility -compat_samplerate(x) = warn_if_unitful(samplerate(x)) - -warn_if_unitful(x) = x -function warn_if_unitful(x::SIQuantity) - if !unit_depwarned[] - warn("Samplerates with units are deprecated. Switch to plain floats") - map(println, stacktrace()) - unit_depwarned[] = true - end - float(x) -end - -function SinSource{T <: SIQuantity}(eltype, samplerate::SIQuantity, freqs::Array{T}) - SinSource(eltype, warn_if_unitful(samplerate), map(float, freqs)) -end - -function SinSource(eltype, samplerate::SIQuantity, freqs::Array) - SinSource(eltype, warn_if_unitful(samplerate), freqs) -end - -function SinSource{T <: SIQuantity}(eltype, samplerate, freqs::Array{T}) - SinSource(eltype, samplerate, map(warn_if_unitful, freqs)) -end diff --git a/test/DummySampleStream.jl b/test/DummySampleStream.jl index 0a6b60b..e882dcd 100644 --- a/test/DummySampleStream.jl +++ b/test/DummySampleStream.jl @@ -6,28 +6,6 @@ DummyMonoSink() = DummySampleSink(DEFAULT_T, DEFAULT_SR, 1) DummyStereoSink() = DummySampleSink(DEFAULT_T, DEFAULT_SR, 2) - @testset "write writes to buf" begin - sink = DummyStereoSink() - buf = SampleBuf(convert(Array{DEFAULT_T}, randn(32, 2)), DEFAULT_SR) - write(sink, buf) - @test sink.buf == buf.data - end - - @testset "read reads from buf" begin - data = rand(DEFAULT_T, (64, 2)) - source = DummySource(data) - buf = read(source, 64) - @test buf.data == data - end - - @testset "read can read in seconds" begin - # fill with 1s of data - data = rand(DEFAULT_T, (DEFAULT_SR, 2)) - source = DummySource(data) - buf = read(source, 0.0005s) - @test buf.data == data[1:round(Int, 0.0005*DEFAULT_SR), :] - end - @testset "supports audio interface" begin data = rand(DEFAULT_T, (64, 2)) source = DummySource(data) @@ -42,7 +20,7 @@ @testset "can be created with non-unit sampling rate" begin sink = DummySampleSink(Float32, 48000, 2) @test samplerate(sink) == 48000 - source = DummySampleSource(48000, Array(Float32, 16, 2)) + source = DummySampleSource(48000, Array{Float32}(16, 2)) @test samplerate(source) == 48000 end end diff --git a/test/Interval.jl b/test/Interval.jl index 8efeedd..7928017 100644 --- a/test/Interval.jl +++ b/test/Interval.jl @@ -13,7 +13,7 @@ i = 5..7 io = IOBuffer() print(io, i) - @test takebuf_string(io) == "5..7" + @test String(take!(io)) == "5..7" end @testset "Promotion Rules" begin diff --git a/test/SampleBuf.jl b/test/SampleBuf.jl index 5b0622e..c57ce1d 100644 --- a/test/SampleBuf.jl +++ b/test/SampleBuf.jl @@ -20,7 +20,7 @@ end @testset "Can get type params from contained array" begin - timebuf = SampleBuf(Array(TEST_T, 32, 2), TEST_SR) + timebuf = SampleBuf(Array{TEST_T}(32, 2), TEST_SR) @test nframes(timebuf) == 32 @test nchannels(timebuf) == 2 end @@ -158,7 +158,7 @@ @testset "Can be created without units" begin buf = SampleBuf(Float32, 48000, 100, 2) @test samplerate(buf) == 48000 - buf = SampleBuf(Array(Float32, 100, 2), 48000) + buf = SampleBuf(Array{Float32}(100, 2), 48000) @test samplerate(buf) == 48000 end @@ -211,12 +211,18 @@ 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) + @test typeof(sum) == typeof(buf1) 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) @test typeof(diff) == typeof(buf1) + diff = buf1 .- buf2 + @test diff == SampleBuf(arr1 .- arr2, TEST_SR) + @test typeof(diff) == typeof(buf1) quot = buf1 ./ buf2 @test quot == SampleBuf(arr1 ./ arr2, TEST_SR) @test typeof(quot) == typeof(buf1) @@ -229,15 +235,27 @@ sum = buf1 + 2.0f0 @test sum == SampleBuf(arr1 + 2.0f0, TEST_SR) @test typeof(sum) == typeof(buf1) + sum = buf1 .+ 2.0f0 + @test sum == SampleBuf(arr1 .+ 2.0f0, TEST_SR) + @test typeof(sum) == typeof(buf1) prod = buf1 * 2.0f0 @test prod == SampleBuf(arr1 * 2.0f0, TEST_SR) @test typeof(prod) == typeof(buf1) + prod = buf1 .* 2.0f0 + @test prod == SampleBuf(arr1 .* 2.0f0, TEST_SR) + @test typeof(prod) == typeof(buf1) diff = buf1 - 2.0f0 @test diff == SampleBuf(arr1 - 2.0f0, TEST_SR) @test typeof(diff) == typeof(buf1) + diff = buf1 .- 2.0f0 + @test diff == SampleBuf(arr1 .- 2.0f0, TEST_SR) + @test typeof(diff) == typeof(buf1) quot = buf1 / 2.0f0 @test quot == SampleBuf(arr1 / 2.0f0, TEST_SR) @test typeof(quot) == typeof(buf1) + quot = buf1 ./ 2.0f0 + @test quot == SampleBuf(arr1 ./ 2.0f0, TEST_SR) + @test typeof(quot) == typeof(buf1) end @testset "Arithmetic with arrays gives SampleBufs" begin @@ -248,12 +266,18 @@ sum = buf1 + arr2 @test sum == SampleBuf(arr1 + arr2, TEST_SR) @test typeof(sum) == typeof(buf1) + sum = buf1 .+ arr2 + @test sum == SampleBuf(arr1 .+ arr2, TEST_SR) + @test typeof(sum) == typeof(buf1) prod = buf1 .* arr2 @test prod == SampleBuf(arr1 .* arr2, TEST_SR) @test typeof(prod) == typeof(buf1) diff = buf1 - arr2 @test diff == SampleBuf(arr1 - arr2, TEST_SR) @test typeof(diff) == typeof(buf1) + diff = buf1 .- arr2 + @test diff == SampleBuf(arr1 .- arr2, TEST_SR) + @test typeof(diff) == typeof(buf1) quot = buf1 ./ arr2 @test quot == SampleBuf(arr1 ./ arr2, TEST_SR) @test typeof(quot) == typeof(buf1) @@ -267,12 +291,18 @@ sum = buf1 + arr2 @test sum == SampleBuf(arr1 + arr2, TEST_SR) @test typeof(sum) == typeof(buf1) + sum = buf1 .+ arr2 + @test sum == SampleBuf(arr1 + arr2, TEST_SR) + @test typeof(sum) == typeof(buf1) prod = buf1 .* arr2 @test prod == SampleBuf(arr1 .* arr2, TEST_SR) @test typeof(prod) == typeof(buf1) diff = buf1 - arr2 @test diff == SampleBuf(arr1 - arr2, TEST_SR) @test typeof(diff) == typeof(buf1) + diff = buf1 .- arr2 + @test diff == SampleBuf(arr1 - arr2, TEST_SR) + @test typeof(diff) == typeof(buf1) quot = buf1 ./ arr2 @test quot == SampleBuf(arr1 ./ arr2, TEST_SR) @test typeof(quot) == typeof(buf1) @@ -416,24 +446,24 @@ @testset "multichannel buf prints prettily" begin t = collect(linspace(0, 2pi, 300)) - buf = SampleBuf([cos(t) sin(t)]*0.2, 48000) + buf = SampleBuf([cos.(t) sin.(t)]*0.2, 48000) expected = """300-frame, 2-channel SampleBuf{Float64, 2} 0.00625s sampled at 48000.0Hz ▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▄▄▃▃▄▄▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▄▄▃▃▄▄▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆ ▃▄▄▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▄▄▂▄▄▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▄▄▃""" iobuf = IOBuffer() display(TextDisplay(iobuf), buf) - @test takebuf_string(iobuf) == expected + @test String(take!(iobuf)) == expected end @testset "1D buf prints prettily" begin t = collect(linspace(0, 2pi, 300)) - buf = SampleBuf(cos(t)*0.2, 48000) + buf = SampleBuf(cos.(t)*0.2, 48000) expected = """300-frame, 1-channel SampleBuf{Float64, 1} 0.00625s sampled at 48000.0Hz ▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▄▄▃▃▄▄▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▄▄▃▃▄▄▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆""" iobuf = IOBuffer() display(TextDisplay(iobuf), buf) - @test takebuf_string(iobuf) == expected + @test String(take!(iobuf)) == expected end @testset "zero-length buf prints prettily" begin buf = SampleBuf(Float64, 48000, 0, 2) @@ -441,6 +471,6 @@ 0.0s sampled at 48000.0Hz""" iobuf = IOBuffer() display(TextDisplay(iobuf), buf) - @test takebuf_string(iobuf) == expected + @test String(take!(iobuf)) == expected end end diff --git a/test/SampleStream.jl b/test/SampleStream.jl index 70d8935..ff3b4bb 100644 --- a/test/SampleStream.jl +++ b/test/SampleStream.jl @@ -2,6 +2,10 @@ # use Dummy sinks and sources, but all these features should be implemented # on the abstract Source/Sinks @testset "SampleStream Tests" begin + DummySource(buf) = DummySampleSource(48000, buf) + DummyMonoSink() = DummySampleSink(Float64, 48000, 1) + DummyStereoSink() = DummySampleSink(Float64, 48000, 2) + @testset "writing sink to source" begin data = rand(Float32, 64, 2) source = DummySampleSource(48000, data) @@ -150,6 +154,139 @@ @test sink.buf == arr end + @testset "Partial SampleBufs can be written to sinks specifying frames" begin + sink = DummyStereoSink() + buf = SampleBuf(rand(10, 2), samplerate(sink)) + write(sink, buf, 5) + @test sink.buf == buf.data[1:5, :] + end + + @testset "Partial Arrays can be written to sinks specifying frames" begin + sink = DummyStereoSink() + buf = rand(10, 2) + write(sink, buf, 5) + @test sink.buf == buf[1:5, :] + end + + @testset "Partial SampleBufs can be written to sinks specifying duration" begin + sink = DummyStereoSink() + buf = SampleBuf(rand(10, 2), samplerate(sink)) + t = 5/samplerate(sink) * s + write(sink, buf, t) + @test sink.buf == buf.data[1:5, :] + end + + @testset "Partial Arrays can be written to sinks specifying duration" begin + sink = DummyStereoSink() + buf = rand(10, 2) + t = 5/samplerate(sink) * s + write(sink, buf, t) + @test sink.buf == buf[1:5, :] + end + + @testset "can read without specifying frames" begin + data = rand(8, 2) + source = DummySource(data) + buf = read(source) + @test buf isa SampleBuf + @test buf.data == data + end + + @testset "can read long source without specifying frames" begin + data = rand(10000, 2) + source = DummySource(data) + buf = read(source) + @test buf isa SampleBuf + @test buf.data == data + end + + @testset "read can read in frames" begin + data = rand(8, 2) + source = DummySource(data) + buf = read(source, 5) + @test buf.data == data[1:5, :] + end + + @testset "read can read in seconds" begin + data = rand(20, 2) + source = DummySource(data) + t = 5/samplerate(source) * s + buf = read(source, t) + @test buf.data == data[1:5, :] + end + + @testset "can read! into array specifying frames" begin + data = rand(8, 2) + buf = zeros(8, 2) + source = DummySource(data) + @test read!(source, buf, 5) == 5 + @test buf[1:5, :] == data[1:5, :] + @test buf[6:8, :] == zeros(3, 2) + end + + @testset "can read! into SampleBuf specifying frames" begin + data = rand(8, 2) + source = DummySource(data) + buf = SampleBuf(zeros(8, 2), samplerate(source)) + @test read!(source, buf, 5) == 5 + @test buf.data[1:5, :] == data[1:5, :] + @test buf.data[6:8, :] == zeros(3, 2) + end + + @testset "can read! into array specifying time" begin + data = rand(8, 2) + source = DummySource(data) + buf = zeros(8, 2) + t = 5/samplerate(source) * s + @test read!(source, buf, t) == t + @test buf[1:5, :] == data[1:5, :] + @test buf[6:8, :] == zeros(3, 2) + end + + @testset "can read! into SampleBuf specifying time" begin + data = rand(8, 2) + source = DummySource(data) + buf = SampleBuf(zeros(8, 2), samplerate(source)) + t = 5/samplerate(source) * s + @test read!(source, buf, t) == t + @test buf.data[1:5, :] == data[1:5, :] + @test buf.data[6:8, :] == zeros(3, 2) + end + + @testset "can read! into array without specifying frames" begin + data = rand(8, 2) + buf = rand(5, 2) + source = DummySource(data) + @test read!(source, buf) == 5 + @test buf == data[1:5, :] + end + + @testset "can read! into SampleBuf without specifying frames" begin + data = rand(8, 2) + source = DummySource(data) + buf = SampleBuf(rand(5, 2), samplerate(source)) + @test read!(source, buf) == 5 + @test buf.data == data[1:5, :] + end + + @testset "can read! into too-long array without specifying frames" begin + data = rand(8, 2) + buf = zeros(10, 2) + source = DummySource(data) + @test read!(source, buf) == 8 + @test buf[1:8, :] == data + @test buf[9:10, :] == zeros(2, 2) + end + + @testset "can read! into too-long SampleBuf without specifying frames" begin + data = rand(8, 2) + source = DummySource(data) + buf = SampleBuf(zeros(10, 2), samplerate(source)) + @test read!(source, buf) == 8 + @test buf[1:8, :] == data + @test buf[9:10, :] == zeros(2, 2) + end + @testset "SampleBufs can be written to sinks with downmixing" begin buf = SampleBuf(rand(16, 2), 48000) sink = DummySampleSink(Float64, 48000, 1) @@ -215,7 +352,7 @@ end @testset "Arrays can be read from sources" begin - arr = Array(Float64, 16, 2) + arr = Array{Float64}(16, 2) data = rand(Float64, 16, 2) source = DummySampleSource(48000, data) read!(source, arr) diff --git a/test/SinSource.jl b/test/SinSource.jl index 3912b1c..27814f8 100644 --- a/test/SinSource.jl +++ b/test/SinSource.jl @@ -2,14 +2,14 @@ @testset "SinSource generates sin" begin source = SinSource(Float32, 44100, [220, 440]) t = (0:31) / 44100 - expected = Float32[sin(2pi*220*t) sin(2pi*440*t)] + expected = Float32[sin.(2pi*220*t) sin.(2pi*440*t)] @test read(source, 32) ≈ expected end @testset "SinSource generates works with one frequency" begin source = SinSource(Float32, 44100, 220) t = (0:31) / 44100 - expected = Float32[sin(2pi*220*t);] + expected = Float32[sin.(2pi*220*t);] @test read(source, 32) ≈ expected end @@ -17,7 +17,7 @@ source = SinSource(Float32, 44100, 220) sink = DummySampleSink(Float32, 44100, 1) t = (0:31) / 44100 - expected = Float32[sin(2pi*220*t);] + expected = Float32[sin.(2pi*220*t);] write(sink, source, 32) @test sink.buf ≈ expected end diff --git a/test/WAVDisplay.jl b/test/WAVDisplay.jl index b2f0e3a..d26bbc3 100644 --- a/test/WAVDisplay.jl +++ b/test/WAVDisplay.jl @@ -1,7 +1,7 @@ function parsehtmldisplay(buf) outputbuf = IOBuffer() - @compat show(outputbuf, MIME"text/html"(), buf) - fragment = takebuf_string(outputbuf) + display(TextDisplay(outputbuf), "text/html", buf) + fragment = String(take!(outputbuf)) fullhtml = """ = v"0.5.0-dev+7720" - using Base.Test -else - using BaseTestNext -end - -using TestSetExtensions using FixedPointNumbers using Gumbo using WAV diff --git a/test/support/util.jl b/test/support/util.jl index 8b9b09b..03327c3 100644 --- a/test/support/util.jl +++ b/test/support/util.jl @@ -29,7 +29,7 @@ type DummySampleSink{T} <: SampleSink end DummySampleSink(eltype, samplerate, channels) = - DummySampleSink{eltype}(samplerate, Array(eltype, 0, channels)) + DummySampleSink{eltype}(samplerate, Array{eltype}(0, channels)) samplerate(sink::DummySampleSink) = sink.samplerate nchannels(sink::DummySampleSink) = size(sink.buf, 2)