adds ability to control SinOsc freq from a signal

This commit is contained in:
Spencer Russell 2014-06-24 03:30:38 -04:00
parent bf2e5bfb84
commit efd841d74d
5 changed files with 71 additions and 18 deletions

View file

@ -14,27 +14,45 @@ end
# Generates a sin tone at the given frequency
type SinOscRenderer <: AudioRenderer
freq::Real
phase::FloatingPoint
type SinOscRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
freq::T
phase::Float64
function SinOscRenderer(freq::Real)
function SinOscRenderer(freq)
new(freq, 0.0)
end
end
typealias SinOsc AudioNode{SinOscRenderer}
SinOsc(freq::Real) = SinOsc(SinOscRenderer(freq))
SinOsc(freq::Real) = SinOsc(SinOscRenderer{Float32}(freq))
SinOsc(freq::AudioNode) = SinOsc(SinOscRenderer{AudioNode}(freq))
SinOsc() = SinOsc(440)
export SinOsc
function render(node::SinOscRenderer, device_input::AudioBuf, info::DeviceInfo)
function render(node::SinOscRenderer{Float32}, device_input::AudioBuf,
info::DeviceInfo)
outbuf = Array(AudioSample, info.buf_size)
phase = node.phase
dt = 1/info.sample_rate
for i in 1:info.buf_size
outbuf[i] = sin(2pi*node.freq*phase)
phase += dt
outbuf[i] = sin(phase)
phase += 2pi*node.freq*dt
end
node.phase = phase
return outbuf
end
function render(node::SinOscRenderer{AudioNode}, device_input::AudioBuf,
info::DeviceInfo)
freq = render(node.freq, device_input, info)
block_size = min(length(freq), info.buf_size)
outbuf = Array(AudioSample, block_size)
phase = node.phase
dt = 1/(info.sample_rate)
for i in 1:block_size
outbuf[i] = sin(phase)
phase += 2pi*dt*freq[i]
end
node.phase = phase
return outbuf

View file

@ -111,6 +111,9 @@ function portaudio_task(jl_filedesc::Integer, stream::PortAudioStream)
ccall(:read, Clong, (Cint, Ptr{Void}, Culong),
jl_filedesc, desc_bytes, 1)
end
catch ex
warn("Audio Task died with exception: $ex")
Base.show_backtrace(STDOUT, catch_backtrace())
finally
# TODO: we need to close the stream here. Otherwise the audio callback
# will segfault accessing the output array if there were exceptions

View file

@ -8,6 +8,17 @@ if length(test_files) == 0
error("No test files found. Make sure you're running from the root directory")
end
# convenience function to calculate the mean-squared error
function mse(arr1::Array, arr2::Array)
@assert length(arr1) == length(arr2)
N = length(arr1)
err = 0.0
for i in 1:N
err += (arr2[i] - arr1[i])^2
end
err /= N
end
for test_file in test_files
info("")
info("Running tests from \"$(test_file)\"...")

View file

@ -1,5 +1,6 @@
using Base.Test
using AudioIO
import AudioIO.AudioBuf
const TEST_SAMPLERATE = 44100
const TEST_BUF_SIZE = 1024
@ -43,14 +44,14 @@ info("Testing Playing Float64 arrays...")
f64 = convert(Array{Float64}, sin(phase))
test_stream = TestAudioStream()
player = play(f64, test_stream)
@test process(test_stream) == convert(AudioIO.AudioBuf, f64[1:TEST_BUF_SIZE])
@test process(test_stream) == convert(AudioBuf, f64[1:TEST_BUF_SIZE])
info("Testing Playing Int8(Signed) arrays...")
i8 = Int8[-127:127]
test_stream = TestAudioStream()
player = play(i8, test_stream)
@test_approx_eq(process(test_stream)[1:255],
convert(AudioIO.AudioBuf, linspace(-1.0, 1.0, 255)))
convert(AudioBuf, linspace(-1.0, 1.0, 255)))
info("Testing Playing Uint8(Unsigned) arrays...")
# for unsigned 8-bit audio silence is represented as 128, so the symmetric range
@ -59,7 +60,7 @@ ui8 = Uint8[1:255]
test_stream = TestAudioStream()
player = play(ui8, test_stream)
@test_approx_eq(process(test_stream)[1:255],
convert(AudioIO.AudioBuf, linspace(-1.0, 1.0, 255)))
convert(AudioBuf, linspace(-1.0, 1.0, 255)))
info("Testing AudioNode Stopping...")
test_stream = TestAudioStream()
@ -93,3 +94,22 @@ end
info("Testing Audio Device Listing...")
d_list = get_audio_devices()
@test length(d_list) > 0
info("Testing param control with signals")
t = linspace(0, 1, TEST_SAMPLERATE+1)
f = 440 .- t .* (440-110)
dt = 1 / TEST_SAMPLERATE
# NOTE - this treats the phase as constant at each sample, which isn't strictly
# true. Unfortunately doing this correctly requires knowing more about the
# modulating signal and doing the real integral
phase = cumsum(2pi * dt .* f)
unshift!(phase, 0)
expected = convert(AudioBuf, sin(phase))
test_stream = TestAudioStream()
freq = LinRamp(440, 110, 1)
player = play(SinOsc(freq), test_stream)
out = process(test_stream)
#println("expected: $(expected[1:30])")
#println("got: $(out[1:30])")
@test mse(out, expected[1:TEST_BUF_SIZE]) < 1e-16

View file

@ -63,6 +63,11 @@ osc = SinOsc(freq)
render_output = render(osc, dev_input, test_info)
@test render_output == test_vect[1:test_info.buf_size]
render_output = render(osc, dev_input, test_info)
for i in 1:test_info.buf_size
if render_output[i] != test_vect[test_info.buf_size+i]
println("error at index $i")
end
end
@test render_output == test_vect[test_info.buf_size+1:2*test_info.buf_size]
stop(osc)
render_output = render(osc, dev_input, test_info)
@ -96,10 +101,6 @@ info("Testing LinRamp...")
ramp = LinRamp(0.25, 0.80, 1)
expected = convert(AudioBuf, linspace(0.25, 0.80, test_info.sample_rate+1))
render_output = render(ramp, dev_input, test_info)
@test render_output == expected[1:test_info.buf_size]
# TODO: there seems to be some slight error in the 2nd block. I THINK it's just
# floating point stuff, but we should probably check to be sure
#render_output = render(ramp, dev_input, test_info)
#println("expected: $(expected[test_info.buf_size+1:test_info.buf_size+10])")
#println("output: $(render_output[1:10])")
#@test render_output == expected[(test_info.buf_size+1):(2*test_info.buf_size)]
@test mse(render_output, expected[1:test_info.buf_size]) < 1e-16
render_output = render(ramp, dev_input, test_info)
@test mse(render_output, expected[(test_info.buf_size+1):(2*test_info.buf_size)]) < 1e-14