adds ability to control SinOsc freq from a signal
This commit is contained in:
parent
bf2e5bfb84
commit
efd841d74d
5 changed files with 71 additions and 18 deletions
34
src/nodes.jl
34
src/nodes.jl
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
11
test/test.jl
11
test/test.jl
|
@ -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)\"...")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue