diff --git a/src/nodes.jl b/src/nodes.jl index f7aca26..9b69337 100644 --- a/src/nodes.jl +++ b/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 diff --git a/src/portaudio.jl b/src/portaudio.jl index 882f02ca..07ba6bf 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -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 diff --git a/test/test.jl b/test/test.jl index 36555ab..bb2a104 100755 --- a/test/test.jl +++ b/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)\"...") diff --git a/test/test_AudioIO.jl b/test/test_AudioIO.jl index a292e75..0077f6e 100644 --- a/test/test_AudioIO.jl +++ b/test/test_AudioIO.jl @@ -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 diff --git a/test/test_nodes.jl b/test/test_nodes.jl index 05ec63b..241e867 100644 --- a/test/test_nodes.jl +++ b/test/test_nodes.jl @@ -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