342 lines
9.3 KiB
Julia
342 lines
9.3 KiB
Julia
#### NullNode ####
|
|
|
|
type NullRenderer <: AudioRenderer end
|
|
typealias NullNode AudioNode{NullRenderer}
|
|
export NullNode
|
|
|
|
function render(node::NullRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
# TODO: preallocate buffer
|
|
return zeros(info.buf_size)
|
|
end
|
|
|
|
#### SinOsc ####
|
|
|
|
# Generates a sin tone at the given frequency
|
|
|
|
type SinOscRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
|
|
freq::T
|
|
phase::Float32
|
|
buf::AudioBuf
|
|
|
|
function SinOscRenderer(freq)
|
|
new(freq, 0.0, AudioSample[])
|
|
end
|
|
end
|
|
|
|
typealias SinOsc AudioNode{SinOscRenderer}
|
|
SinOsc(freq::Real) = SinOsc(SinOscRenderer{Float32}(freq))
|
|
SinOsc(freq::AudioNode) = SinOsc(SinOscRenderer{AudioNode}(freq))
|
|
SinOsc() = SinOsc(440)
|
|
export SinOsc
|
|
|
|
function render(node::SinOscRenderer{Float32}, device_input::AudioBuf,
|
|
info::DeviceInfo)
|
|
if length(node.buf) != info.buf_size
|
|
resize!(node.buf, info.buf_size)
|
|
end
|
|
outbuf = node.buf
|
|
phase = node.phase
|
|
freq = node.freq
|
|
# make sure these are Float32s so that we don't allocate doing conversions
|
|
# in the tight loop
|
|
pi2::Float32 = 2pi
|
|
phase_inc::Float32 = 2pi * freq / info.sample_rate
|
|
i::Int = 1
|
|
while i <= info.buf_size
|
|
outbuf[i] = sin(phase)
|
|
phase = (phase + phase_inc) % pi2
|
|
i += 1
|
|
end
|
|
node.phase = phase
|
|
return outbuf
|
|
end
|
|
|
|
function render(node::SinOscRenderer{AudioNode}, device_input::AudioBuf,
|
|
info::DeviceInfo)
|
|
freq = render(node.freq, device_input, info)::AudioBuf
|
|
block_size = min(length(freq), info.buf_size)
|
|
if(length(node.buf) != block_size)
|
|
resize!(node.buf, block_size)
|
|
end
|
|
outbuf = node.buf
|
|
|
|
phase::Float32 = node.phase
|
|
pi2::Float32 = 2pi
|
|
phase_step::Float32 = 2pi/(info.sample_rate)
|
|
i::Int = 1
|
|
while i <= block_size
|
|
outbuf[i] = sin(phase)
|
|
phase = (phase + phase_step*freq[i]) % pi2
|
|
i += 1
|
|
end
|
|
node.phase = phase
|
|
return outbuf
|
|
end
|
|
|
|
#### AudioMixer ####
|
|
|
|
# Mixes a set of inputs equally
|
|
|
|
type MixRenderer <: AudioRenderer
|
|
inputs::Vector{AudioNode}
|
|
buf::AudioBuf
|
|
|
|
MixRenderer(inputs) = new(inputs, AudioSample[])
|
|
MixRenderer() = MixRenderer(AudioNode[])
|
|
end
|
|
|
|
typealias AudioMixer AudioNode{MixRenderer}
|
|
export AudioMixer
|
|
|
|
function render(node::MixRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
if length(node.buf) != info.buf_size
|
|
resize!(node.buf, info.buf_size)
|
|
end
|
|
mix_buffer = node.buf
|
|
n_inputs = length(node.inputs)
|
|
i = 1
|
|
max_samples = 0
|
|
fill!(mix_buffer, 0)
|
|
while i <= n_inputs
|
|
rendered = render(node.inputs[i], device_input, info)::AudioBuf
|
|
nsamples = length(rendered)
|
|
max_samples = max(max_samples, nsamples)
|
|
j::Int = 1
|
|
while j <= nsamples
|
|
mix_buffer[j] += rendered[j]
|
|
j += 1
|
|
end
|
|
if nsamples < info.buf_size
|
|
deleteat!(node.inputs, i)
|
|
n_inputs -= 1
|
|
else
|
|
i += 1
|
|
end
|
|
end
|
|
if max_samples < length(mix_buffer)
|
|
return mix_buffer[1:max_samples]
|
|
else
|
|
# save the allocate and copy if we don't need to
|
|
return mix_buffer
|
|
end
|
|
end
|
|
|
|
Base.push!(mixer::AudioMixer, node::AudioNode) = push!(mixer.renderer.inputs, node)
|
|
|
|
#### Gain ####
|
|
type GainRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
|
|
in1::AudioNode
|
|
in2::T
|
|
buf::AudioBuf
|
|
|
|
GainRenderer(in1, in2) = new(in1, in2, AudioSample[])
|
|
end
|
|
|
|
function render(node::GainRenderer{Float32},
|
|
device_input::AudioBuf,
|
|
info::DeviceInfo)
|
|
input = render(node.in1, device_input, info)::AudioBuf
|
|
if length(node.buf) != length(input)
|
|
resize!(node.buf, length(input))
|
|
end
|
|
i = 1
|
|
while i <= length(input)
|
|
node.buf[i] = input[i] * node.in2
|
|
i += 1
|
|
end
|
|
return node.buf
|
|
end
|
|
|
|
function render(node::GainRenderer{AudioNode},
|
|
device_input::AudioBuf,
|
|
info::DeviceInfo)
|
|
in1_data = render(node.in1, device_input, info)::AudioBuf
|
|
in2_data = render(node.in2, device_input, info)::AudioBuf
|
|
block_size = min(length(in1_data), length(in2_data))
|
|
if length(node.buf) != block_size
|
|
resize!(node.buf, block_size)
|
|
end
|
|
i = 1
|
|
while i <= block_size
|
|
node.buf[i] = in1_data[i] * in2_data[i]
|
|
i += 1
|
|
end
|
|
return node.buf
|
|
end
|
|
|
|
typealias Gain AudioNode{GainRenderer}
|
|
Gain(in1::AudioNode, in2::Real) = Gain(GainRenderer{Float32}(in1, in2))
|
|
Gain(in1::AudioNode, in2::AudioNode) = Gain(GainRenderer{AudioNode}(in1, in2))
|
|
export Gain
|
|
|
|
#### Offset ####
|
|
type OffsetRenderer <: AudioRenderer
|
|
in_node::AudioNode
|
|
offset::Float32
|
|
buf::AudioBuf
|
|
|
|
OffsetRenderer(in_node, offset) = new(in_node, offset, AudioSample[])
|
|
end
|
|
|
|
function render(node::OffsetRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
input = render(node.in_node, device_input, info)::AudioBuf
|
|
if length(node.buf) != length(input)
|
|
resize!(node.buf, length(input))
|
|
end
|
|
i = 1
|
|
while i <= length(input)
|
|
node.buf[i] = input[i] + node.offset
|
|
i += 1
|
|
end
|
|
return node.buf
|
|
end
|
|
|
|
typealias Offset AudioNode{OffsetRenderer}
|
|
export Offset
|
|
|
|
|
|
#### Array Player ####
|
|
|
|
# Plays a AudioBuf by rendering it out piece-by-piece
|
|
|
|
type ArrayRenderer <: AudioRenderer
|
|
arr::AudioBuf
|
|
arr_index::Int
|
|
buf::AudioBuf
|
|
|
|
ArrayRenderer(arr::AudioBuf) = new(arr, 1, AudioSample[])
|
|
end
|
|
|
|
typealias ArrayPlayer AudioNode{ArrayRenderer}
|
|
export ArrayPlayer
|
|
|
|
function render(node::ArrayRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
range_end = min(node.arr_index + info.buf_size-1, length(node.arr))
|
|
block_size = range_end - node.arr_index + 1
|
|
if length(node.buf) != block_size
|
|
resize!(node.buf, block_size)
|
|
end
|
|
copy!(node.buf, 1, node.arr, node.arr_index, block_size)
|
|
node.arr_index = range_end + 1
|
|
return node.buf
|
|
end
|
|
|
|
# Allow users to play a raw array by wrapping it in an ArrayPlayer
|
|
function play(arr::AudioBuf, args...)
|
|
player = ArrayPlayer(arr)
|
|
play(player, args...)
|
|
end
|
|
|
|
# If the array is the wrong floating type, convert it
|
|
function play{T <: FloatingPoint}(arr::Array{T}, args...)
|
|
arr = convert(AudioBuf, arr)
|
|
play(arr, args...)
|
|
end
|
|
|
|
# If the array is an integer type, scale to [-1, 1] floating point
|
|
|
|
# integer audio can be slightly (by 1) more negative than positive,
|
|
# so we just scale so that +/- typemax(T) becomes +/- 1
|
|
function play{T <: Signed}(arr::Array{T}, args...)
|
|
arr = arr / typemax(T)
|
|
play(arr, args...)
|
|
end
|
|
|
|
function play{T <: Unsigned}(arr::Array{T}, args...)
|
|
zero = (typemax(T) + 1) / 2
|
|
range = floor(typemax(T) / 2)
|
|
arr = (arr .- zero) / range
|
|
play(arr, args...)
|
|
end
|
|
|
|
#### Noise ####
|
|
|
|
type WhiteNoiseRenderer <: AudioRenderer end
|
|
typealias WhiteNoise AudioNode{WhiteNoiseRenderer}
|
|
export WhiteNoise
|
|
|
|
function render(node::WhiteNoiseRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
return rand(AudioSample, info.buf_size) .* 2 .- 1
|
|
end
|
|
|
|
|
|
#### AudioInput ####
|
|
|
|
# Renders incoming audio input from the hardware
|
|
|
|
type InputRenderer <: AudioRenderer
|
|
channel::Int
|
|
InputRenderer(channel::Integer) = new(channel)
|
|
InputRenderer() = new(1)
|
|
end
|
|
|
|
function render(node::InputRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
@assert size(device_input, 1) == info.buf_size
|
|
return device_input[:, node.channel]
|
|
end
|
|
|
|
typealias AudioInput AudioNode{InputRenderer}
|
|
export AudioInput
|
|
|
|
#### LinRamp ####
|
|
|
|
type LinRampRenderer <: AudioRenderer
|
|
key_samples::Array{AudioSample}
|
|
key_durations::Array{Float32}
|
|
|
|
duration::Float32
|
|
buf::AudioBuf
|
|
|
|
LinRampRenderer(start, finish, dur) = LinRampRenderer([start,finish], [dur])
|
|
|
|
LinRampRenderer(key_samples, key_durations) =
|
|
LinRampRenderer(
|
|
[convert(AudioSample,s) for s in key_samples],
|
|
[convert(Float32,d) for d in key_durations]
|
|
)
|
|
|
|
function LinRampRenderer(key_samples::Array{AudioSample}, key_durations::Array{Float32})
|
|
@assert length(key_samples) == length(key_durations) + 1
|
|
new(key_samples, key_durations, sum(key_durations), AudioSample[])
|
|
end
|
|
end
|
|
|
|
typealias LinRamp AudioNode{LinRampRenderer}
|
|
export LinRamp
|
|
|
|
function render(node::LinRampRenderer, device_input::AudioBuf, info::DeviceInfo)
|
|
# Resize buffer if (1) it's too small or (2) we've hit the end of the ramp
|
|
ramp_samples::Int = int(node.duration * info.sample_rate)
|
|
block_samples = min(ramp_samples, info.buf_size)
|
|
if length(node.buf) != block_samples
|
|
resize!(node.buf, block_samples)
|
|
end
|
|
|
|
# Fill the buffer as long as there are more segments
|
|
dt::Float32 = 1/info.sample_rate
|
|
i::Int = 1
|
|
while i <= length(node.buf) && length(node.key_samples) > 1
|
|
|
|
# Fill as much of the buffer as we can with the current segment
|
|
ds::Float32 = (node.key_samples[2] - node.key_samples[1]) / node.key_durations[1] / info.sample_rate
|
|
while i <= length(node.buf)
|
|
node.buf[i] = node.key_samples[1]
|
|
node.key_samples[1] += ds
|
|
node.key_durations[1] -= dt
|
|
node.duration -= dt
|
|
i += 1
|
|
|
|
# Discard segment if we're finished
|
|
if node.key_durations[1] <= 0
|
|
if length(node.key_durations) > 1
|
|
node.key_durations[2] -= node.key_durations[1]
|
|
end
|
|
shift!(node.key_samples)
|
|
shift!(node.key_durations)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return node.buf
|
|
end
|