Skip to content

Commit

Permalink
Merge pull request #21 from JuliaAudio/06_depwarn
Browse files Browse the repository at this point in the history
updates for 0.6, some extra read/write capabilities
  • Loading branch information
ssfrr authored May 17, 2017
2 parents 3828970 + af604a8 commit f4c300d
Show file tree
Hide file tree
Showing 20 changed files with 378 additions and 296 deletions.
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ os:
- linux
- osx
julia:
# - release
- 0.4
- 0.5
- 0.6
notifications:
email: false
script:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
julia 0.4
julia 0.6-
SIUnits
FixedPointNumbers
Compat 0.8.8
DSP
5 changes: 1 addition & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion runtests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 12 additions & 12 deletions src/Interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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"
Expand All @@ -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
Expand Down
132 changes: 80 additions & 52 deletions src/SampleBuf.jl
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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-
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -62,54 +62,82 @@ 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"

# 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
@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)
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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];

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Loading

0 comments on commit f4c300d

Please sign in to comment.