From 9c23d02066ff623a3b7283f73e333fc6dc139f06 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 1 Nov 2015 01:09:30 -1000 Subject: [PATCH 01/54] Update portaudio.jl --- src/portaudio.jl | 157 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 6b081bb..b3520cd 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -1,14 +1,15 @@ typealias PaTime Cdouble typealias PaError Cint typealias PaSampleFormat Culong -# PaStream is always used as an opaque type, so we're always dealing with the -# pointer +# PaStream is always used as an opaque type, so we're always dealing +# with the pointer typealias PaStream Ptr{Void} typealias PaDeviceIndex Cint typealias PaHostApiIndex Cint typealias PaTime Cdouble typealias PaHostApiTypeId Cint typealias PaStreamCallback Void +typealias PaStreamFlags Culong const PA_NO_ERROR = 0 const PA_INPUT_OVERFLOWED = -10000 + 19 @@ -81,6 +82,90 @@ function destroy(stream::PortAudioStream) portaudio_inited = false end +type Pa_StreamParameters + device::PaDeviceIndex + channelCount::Cint + sampleFormat::PaSampleFormat + suggestedLatency::PaTime + hostAPISpecificStreamInfo::Ptr{Void} +end + +function Pa_OpenStream(device::PaDeviceIndex, + channels::Cint, input::Bool, + sampleFormat::PaSampleFormat, + sampleRate::Cdouble, framesPerBuffer::Culong) + #= + Open a single stream, not necessarily the default one + The stream is unidirectional, either inout or default output + see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html + =# + streamPtr::Array{PaStream} = PaStream[0] + ioParameters = Pa_StreamParameters(device, channels, + sampleFormat, PaTime(0.001), + Ptr{Void}(0)) + if input + err = ccall((:Pa_OpenStream, libportaudio), PaError, + (Ptr{PaStream}, Ref{Pa_StreamParameters}, Ptr{Void}, + Cdouble, Culong, Culong, + Ptr{PaStreamCallback}, Ptr{Void}), + streamPtr, ioParameters, Ptr{Void}(0), + sampleRate, framesPerBuffer, 0, + Ptr{PaStreamCallback}(0), Ptr{Void}(0)) + else + err = ccall((:Pa_OpenStream, libportaudio), PaError, + (Ptr{PaStream}, Ptr{Void}, Ref{Pa_StreamParameters}, + Cdouble, Culong, Culong, + Ptr{PaStreamCallback}, Ptr{Void}), + streamPtr, Ptr{Void}(0), ioParameters, + sampleRate, framesPerBuffer, 0, + Ptr{PaStreamCallback}(0), Ptr{Void}(0)) + end + handle_status(err) + streamPtr[1] +end + +type Pa_AudioStream <: AudioStream + root::AudioMixer + info::DeviceInfo + show_warnings::Bool + stream::PaStream + sformat::PaSampleFormat + # NOTE: SharedArray is broken under Windows in the initial v0.40 + # but later development versions may have corrected this + # this is only used by the input stream subprocess currently + sbuffer::SharedArray + parent_working::Bool + + function Pa_AudioStream(device_index, channels=2, input=false, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16) + #= + Get device parameters needed for opening with portaudio + default is input as 44100/16bit int, same as CD audio type input + =# + require_portaudio_init() + stream = Pa_OpenStream(device_index, channels, input, sample_format, + Cdouble(sample_rate), Culong(framesPerBuffer)) + Pa_StartStream(stream) + root = AudioMixer() + datatype = PaSampleFormat_to_T(sample_format) + sbuf = SharedArray(datatype, framesPerBuffer) + this = new(root, DeviceInfo(sample_rate, framesPerBuffer), + show_warnings, stream, sample_format, sbuf, false) + info("Scheduling PortAudio Render Task...") + # the task will actually start running the next time the current task yields + if input + @schedule(pa_input_task(this)) + else + @schedule(pa_output_task(this)) + end + this + end +end + + ############ Internal Functions ############ function portaudio_task(stream::PortAudioStream) @@ -112,6 +197,74 @@ function portaudio_task(stream::PortAudioStream) end end +function PaSampleFormat_to_T(fmt::PaSampleFormat) + #= + Helper function to make the right type of buffer for various + sample formats. Converts PaSampleFormat to a typeof + =# + retval = UInt8(0x0) + if fmt == 1 + retval = Float32(1.0) + elseif fmt == 2 + retval = Int32(0x02) + elseif fmt == 4 + retval = Int24(0x04) + elseif fmt == 8 + retval = Int16(0x08) + elseif fmt == 16 + retval = Int8(0x10) + elseif fmt == 32 + retval = UInt8(0x20) + else + info("Flawed input to PaSampleFormat_to_primitive") + end + typeof(retval) +end + +function pa_input_task(stream::Pa_AudioStream) + #= + Get input device data, pass as SharedArray, no rendering + =# + info("PortAudio Input Task Running...") + n = bufsize(stream) + try + while true + while ((Pa_GetStreamReadAvailable(stream.stream) < n ) | + stream.parent_working) + sleep(0.005) + end + Pa_ReadStream(stream.stream, sdata(stream.sbuffer), n, + stream.show_warnings) + stream.parent_working = true + sleep(0.005) + end + catch ex + warn("Audio Input Task died with exception: $ex") + Base.show_backtrace(STDOUT, catch_backtrace()) + end +end + +function pa_output_task(stream::Pa_AudioStream) + #= + Send output device data, no rendering + =# + info("PortAudio Output Task Running...") + n = bufsize(stream) + datatype = PaSampleFormat_to_T(stream.sformat) + buffer = zeros(datatype, n) + try + while true + while Pa_GetStreamWriteAvailable(stream.stream) < n + sleep(0.005) + end + Pa_WriteStream(stream.stream, buffer, n, stream.show_warnings) + end + catch ex + warn("Audio Output Task died with exception: $ex") + Base.show_backtrace(STDOUT, catch_backtrace()) + end +end + type PaDeviceInfo struct_version::Cint name::Ptr{Cchar} From 3f90e3c90758d9ee38855f412d2ed06fae33a98f Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 1 Nov 2015 22:55:55 -1000 Subject: [PATCH 02/54] Update portaudio.jl Add methods to use audio streams other than the default output stream --- src/portaudio.jl | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index b3520cd..91769b3 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -105,7 +105,7 @@ function Pa_OpenStream(device::PaDeviceIndex, Ptr{Void}(0)) if input err = ccall((:Pa_OpenStream, libportaudio), PaError, - (Ptr{PaStream}, Ref{Pa_StreamParameters}, Ptr{Void}, + (PaStream, Ref{Pa_StreamParameters}, Ptr{Void}, Cdouble, Culong, Culong, Ptr{PaStreamCallback}, Ptr{Void}), streamPtr, ioParameters, Ptr{Void}(0), @@ -113,7 +113,7 @@ function Pa_OpenStream(device::PaDeviceIndex, Ptr{PaStreamCallback}(0), Ptr{Void}(0)) else err = ccall((:Pa_OpenStream, libportaudio), PaError, - (Ptr{PaStream}, Ptr{Void}, Ref{Pa_StreamParameters}, + (PaStream, Ptr{Void}, Ref{Pa_StreamParameters}, Cdouble, Culong, Culong, Ptr{PaStreamCallback}, Ptr{Void}), streamPtr, Ptr{Void}(0), ioParameters, @@ -130,11 +130,8 @@ type Pa_AudioStream <: AudioStream show_warnings::Bool stream::PaStream sformat::PaSampleFormat - # NOTE: SharedArray is broken under Windows in the initial v0.40 - # but later development versions may have corrected this - # this is only used by the input stream subprocess currently - sbuffer::SharedArray - parent_working::Bool + sbuffer::Array{Real} + parent_may_use_buffer::Bool function Pa_AudioStream(device_index, channels=2, input=false, sample_rate::Integer=44100, @@ -151,11 +148,10 @@ type Pa_AudioStream <: AudioStream Pa_StartStream(stream) root = AudioMixer() datatype = PaSampleFormat_to_T(sample_format) - sbuf = SharedArray(datatype, framesPerBuffer) + sbuf = ones(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), show_warnings, stream, sample_format, sbuf, false) info("Scheduling PortAudio Render Task...") - # the task will actually start running the next time the current task yields if input @schedule(pa_input_task(this)) else @@ -216,26 +212,34 @@ function PaSampleFormat_to_T(fmt::PaSampleFormat) elseif fmt == 32 retval = UInt8(0x20) else - info("Flawed input to PaSampleFormat_to_primitive") + info("Flawed input to PaSampleFormat_to_T, primitive unknown") end typeof(retval) end -function pa_input_task(stream::Pa_AudioStream) +function pa_input_task(strm::Pa_AudioStream) #= - Get input device data, pass as SharedArray, no rendering + Get input device data, pass as a producer, no rendering =# info("PortAudio Input Task Running...") - n = bufsize(stream) + n = bufsize(strm) + datatype = PaSampleFormat_to_T(strm.sformat) + # bigger ccall buffer to avoid overflow related errorss + buffer = zeros(datatype, n * 8) try while true - while ((Pa_GetStreamReadAvailable(stream.stream) < n ) | - stream.parent_working) + while Pa_GetStreamReadAvailable(strm.stream) < n + sleep(0.005) + end + while strm.parent_may_use_buffer sleep(0.005) end - Pa_ReadStream(stream.stream, sdata(stream.sbuffer), n, - stream.show_warnings) - stream.parent_working = true + err = ccall((:Pa_ReadStream, libportaudio), PaError, + (Ptr{PaStream}, Ptr{Void}, Culong), + strm.stream, buffer, n) + handle_status(err, strm.show_warnings) + strm.sbuffer[1: n] = buffer[1: n] + strm.parent_may_use_buffer = true sleep(0.005) end catch ex From 57fcabb6de4345be07a70dda15d7ed2a193108a7 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 09:44:30 -1000 Subject: [PATCH 03/54] Update REQUIRE --- REQUIRE | 1 + 1 file changed, 1 insertion(+) diff --git a/REQUIRE b/REQUIRE index a613e52..754bd86 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,5 @@ julia 0.3- BinDeps +Compat @osx Homebrew @windows WinRPM From 6786c2b4bb5d903bbcefeef2d5a04cdaf8e0d814 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 09:46:02 -1000 Subject: [PATCH 04/54] Update AudioIO.jl Add compatibility from 0.3 to 0.4 --- src/AudioIO.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AudioIO.jl b/src/AudioIO.jl index 644ad8f..4f29acd 100644 --- a/src/AudioIO.jl +++ b/src/AudioIO.jl @@ -1,4 +1,5 @@ module AudioIO +using Compat # export the basic API export play, stop, get_audio_devices From e87ef290a28b46cd3f943b5d257dc32c2a66117d Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 09:47:50 -1000 Subject: [PATCH 05/54] Update nodes.jl compatibility of union syntax from 0.3 to 0.4 --- src/nodes.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nodes.jl b/src/nodes.jl index daca2cd..ca496f2 100644 --- a/src/nodes.jl +++ b/src/nodes.jl @@ -13,7 +13,7 @@ end # Generates a sin tone at the given frequency -type SinOscRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer +@compat type SinOscRenderer{T<:Union{Float32, AudioNode}} <: AudioRenderer freq::T phase::Float32 buf::AudioBuf @@ -124,7 +124,7 @@ end Base.push!(mixer::AudioMixer, node::AudioNode) = push!(mixer.renderer.inputs, node) #### Gain #### -type GainRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer +@compat type GainRenderer{T<:Union{Float32, AudioNode}} <: AudioRenderer in1::AudioNode in2::T buf::AudioBuf From 2c4b21940ca3dbf3218e6ccfd6b251cdb247b2b3 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 09:50:42 -1000 Subject: [PATCH 06/54] Update sndfile.jl --- src/sndfile.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sndfile.jl b/src/sndfile.jl index b8ac331..1f6243e 100644 --- a/src/sndfile.jl +++ b/src/sndfile.jl @@ -16,10 +16,10 @@ const SF_SEEK_SET = 0 const SF_SEEK_CUR = 1 const SF_SEEK_END = 2 -const EXT_TO_FORMAT = [ +@compat const EXT_TO_FORMAT = ( ".wav" => SF_FORMAT_WAV, ".flac" => SF_FORMAT_FLAC -] +) type SF_INFO frames::Int64 From 1ad2d5b27d76015e176e56208aec13c3b933c00f Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 13:49:36 -1000 Subject: [PATCH 07/54] Update sndfile.jl compatibility 0.3 -> 0.4 changes --- src/sndfile.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sndfile.jl b/src/sndfile.jl index 1f6243e..66fd589 100644 --- a/src/sndfile.jl +++ b/src/sndfile.jl @@ -1,7 +1,7 @@ export af_open, FilePlayer, rewind, samplerate -const SFM_READ = int32(0x10) -const SFM_WRITE = int32(0x20) +@compat const SFM_READ = Int32(0x10) +@compat const SFM_WRITE = Int32(0x20) const SF_FORMAT_WAV = 0x010000 const SF_FORMAT_FLAC = 0x170000 @@ -45,7 +45,7 @@ samplerate(f::AudioFile) = f.sfinfo.samplerate # AudioIO.open is part of the public API, but is not exported so that it # doesn't conflict with Base.open -function open(path::String, mode::String = "r", +@compat function open(path::AbstractString, mode::AbstractString = "r", sampleRate::Integer = 44100, channels::Integer = 1, format::Integer = 0) @assert channels <= 2 @@ -180,7 +180,7 @@ end typealias FilePlayer AudioNode{FileRenderer} FilePlayer(file::AudioFile) = FilePlayer(FileRenderer(file)) -FilePlayer(path::String) = FilePlayer(AudioIO.open(path)) +@compat FilePlayer(path::AbstractString) = FilePlayer(AudioIO.open(path)) function render(node::FileRenderer, device_input::AudioBuf, info::DeviceInfo) @assert node.file.sfinfo.samplerate == info.sample_rate @@ -204,7 +204,7 @@ function render(node::FileRenderer, device_input::AudioBuf, info::DeviceInfo) end end -function play(filename::String, args...) +@compat function play(filename::AbstractString, args...) player = FilePlayer(filename) play(player, args...) end From 3c663c675a29b65a8cf12b70b2c1f8cd981b58fa Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 17:38:05 -1000 Subject: [PATCH 08/54] Update AudioIO.jl --- src/AudioIO.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AudioIO.jl b/src/AudioIO.jl index 4f29acd..95d7d5d 100644 --- a/src/AudioIO.jl +++ b/src/AudioIO.jl @@ -1,5 +1,6 @@ module AudioIO using Compat +importall Base.Operators # export the basic API export play, stop, get_audio_devices From 348e2576f9066bb2fa6d7993c7a6f038ef42365f Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 18:26:24 -1000 Subject: [PATCH 09/54] Update nodes.jl --- src/nodes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes.jl b/src/nodes.jl index ca496f2..0f142b1 100644 --- a/src/nodes.jl +++ b/src/nodes.jl @@ -228,7 +228,7 @@ function play(arr::AudioBuf, args...) end # If the array is the wrong floating type, convert it -function play{T <: FloatingPoint}(arr::Array{T}, args...) +@compat function play{T <: AbstractFloat}(arr::Array{T}, args...) arr = convert(AudioBuf, arr) play(arr, args...) end From 826fdafe5fd4bf80305171c00752a62351194765 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Nov 2015 18:58:57 -1000 Subject: [PATCH 10/54] Update portaudio.jl --- src/portaudio.jl | 137 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 44 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 91769b3..113727f 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -23,7 +23,7 @@ const paInt8 = convert(PaSampleFormat, 0x10) const paUInt8 = convert(PaSampleFormat, 0x20) # PaHostApiTypeId values -const pa_host_api_names = { +@compat const pa_host_api_names = ( 0 => "In Development", # use while developing support for a new host API 1 => "Direct Sound", 2 => "MME", @@ -38,7 +38,7 @@ const pa_host_api_names = { 12 => "Jack", 13 => "WASAPI", 14 => "AudioScience HPI" -} +) # track whether we've already inited PortAudio portaudio_inited = false @@ -90,15 +90,15 @@ type Pa_StreamParameters hostAPISpecificStreamInfo::Ptr{Void} end +""" + Open a single stream, not necessarily the default one + The stream is unidirectional, either inout or default output + see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html +""" function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, input::Bool, sampleFormat::PaSampleFormat, sampleRate::Cdouble, framesPerBuffer::Culong) - #= - Open a single stream, not necessarily the default one - The stream is unidirectional, either inout or default output - see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html - =# streamPtr::Array{PaStream} = PaStream[0] ioParameters = Pa_StreamParameters(device, channels, sampleFormat, PaTime(0.001), @@ -131,17 +131,18 @@ type Pa_AudioStream <: AudioStream stream::PaStream sformat::PaSampleFormat sbuffer::Array{Real} + sbuffer_output_waiting::Integer parent_may_use_buffer::Bool + """ + Get device parameters needed for opening with portaudio + default is input as 44100/16bit int, same as CD audio type input + """ function Pa_AudioStream(device_index, channels=2, input=false, sample_rate::Integer=44100, framesPerBuffer::Integer=2048, show_warnings::Bool=false, sample_format::PaSampleFormat=paInt16) - #= - Get device parameters needed for opening with portaudio - default is input as 44100/16bit int, same as CD audio type input - =# require_portaudio_init() stream = Pa_OpenStream(device_index, channels, input, sample_format, Cdouble(sample_rate), Culong(framesPerBuffer)) @@ -150,7 +151,7 @@ type Pa_AudioStream <: AudioStream datatype = PaSampleFormat_to_T(sample_format) sbuf = ones(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), - show_warnings, stream, sample_format, sbuf, false) + show_warnings, stream, sample_format, sbuf, 0, false) info("Scheduling PortAudio Render Task...") if input @schedule(pa_input_task(this)) @@ -161,6 +162,45 @@ type Pa_AudioStream <: AudioStream end end +""" +Blocking read from a Pa_AudioStream that is open as input +""" +function read_Pa_AudioStream(stream::Pa_AudioStream) + while true + while stream.parent_may_use_buffer == false + sleep(0.001) + end + buffer = deepcopy(stream.sbuffer) + stream.parent_may_use_buffer = false + return buffer + end +end + +""" +Blocking write to a Pa_AudioStream that is open for output +""" +function write_Pa_AudioStream(stream::Pa_AudioStream, buffer) + retval = 1 + sbufsize = length(stream.sbuffer) + inputlen = length(buffer) + if(inputlen > sbufsize) + info("Overflow at write_Pa_AudioStream") + retval = 0 + elseif(inputlen < sbufsize) + info("Underflow at write_Pa_AudioStream") + retval = -1 + end + while true + while stream.parent_may_use_buffer == false + sleep(0.001) + end + for idx in 1:min(sbufsize, inputlen) + stream.sbuffer[idx] = buffer[idx] + end + stream.parent_may_use_buffer = false + end + retval +end ############ Internal Functions ############ @@ -193,11 +233,11 @@ function portaudio_task(stream::PortAudioStream) end end -function PaSampleFormat_to_T(fmt::PaSampleFormat) - #= +""" Helper function to make the right type of buffer for various sample formats. Converts PaSampleFormat to a typeof - =# +""" +function PaSampleFormat_to_T(fmt::PaSampleFormat) retval = UInt8(0x0) if fmt == 1 retval = Float32(1.0) @@ -217,29 +257,29 @@ function PaSampleFormat_to_T(fmt::PaSampleFormat) typeof(retval) end -function pa_input_task(strm::Pa_AudioStream) - #= +""" Get input device data, pass as a producer, no rendering - =# +""" +function pa_input_task(stream::Pa_AudioStream) info("PortAudio Input Task Running...") - n = bufsize(strm) - datatype = PaSampleFormat_to_T(strm.sformat) - # bigger ccall buffer to avoid overflow related errorss + n = bufsize(stream) + datatype = PaSampleFormat_to_T(stream.sformat) + # bigger ccall buffer to avoid overflow related errors buffer = zeros(datatype, n * 8) try while true - while Pa_GetStreamReadAvailable(strm.stream) < n + while Pa_GetStreamReadAvailable(stream.stream) < n sleep(0.005) end - while strm.parent_may_use_buffer + while stream.parent_may_use_buffer sleep(0.005) end err = ccall((:Pa_ReadStream, libportaudio), PaError, - (Ptr{PaStream}, Ptr{Void}, Culong), - strm.stream, buffer, n) - handle_status(err, strm.show_warnings) - strm.sbuffer[1: n] = buffer[1: n] - strm.parent_may_use_buffer = true + (PaStream, Ptr{Void}, Culong), + stream.stream, buffer, n) + handle_status(err, stream.show_warnings) + stream.sbuffer[1: n] = buffer[1: n] + stream.parent_may_use_buffer = true sleep(0.005) end catch ex @@ -248,20 +288,27 @@ function pa_input_task(strm::Pa_AudioStream) end end -function pa_output_task(stream::Pa_AudioStream) - #= +""" Send output device data, no rendering - =# +""" +function pa_output_task(stream::Pa_AudioStream) info("PortAudio Output Task Running...") n = bufsize(stream) - datatype = PaSampleFormat_to_T(stream.sformat) - buffer = zeros(datatype, n) try while true - while Pa_GetStreamWriteAvailable(stream.stream) < n - sleep(0.005) + navail = stream.sbuffer_output_waiting + if navail > n + info("Possible output buffer overflow in stream") + navail = n end - Pa_WriteStream(stream.stream, buffer, n, stream.show_warnings) + if (navail > 1) & (stream.parent_may_use_buffer == false) & + (Pa_GetStreamWriteAvailable(stream.stream) < navail) + Pa_WriteStream(stream.stream, stream.sbuffer, + navail, stream.show_warnings) + stream.parent_may_use_buffer = true + else + sleep(0.005) + end end catch ex warn("Audio Output Task died with exception: $ex") @@ -291,21 +338,23 @@ type PaHostApiInfo defaultOutputDevice::PaDeviceIndex end -type PortAudioInterface <: AudioInterface - name::String - host_api::String +@compat type PortAudioInterface <: AudioInterface + name::AbstractString + host_api::AbstractString max_input_channels::Int max_output_channels::Int + device_index::PaDeviceIndex end function get_portaudio_devices() require_portaudio_init() device_count = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) - pa_devices = [Pa_GetDeviceInfo(i) for i in 0:(device_count - 1)] - [PortAudioInterface(bytestring(d.name), - bytestring(Pa_GetHostApiInfo(d.host_api).name), - d.max_input_channels, - d.max_output_channels) + pa_devices = [ [Pa_GetDeviceInfo(i), i] for i in 0:(device_count - 1)] + [PortAudioInterface(bytestring(d[1].name), + bytestring(Pa_GetHostApiInfo(d[1].host_api).name), + d[1].max_input_channels, + d[1].max_output_channels, + d[2]) for d in pa_devices] end From 7c87bfa7978832ec4d45b30436cf8a9d04d72164 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 15 Nov 2015 20:46:25 -1000 Subject: [PATCH 11/54] Create test_single_stream_input_and_output.jl --- test/test_single_stream_input_and_output.jl | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/test_single_stream_input_and_output.jl diff --git a/test/test_single_stream_input_and_output.jl b/test/test_single_stream_input_and_output.jl new file mode 100644 index 0000000..04dd08b --- /dev/null +++ b/test/test_single_stream_input_and_output.jl @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +#= +Testing for Julia AudioIO module +Open an input device, record 3 seconds of audio, +cube the values, then output the 3 seconds to an output device +=# + + +using Base.Test + +using AudioIO + +CHUNKSIZE = 2048 +FORMAT = AudioIO.paInt16 +CHANNELS = 2 +SRATE = 44100 +RECORD_SECONDS = 3 + +BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 2) + +""" +choose the devices for 2 channel IO +""" +function choose_input_output() + devices = get_audio_devices() +@test length(devices) > 0 + indev = -1 + outdev = -1 + for aud in devices + if (aud.max_input_channels == CHANNELS) & (indev == -1) + indev = aud.device_index + end + if(aud.max_output_channels == CHANNELS) & (outdev == -1) + outdev = aud.device_index + end + end + if indev == -1 + info("Appropriate input device not found.") + elseif outdev == -1 + info("Appropriate output device not found.") + else + info("Using input device ", bytestring(devices[indev + 1].name), + ", number ", devices[indev + 1].device_index, + " and output device ", bytestring(devices[outdev + 1].name), + ", number ", devices[outdev + 1].device_index) + end +@test indev >= 0 +@test outdev >= 0 + return indev, outdev +end + +""" +read from input +""" +function read_seconds!(devnum, buf = BUFFER, secs = RECORD_SECONDS) + chunksize = Integer(length(buf) / 10) + instream = AudioIO.Pa_AudioStream(devnum, CHANNELS, true, + SRATE, chunksize) + read_pos = 1 + while read_pos < length(buf) + chunk = AudioIO.read_Pa_AudioStream(instream) + end_pos = read_pos + chunksize - 1 + buf[read_pos: end_pos] = instream.sbuffer[1:chunksize] + read_pos = end_pos + 1 + end +@test read_pos < length(buf) + 10 +end + +""" +write to output +""" +function write_seconds(devnum, outbuf = BUFFER) + chunksize = Integer(length(outbuf) / 10) + outstream = AudioIO.Pa_AudioStream(devnum, CHANNELS, false, + SRATE, chunksize) + write_pos = 1 + while write_pos < length(outbuf) + end_pos = write_pos + chunksize - 1 + buffer = outbuf[write_pos: end_pos] + write_pos = end_pos + 1 + end +@test write_pos < length(outbuf) + 10 +end + +INS, OUTS = choose_input_output() + +read_seconds!(INS, BUFFER) +println("Finished reading") + +BUFFER .^= 3 +write_seconds(OUTS) +println("Finished writing") + From 5f4c990929c2b8c167d5f79e9546197e97ea234c Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 15 Nov 2015 21:01:08 -1000 Subject: [PATCH 12/54] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e19230e..2dd77fc 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,8 @@ AudioStream is an abstract type, which currently has a PortAudioStream subtype that writes to the sound card, and a TestAudioStream that is used in the unit tests. -Currently only 1 stream at a time is supported so there's no reason to provide +With the method PortAudioStream, which uses the default input and output device(s), +only 1 stream at a time is supported, so there's no reason to provide an explicit stream to the `play` function. The stream has a root mixer field which is an instance of the AudioMixer type, so that multiple AudioNodes can be heard at the same time. Whenever a new frame of audio is needed by the @@ -114,6 +115,15 @@ sound card, the stream calls the `render` method on the root audio mixer, which will in turn call the `render` methods on any input AudioNodes that are set up as inputs. +With the method Pa_OpenStream, a single stream is opened for reading or writing. +This function returns a Pa_AudioStream on success, which is accessed either via +the functions read_Pa_AudioStream or write_Pa_AudioStream. If nonblocking I/O is +needed, Pa_AudioStream.sbuffer is the I/O buffer, which should be accessed when +Pa_AudioStream.parent_may_use_buffer is true. After the sbuffer is changed by an +nonblocking user, the Pa_audioStream.parent_may_use_buffer field should be set to +false to signal that the device subprocess may continue with I/O. This is similar +to the methods used by PyAudio's python interface to PortAudio. + Installation ------------ From 8449a477884705e2fb001393290287596cdfca57 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 00:41:52 -1000 Subject: [PATCH 13/54] Update portaudio.jl --- src/portaudio.jl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 113727f..e697f91 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -131,7 +131,6 @@ type Pa_AudioStream <: AudioStream stream::PaStream sformat::PaSampleFormat sbuffer::Array{Real} - sbuffer_output_waiting::Integer parent_may_use_buffer::Bool """ @@ -151,7 +150,7 @@ type Pa_AudioStream <: AudioStream datatype = PaSampleFormat_to_T(sample_format) sbuf = ones(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), - show_warnings, stream, sample_format, sbuf, 0, false) + show_warnings, stream, sample_format, sbuf, false) info("Scheduling PortAudio Render Task...") if input @schedule(pa_input_task(this)) @@ -258,7 +257,7 @@ function PaSampleFormat_to_T(fmt::PaSampleFormat) end """ - Get input device data, pass as a producer, no rendering + Get input device data, pass via stream.sbuffer, no rendering """ function pa_input_task(stream::Pa_AudioStream) info("PortAudio Input Task Running...") @@ -296,15 +295,10 @@ function pa_output_task(stream::Pa_AudioStream) n = bufsize(stream) try while true - navail = stream.sbuffer_output_waiting - if navail > n - info("Possible output buffer overflow in stream") - navail = n - end - if (navail > 1) & (stream.parent_may_use_buffer == false) & - (Pa_GetStreamWriteAvailable(stream.stream) < navail) + if (stream.parent_may_use_buffer == false) & + (Pa_GetStreamWriteAvailable(stream.stream) >= n) Pa_WriteStream(stream.stream, stream.sbuffer, - navail, stream.show_warnings) + n, stream.show_warnings) stream.parent_may_use_buffer = true else sleep(0.005) From b6099d8385b68d3205e262681ff58df7ab13e64c Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 07:44:22 -1000 Subject: [PATCH 14/54] Update portaudio.jl --- src/portaudio.jl | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index e697f91..cd47550 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -148,7 +148,7 @@ type Pa_AudioStream <: AudioStream Pa_StartStream(stream) root = AudioMixer() datatype = PaSampleFormat_to_T(sample_format) - sbuf = ones(datatype, framesPerBuffer) + sbuf = zeros(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), show_warnings, stream, sample_format, sbuf, false) info("Scheduling PortAudio Render Task...") @@ -164,29 +164,37 @@ end """ Blocking read from a Pa_AudioStream that is open as input """ -function read_Pa_AudioStream(stream::Pa_AudioStream) - while true - while stream.parent_may_use_buffer == false - sleep(0.001) - end - buffer = deepcopy(stream.sbuffer) - stream.parent_may_use_buffer = false - return buffer - end +function read(stream::Pa_AudioStream, bufsize=stream.framesPerBuffer) + datatype = PaSampleFormat_to_T(stream.sample_format) + ret_buffer = zeros(datatype, bufsize) + cur = 0 + read_needed = bufsize + while stream.parent_may_use_buffer == false + sleep(0.001) + end + while read_needed > 0 + read_size = min(read_needed, stream.framesPerBuffer) + ret_buffer[cur: cur + read_size] = stream.sbuffer[1:read_size] + cur = cur + read_size + read_needed -= read_size + + end + stream.parent_may_use_buffer = true + ret_buffer end """ Blocking write to a Pa_AudioStream that is open for output """ -function write_Pa_AudioStream(stream::Pa_AudioStream, buffer) +function write(stream::Pa_AudioStream, buffer) retval = 1 sbufsize = length(stream.sbuffer) inputlen = length(buffer) if(inputlen > sbufsize) - info("Overflow at write_Pa_AudioStream") + info("Overflow at write to Pa_AudioStream") retval = 0 elseif(inputlen < sbufsize) - info("Underflow at write_Pa_AudioStream") + info("Underflow at write to Pa_AudioStream") retval = -1 end while true @@ -201,6 +209,13 @@ function write_Pa_AudioStream(stream::Pa_AudioStream, buffer) retval end +""" +Callback open of Pa_AudioStream for non-blocking I/O +""" + + + + ############ Internal Functions ############ function portaudio_task(stream::PortAudioStream) From 46db9e496124a5d690e6d4020a0c43be8626e267 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 09:19:37 -1000 Subject: [PATCH 15/54] Update portaudio.jl --- src/portaudio.jl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index cd47550..921806c 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -210,10 +210,25 @@ function write(stream::Pa_AudioStream, buffer) end """ -Callback open of Pa_AudioStream for non-blocking I/O +Callback helper function """ +function make_c_callback(func) + const mycallback_c = cfunction(func, (Ptr(Void), Cint), ())) +end +""" +Read callback process +""" +function pa_read_callback_task + +end +""" +Write callback process +""" +function pa_write_callback_task + +end ############ Internal Functions ############ From 409597b68853006388ee7d47bc490c270b28132f Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 09:57:33 -1000 Subject: [PATCH 16/54] Update portaudio.jl --- src/portaudio.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/portaudio.jl b/src/portaudio.jl index 921806c..e134ad9 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -211,8 +211,15 @@ end """ Callback helper function +makes the C function of this type: +typedef int PaStreamCallback(const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) """ function make_c_callback(func) + const mycallback_c = cfunction(func, (Ptr(Void), Cint), ())) end From 929a805ae0e5eece5aeaf9ea7b9d26f306b991ff Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 10:00:58 -1000 Subject: [PATCH 17/54] Update portaudio.jl --- src/portaudio.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index e134ad9..b44736c 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -218,9 +218,9 @@ typedef int PaStreamCallback(const void *input, void *output, PaStreamCallbackFlags statusFlags, void *userData) """ -function make_c_callback(func) +function make_c_callback(func, output=true) - const mycallback_c = cfunction(func, (Ptr(Void), Cint), ())) + const mycallback_c = cfunction(func, (Ptr(Void), Ptr(Void), Culong, Ptr(Void), Cint, Ptr(Void)), (Cint)) end """ From c0d4da831f5596fbbd52f1a0605fc32f608fab79 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 10:47:00 -1000 Subject: [PATCH 18/54] Update portaudio.jl --- src/portaudio.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index b44736c..8da98fb 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -219,8 +219,14 @@ typedef int PaStreamCallback(const void *input, void *output, void *userData) """ function make_c_callback(func, output=true) - - const mycallback_c = cfunction(func, (Ptr(Void), Ptr(Void), Culong, Ptr(Void), Cint, Ptr(Void)), (Cint)) + type Timesforcallback + Cdouble inAdc + Cdouble current + Cdouble outAdc + end + Timesforcallback tfc + + const mycallback_c = cfunction(func, (Ptr(Void), Ptr(Void), Culong, Ptr(Void), Cint, Ptr(Timesforcallback)), (Cint)) end """ From e2d81f37ad8ee7c306411e04b2235611b6109566 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 15:42:26 -1000 Subject: [PATCH 19/54] Update portaudio.jl --- src/portaudio.jl | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 8da98fb..981d8c0 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -95,14 +95,20 @@ end The stream is unidirectional, either inout or default output see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html """ -function Pa_OpenStream(device::PaDeviceIndex, - channels::Cint, input::Bool, - sampleFormat::PaSampleFormat, - sampleRate::Cdouble, framesPerBuffer::Culong) +function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, + input::Bool=false, sampleFormat::PaSampleFormat, + sampleRate::Cdouble, framesPerBuffer::Culong, + callback) streamPtr::Array{PaStream} = PaStream[0] ioParameters = Pa_StreamParameters(device, channels, sampleFormat, PaTime(0.001), Ptr{Void}(0)) + streamcallback = Ptr{PaStreamCallback}(0) + if callback != Void + if input + else + end + end if input err = ccall((:Pa_OpenStream, libportaudio), PaError, (PaStream, Ref{Pa_StreamParameters}, Ptr{Void}, @@ -110,7 +116,7 @@ function Pa_OpenStream(device::PaDeviceIndex, Ptr{PaStreamCallback}, Ptr{Void}), streamPtr, ioParameters, Ptr{Void}(0), sampleRate, framesPerBuffer, 0, - Ptr{PaStreamCallback}(0), Ptr{Void}(0)) + streamcallback, Ptr{Void}(0)) else err = ccall((:Pa_OpenStream, libportaudio), PaError, (PaStream, Ptr{Void}, Ref{Pa_StreamParameters}, @@ -118,7 +124,7 @@ function Pa_OpenStream(device::PaDeviceIndex, Ptr{PaStreamCallback}, Ptr{Void}), streamPtr, Ptr{Void}(0), ioParameters, sampleRate, framesPerBuffer, 0, - Ptr{PaStreamCallback}(0), Ptr{Void}(0)) + streamcallback, Ptr{Void}(0)) end handle_status(err) streamPtr[1] @@ -141,21 +147,25 @@ type Pa_AudioStream <: AudioStream sample_rate::Integer=44100, framesPerBuffer::Integer=2048, show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16) + sample_format::PaSampleFormat=paInt16, + callback=Void) require_portaudio_init() stream = Pa_OpenStream(device_index, channels, input, sample_format, - Cdouble(sample_rate), Culong(framesPerBuffer)) + Cdouble(sample_rate), Culong(framesPerBuffer), + callback) Pa_StartStream(stream) root = AudioMixer() datatype = PaSampleFormat_to_T(sample_format) sbuf = zeros(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), show_warnings, stream, sample_format, sbuf, false) - info("Scheduling PortAudio Render Task...") - if input - @schedule(pa_input_task(this)) - else - @schedule(pa_output_task(this)) + if callback==Void + info("Scheduling PortAudio Render Task...") + if input + @schedule(pa_input_task(this)) + else + @schedule(pa_output_task(this)) + end end this end From fbd489f50ba417938c4d407da273ea14a71fc879 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 18:46:06 -1000 Subject: [PATCH 20/54] Update portaudio.jl --- src/portaudio.jl | 51 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 981d8c0..3310d41 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -141,7 +141,10 @@ type Pa_AudioStream <: AudioStream """ Get device parameters needed for opening with portaudio - default is input as 44100/16bit int, same as CD audio type input + default is output as 44100/16bit int, same as CD audio type input + callback is optional, otherwise use blocing read and write functions + the callback must be a function that, if for writing, can return as an argument + a buffer the size and type same as stream.sbuffer, or if for output should """ function Pa_AudioStream(device_index, channels=2, input=false, sample_rate::Integer=44100, @@ -159,7 +162,7 @@ type Pa_AudioStream <: AudioStream sbuf = zeros(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), show_warnings, stream, sample_format, sbuf, false) - if callback==Void + if callback == Void info("Scheduling PortAudio Render Task...") if input @schedule(pa_input_task(this)) @@ -221,39 +224,35 @@ end """ Callback helper function -makes the C function of this type: -typedef int PaStreamCallback(const void *input, void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) +returns a C function of this type: + typedef int PaStreamCallback(const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) """ -function make_c_callback(func, output=true) - type Timesforcallback +function make_c_callback(func, Culong frameCount, input_buffer) + type CCallbackTimeInfo Cdouble inAdc Cdouble current Cdouble outAdc end - Timesforcallback tfc + typealias PaStreamCallbackFlags Culong + output = Ptr{Void}(0) + input = Ptr{Void}(input_buffer) + timeInfo = CCallbackTimeInfo(0) + statusFlags = PaStreamCallbackFlags(0) + userData = Ptr{Void}(0) - const mycallback_c = cfunction(func, (Ptr(Void), Ptr(Void), Culong, Ptr(Void), Cint, Ptr(Timesforcallback)), (Cint)) -end - -""" -Read callback process -""" -function pa_read_callback_task - + function ccallback() + func(input_buffer) + end end - -""" -Write callback process -""" -function pa_write_callback_task - + + + const c_callback = cfunction(func, (Ptr(Void), Ptr(Void), Culong, Ptr(Void), Cint, Ptr(Timesforcallback)), (Cint)) end - ############ Internal Functions ############ function portaudio_task(stream::PortAudioStream) From 5776cae209e9a4f3f216726f4d018ac703ed85d3 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 21:17:30 -1000 Subject: [PATCH 21/54] Update portaudio.jl --- src/portaudio.jl | 107 ++++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 3310d41..84e8249 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -92,19 +92,19 @@ end """ Open a single stream, not necessarily the default one - The stream is unidirectional, either inout or default output + The stream is unidirectional, either input or default output see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html """ function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, - input::Bool=false, sampleFormat::PaSampleFormat, + input::Bool, sampleFormat::PaSampleFormat, sampleRate::Cdouble, framesPerBuffer::Culong, callback) - streamPtr::Array{PaStream} = PaStream[0] + streamPtr::Array{PaStream} = PaStream[1] ioParameters = Pa_StreamParameters(device, channels, sampleFormat, PaTime(0.001), Ptr{Void}(0)) streamcallback = Ptr{PaStreamCallback}(0) - if callback != Void + if callback != 0 if input else end @@ -151,7 +151,7 @@ type Pa_AudioStream <: AudioStream framesPerBuffer::Integer=2048, show_warnings::Bool=false, sample_format::PaSampleFormat=paInt16, - callback=Void) + callback=0) require_portaudio_init() stream = Pa_OpenStream(device_index, channels, input, sample_format, Cdouble(sample_rate), Culong(framesPerBuffer), @@ -162,7 +162,7 @@ type Pa_AudioStream <: AudioStream sbuf = zeros(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), show_warnings, stream, sample_format, sbuf, false) - if callback == Void + if callback == 0 info("Scheduling PortAudio Render Task...") if input @schedule(pa_input_task(this)) @@ -177,8 +177,9 @@ end """ Blocking read from a Pa_AudioStream that is open as input """ -function read(stream::Pa_AudioStream, bufsize=stream.framesPerBuffer) - datatype = PaSampleFormat_to_T(stream.sample_format) +function read(stream::Pa_AudioStream) + datatype = PaSampleFormat_to_T(stream.sformat) + bufsize = length(stream.sbuffer) ret_buffer = zeros(datatype, bufsize) cur = 0 read_needed = bufsize @@ -186,11 +187,10 @@ function read(stream::Pa_AudioStream, bufsize=stream.framesPerBuffer) sleep(0.001) end while read_needed > 0 - read_size = min(read_needed, stream.framesPerBuffer) - ret_buffer[cur: cur + read_size] = stream.sbuffer[1:read_size] + read_size = min(read_needed, bufsize) + ret_buffer[cur+1: cur+read_size] = stream.sbuffer[1:read_size] cur = cur + read_size read_needed -= read_size - end stream.parent_may_use_buffer = true ret_buffer @@ -210,47 +210,62 @@ function write(stream::Pa_AudioStream, buffer) info("Underflow at write to Pa_AudioStream") retval = -1 end - while true - while stream.parent_may_use_buffer == false - sleep(0.001) - end - for idx in 1:min(sbufsize, inputlen) - stream.sbuffer[idx] = buffer[idx] - end - stream.parent_may_use_buffer = false + while stream.parent_may_use_buffer == false + sleep(0.001) + end + for idx in 1:min(sbufsize, inputlen) + stream.sbuffer[idx] = buffer[idx] end + stream.parent_may_use_buffer = false retval end """ Callback helper function -returns a C function of this type: - typedef int PaStreamCallback(const void *input, void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) +takes a function to process the data when the callback occurs, +plus the number of frames to be passes each callback, +plus an input buffer which is 0 if for output and size framesPerBuffer +if for input. Function should return a buffer of framesPerBuffer frames +if for output, should process the input_buffer argument if for output + +Returns a C function of this type: + typedef int PaStreamCallback(const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) """ -function make_c_callback(func, Culong frameCount, input_buffer) +function make_c_callback(julia_callback, sample_format::PaSampleFormat) type CCallbackTimeInfo - Cdouble inAdc - Cdouble current - Cdouble outAdc + inAdc::Cdouble + current::Cdouble + outAdc::Cdouble end typealias PaStreamCallbackFlags Culong - output = Ptr{Void}(0) - input = Ptr{Void}(input_buffer) - timeInfo = CCallbackTimeInfo(0) - statusFlags = PaStreamCallbackFlags(0) userData = Ptr{Void}(0) - function ccallback() - func(input_buffer) + function callback(output::Ptr{Void}, input::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{CCallBackTimeInfo}, + sflags::PaStreamCallbackFlags, + udata::Ptr{Void}) + if input != 0 + # input stream + julia_callback(Array{datatype}(input)) + else + # output stream + buf = julia_callback(0) + datatype = PaSampleFormat_to_T(sample_format) + obuf = Array{datatype}(output) + output[1:length(buf)] = buf[1:length(buf)] + end + 0 end -end - - - const c_callback = cfunction(func, (Ptr(Void), Ptr(Void), Culong, Ptr(Void), Cint, Ptr(Timesforcallback)), (Cint)) + const c_callback = cfunction(callback, Cint, + (Ptr{Void}, Ptr{Void}, + Culong, Ptr{CCallBackTimeInfo}, + Culong, Ptr{Void})) + return c_callback end ############ Internal Functions ############ @@ -285,8 +300,8 @@ function portaudio_task(stream::PortAudioStream) end """ - Helper function to make the right type of buffer for various - sample formats. Converts PaSampleFormat to a typeof +Helper function to make the right type of buffer for various +sample formats. Converts PaSampleFormat to a typeof """ function PaSampleFormat_to_T(fmt::PaSampleFormat) retval = UInt8(0x0) @@ -309,7 +324,7 @@ function PaSampleFormat_to_T(fmt::PaSampleFormat) end """ - Get input device data, pass via stream.sbuffer, no rendering +Get input device data, pass via stream.sbuffer, no rendering """ function pa_input_task(stream::Pa_AudioStream) info("PortAudio Input Task Running...") @@ -319,10 +334,8 @@ function pa_input_task(stream::Pa_AudioStream) buffer = zeros(datatype, n * 8) try while true - while Pa_GetStreamReadAvailable(stream.stream) < n - sleep(0.005) - end - while stream.parent_may_use_buffer + while (Pa_GetStreamReadAvailable(stream.stream) < n) | + stream.parent_may_use_buffer sleep(0.005) end err = ccall((:Pa_ReadStream, libportaudio), PaError, @@ -340,7 +353,7 @@ function pa_input_task(stream::Pa_AudioStream) end """ - Send output device data, no rendering +Send output device data, no rendering """ function pa_output_task(stream::Pa_AudioStream) info("PortAudio Output Task Running...") From 70d3cc3e823ea1ef3b59e85b4d69377f7fd47ccf Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 21:18:35 -1000 Subject: [PATCH 22/54] Update test_single_stream_input_and_output.jl --- test/test_single_stream_input_and_output.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_single_stream_input_and_output.jl b/test/test_single_stream_input_and_output.jl index 04dd08b..ce9102c 100644 --- a/test/test_single_stream_input_and_output.jl +++ b/test/test_single_stream_input_and_output.jl @@ -59,7 +59,7 @@ function read_seconds!(devnum, buf = BUFFER, secs = RECORD_SECONDS) SRATE, chunksize) read_pos = 1 while read_pos < length(buf) - chunk = AudioIO.read_Pa_AudioStream(instream) + chunk = AudioIO.read(instream) end_pos = read_pos + chunksize - 1 buf[read_pos: end_pos] = instream.sbuffer[1:chunksize] read_pos = end_pos + 1 @@ -78,11 +78,13 @@ function write_seconds(devnum, outbuf = BUFFER) while write_pos < length(outbuf) end_pos = write_pos + chunksize - 1 buffer = outbuf[write_pos: end_pos] + AudioIO.write(outstream, buffer) write_pos = end_pos + 1 end @test write_pos < length(outbuf) + 10 end +# begin main test portion INS, OUTS = choose_input_output() read_seconds!(INS, BUFFER) From 81fcf8c8d11ce3a44aa5ad1d119a157147b57287 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 19 Nov 2015 21:24:33 -1000 Subject: [PATCH 23/54] Update README.md --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2dd77fc..fd0a0cb 100644 --- a/README.md +++ b/README.md @@ -115,13 +115,20 @@ sound card, the stream calls the `render` method on the root audio mixer, which will in turn call the `render` methods on any input AudioNodes that are set up as inputs. -With the method Pa_OpenStream, a single stream is opened for reading or writing. -This function returns a Pa_AudioStream on success, which is accessed either via -the functions read_Pa_AudioStream or write_Pa_AudioStream. If nonblocking I/O is -needed, Pa_AudioStream.sbuffer is the I/O buffer, which should be accessed when -Pa_AudioStream.parent_may_use_buffer is true. After the sbuffer is changed by an -nonblocking user, the Pa_audioStream.parent_may_use_buffer field should be set to -false to signal that the device subprocess may continue with I/O. This is similar +When a Pa_AudioStream is created via a call to +Pa_AudioStream(device_index, channels=2, input=false, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0) +a single stream is opened for reading or writing. This returns a Pa_AudioStream on success, +which is used either via the functions AudioIO.read(::Pa_AudioStream) or +AudioIO.write(::Pa_AudioStream, buffer) for read and write respectively. + +If nonblocking I/O is needed, a callback may be passed to the Pa_AudioStream constructor, +which is provided by the user and should return a buffer of frames if used for output and should +handle a buffer of frames as an argument if sused as input. This is similar to the methods used by PyAudio's python interface to PortAudio. Installation From 978752ff197ba517728a689f3e71c77cc5fcbdf8 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 20 Nov 2015 21:25:24 -1000 Subject: [PATCH 24/54] Update portaudio.jl --- src/portaudio.jl | 127 +++++++++++------------------------------------ 1 file changed, 30 insertions(+), 97 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 84e8249..0142b9a 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -136,8 +136,7 @@ type Pa_AudioStream <: AudioStream show_warnings::Bool stream::PaStream sformat::PaSampleFormat - sbuffer::Array{Real} - parent_may_use_buffer::Bool + framesPerBuffer::Integer """ Get device parameters needed for opening with portaudio @@ -158,66 +157,51 @@ type Pa_AudioStream <: AudioStream callback) Pa_StartStream(stream) root = AudioMixer() - datatype = PaSampleFormat_to_T(sample_format) - sbuf = zeros(datatype, framesPerBuffer) this = new(root, DeviceInfo(sample_rate, framesPerBuffer), - show_warnings, stream, sample_format, sbuf, false) - if callback == 0 - info("Scheduling PortAudio Render Task...") - if input - @schedule(pa_input_task(this)) - else - @schedule(pa_output_task(this)) - end - end - this + show_warnings, stream, sample_format, framesPerBuffer) end end """ Blocking read from a Pa_AudioStream that is open as input """ -function read(stream::Pa_AudioStream) +function read(stream::Pa_AudioStream, nframes::Int) datatype = PaSampleFormat_to_T(stream.sformat) - bufsize = length(stream.sbuffer) - ret_buffer = zeros(datatype, bufsize) + # bigger ccall buffer to avoid overflow related errors + buffer = zeros(datatype, stream.framesPerBuffer * 8) + return_buffer = zeros(datatype, nframes * 8) cur = 0 - read_needed = bufsize - while stream.parent_may_use_buffer == false - sleep(0.001) - end + read_needed = nframes while read_needed > 0 - read_size = min(read_needed, bufsize) - ret_buffer[cur+1: cur+read_size] = stream.sbuffer[1:read_size] + read_size = min(read_needed, stream.framesPerBuffer) + err = ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), + stream.stream, buffer, read_size) + handle_status(err, stream.show_warnings) + return_buffer[cur+1: cur+read_size] = buffer[1:read_size] cur = cur + read_size read_needed -= read_size end - stream.parent_may_use_buffer = true - ret_buffer + return_buffer end """ Blocking write to a Pa_AudioStream that is open for output """ function write(stream::Pa_AudioStream, buffer) - retval = 1 - sbufsize = length(stream.sbuffer) - inputlen = length(buffer) - if(inputlen > sbufsize) - info("Overflow at write to Pa_AudioStream") - retval = 0 - elseif(inputlen < sbufsize) - info("Underflow at write to Pa_AudioStream") - retval = -1 - end - while stream.parent_may_use_buffer == false - sleep(0.001) - end - for idx in 1:min(sbufsize, inputlen) - stream.sbuffer[idx] = buffer[idx] + write_needed = length(buffer) + cur = 0 + while write_needed > 0 + write_size = min(write_needed, stream.framesPerBuffer) + while Pa_GetStreamWriteAvailable(stream.stream) < write_size + sleep(0.001) + end + Pa_WriteStream(stream.stream, buffer[cur+1: cur+write_size], + write_size, stream.show_warnings) + cur = cur + write_size + write_needed -= write_size end - stream.parent_may_use_buffer = false - retval + 0 end """ @@ -251,13 +235,14 @@ function make_c_callback(julia_callback, sample_format::PaSampleFormat) udata::Ptr{Void}) if input != 0 # input stream - julia_callback(Array{datatype}(input)) + julia_callback(pointer_to_array(Ptr{datatype}(input), + (frameCount, ))) else # output stream buf = julia_callback(0) datatype = PaSampleFormat_to_T(sample_format) - obuf = Array{datatype}(output) - output[1:length(buf)] = buf[1:length(buf)] + parray = Ptr{datatype}(buf) + obuf = pointer_to_array(parray, (frameCount, )) end 0 end @@ -323,58 +308,6 @@ function PaSampleFormat_to_T(fmt::PaSampleFormat) typeof(retval) end -""" -Get input device data, pass via stream.sbuffer, no rendering -""" -function pa_input_task(stream::Pa_AudioStream) - info("PortAudio Input Task Running...") - n = bufsize(stream) - datatype = PaSampleFormat_to_T(stream.sformat) - # bigger ccall buffer to avoid overflow related errors - buffer = zeros(datatype, n * 8) - try - while true - while (Pa_GetStreamReadAvailable(stream.stream) < n) | - stream.parent_may_use_buffer - sleep(0.005) - end - err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), - stream.stream, buffer, n) - handle_status(err, stream.show_warnings) - stream.sbuffer[1: n] = buffer[1: n] - stream.parent_may_use_buffer = true - sleep(0.005) - end - catch ex - warn("Audio Input Task died with exception: $ex") - Base.show_backtrace(STDOUT, catch_backtrace()) - end -end - -""" -Send output device data, no rendering -""" -function pa_output_task(stream::Pa_AudioStream) - info("PortAudio Output Task Running...") - n = bufsize(stream) - try - while true - if (stream.parent_may_use_buffer == false) & - (Pa_GetStreamWriteAvailable(stream.stream) >= n) - Pa_WriteStream(stream.stream, stream.sbuffer, - n, stream.show_warnings) - stream.parent_may_use_buffer = true - else - sleep(0.005) - end - end - catch ex - warn("Audio Output Task died with exception: $ex") - Base.show_backtrace(STDOUT, catch_backtrace()) - end -end - type PaDeviceInfo struct_version::Cint name::Ptr{Cchar} From e6b5491c51f9f1a97121f5968aadf7ce6ba6faa5 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 20 Nov 2015 21:32:57 -1000 Subject: [PATCH 25/54] Update portaudio.jl --- src/portaudio.jl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/portaudio.jl b/src/portaudio.jl index 0142b9a..f82f70a 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -162,6 +162,29 @@ type Pa_AudioStream <: AudioStream end end +function open_read(device_index, channels=2, input=true, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0) + Pa_AudioStream(device_index, channels, input, sample_rate, + framesPerBuffer, show_warnings, + sample_format, callback) +end + +function open_write(device_index, channels=2, input=false, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0) + Pa_AudioStream(device_index, channels, input, sample_rate, + framesPerBuffer, show_warnings, + sample_format, callback) +end + + """ Blocking read from a Pa_AudioStream that is open as input """ From fb28a9493207c2e688b8c81b4c9455883747ebca Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 20 Nov 2015 21:34:18 -1000 Subject: [PATCH 26/54] Update AudioIO.jl --- src/AudioIO.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AudioIO.jl b/src/AudioIO.jl index 95d7d5d..9256f60 100644 --- a/src/AudioIO.jl +++ b/src/AudioIO.jl @@ -3,7 +3,7 @@ using Compat importall Base.Operators # export the basic API -export play, stop, get_audio_devices +export play, stop, get_audio_devices, open_read, open_write, read, write # default stream used when none is given _stream = nothing From 08bb3c9c5f42900a3a1eeea289138b2e08078c0f Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 20 Nov 2015 21:43:22 -1000 Subject: [PATCH 27/54] Update README.md --- README.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fd0a0cb..3f245c8 100644 --- a/README.md +++ b/README.md @@ -115,21 +115,25 @@ sound card, the stream calls the `render` method on the root audio mixer, which will in turn call the `render` methods on any input AudioNodes that are set up as inputs. -When a Pa_AudioStream is created via a call to -Pa_AudioStream(device_index, channels=2, input=false, - sample_rate::Integer=44100, - framesPerBuffer::Integer=2048, - show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16, - callback=0) -a single stream is opened for reading or writing. This returns a Pa_AudioStream on success, -which is used either via the functions AudioIO.read(::Pa_AudioStream) or -AudioIO.write(::Pa_AudioStream, buffer) for read and write respectively. - -If nonblocking I/O is needed, a callback may be passed to the Pa_AudioStream constructor, +For opening an audio device for either reading or writing but not both, the functions +open_read(device_index, channels=2, sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, callback=0) + +and +open_write(device_index, channels=2, sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, callback=0) + +are provided. These return a Pa_Audiostream which can be used with the blocking I/O functions +read(stream::Pa_AudioStream, nframes::Int) (returns an array of frames) +and +write(stream::Pa_AudioStream, buffer) +for streams opened for reading and writing, respectively. + +If nonblocking I/O is needed, a callback may be passed to the open_read or open_write methods, which is provided by the user and should return a buffer of frames if used for output and should -handle a buffer of frames as an argument if sused as input. This is similar -to the methods used by PyAudio's python interface to PortAudio. +handle a buffer of frames as an argument if used as input. Installation ------------ From 0dff3842c8cc97728e0723078f2fbdafec210e78 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 21 Nov 2015 23:39:08 -1000 Subject: [PATCH 28/54] Update portaudio.jl --- src/portaudio.jl | 98 +++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index f82f70a..6bc4e8e 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -136,6 +136,7 @@ type Pa_AudioStream <: AudioStream show_warnings::Bool stream::PaStream sformat::PaSampleFormat + channels::Integer framesPerBuffer::Integer """ @@ -158,28 +159,29 @@ type Pa_AudioStream <: AudioStream Pa_StartStream(stream) root = AudioMixer() this = new(root, DeviceInfo(sample_rate, framesPerBuffer), - show_warnings, stream, sample_format, framesPerBuffer) + show_warnings, stream, sample_format, + channels, framesPerBuffer) end end -function open_read(device_index, channels=2, input=true, +function open_read(device_index, channels=2, sample_rate::Integer=44100, framesPerBuffer::Integer=2048, show_warnings::Bool=false, sample_format::PaSampleFormat=paInt16, callback=0) - Pa_AudioStream(device_index, channels, input, sample_rate, + Pa_AudioStream(device_index, channels, true, sample_rate, framesPerBuffer, show_warnings, sample_format, callback) end -function open_write(device_index, channels=2, input=false, +function open_write(device_index, channels=2, sample_rate::Integer=44100, framesPerBuffer::Integer=2048, show_warnings::Bool=false, sample_format::PaSampleFormat=paInt16, callback=0) - Pa_AudioStream(device_index, channels, input, sample_rate, + Pa_AudioStream(device_index, channels, false, sample_rate, framesPerBuffer, show_warnings, sample_format, callback) end @@ -190,19 +192,19 @@ Blocking read from a Pa_AudioStream that is open as input """ function read(stream::Pa_AudioStream, nframes::Int) datatype = PaSampleFormat_to_T(stream.sformat) - # bigger ccall buffer to avoid overflow related errors - buffer = zeros(datatype, stream.framesPerBuffer * 8) - return_buffer = zeros(datatype, nframes * 8) + buf = zeros(datatype, stream.framesPerBuffer * stream.channels) + return_buffer = zeros(datatype, nframes * stream.channels) cur = 0 read_needed = nframes while read_needed > 0 read_size = min(read_needed, stream.framesPerBuffer) + used = read_size * stream.channels err = ccall((:Pa_ReadStream, libportaudio), PaError, (PaStream, Ptr{Void}, Culong), - stream.stream, buffer, read_size) - handle_status(err, stream.show_warnings) - return_buffer[cur+1: cur+read_size] = buffer[1:read_size] - cur = cur + read_size + stream.stream, buf, read_size) + handle_status(err, stream.show_warnings) + return_buffer[cur + 1: cur + used] = buf[1:used] + cur = cur + used read_needed -= read_size end return_buffer @@ -211,22 +213,48 @@ end """ Blocking write to a Pa_AudioStream that is open for output """ -function write(stream::Pa_AudioStream, buffer) - write_needed = length(buffer) +function write(ostream::Pa_AudioStream, buffer) cur = 0 + write_needed = length(buffer) while write_needed > 0 - write_size = min(write_needed, stream.framesPerBuffer) - while Pa_GetStreamWriteAvailable(stream.stream) < write_size + write_size = min(write_needed, ostream.framesPerBuffer) + while Pa_GetStreamWriteAvailable(ostream.stream) < write_size sleep(0.001) end - Pa_WriteStream(stream.stream, buffer[cur+1: cur+write_size], - write_size, stream.show_warnings) - cur = cur + write_size + end_cur = cur + write_size + ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), ostream.stream, + buffer[cur + 1: end_cur], Culong(write_size/ostream.channels)) write_needed -= write_size + cur = end_cur end 0 end +type CCallbackTimeInfo + inAdc::Cdouble + current::Cdouble + outAdc::Cdouble +end +typealias PaStreamCallbackFlags Culong + +function callback(output::Ptr{Void}, input::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, + sflags::Culong, udata::Ptr{Void}) + global julia_callback + if input + # input stream + julia_callback(pointer_to_array(Ptr{datatype}(input), + (frameCount, ))) + else + # output stream + buf = julia_callback(0) + parray = Ptr{datatype}(buf) + obuf = pointer_to_array(parray, (frameCount, )) + end + Cint(0) +end """ Callback helper function takes a function to process the data when the callback occurs, @@ -242,36 +270,12 @@ Returns a C function of this type: PaStreamCallbackFlags statusFlags, void *userData) """ -function make_c_callback(julia_callback, sample_format::PaSampleFormat) - type CCallbackTimeInfo - inAdc::Cdouble - current::Cdouble - outAdc::Cdouble - end - typealias PaStreamCallbackFlags Culong - userData = Ptr{Void}(0) - - function callback(output::Ptr{Void}, input::Ptr{Void}, - frameCount::Culong, - timeInfo::Ptr{CCallBackTimeInfo}, - sflags::PaStreamCallbackFlags, - udata::Ptr{Void}) - if input != 0 - # input stream - julia_callback(pointer_to_array(Ptr{datatype}(input), - (frameCount, ))) - else - # output stream - buf = julia_callback(0) - datatype = PaSampleFormat_to_T(sample_format) - parray = Ptr{datatype}(buf) - obuf = pointer_to_array(parray, (frameCount, )) - end - 0 - end +function make_c_callback(jul_callback, sample_format::PaSampleFormat) + datatype = PaSampleFormat_to_T(sample_format) + julia_callback = jul_callback const c_callback = cfunction(callback, Cint, (Ptr{Void}, Ptr{Void}, - Culong, Ptr{CCallBackTimeInfo}, + Culong, Ptr{AudioIO.CCallbackTimeInfo}, Culong, Ptr{Void})) return c_callback end From eb9850000438a06f7ef43e5f622159f2ae1d9a42 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 21 Nov 2015 23:56:46 -1000 Subject: [PATCH 29/54] Delete test_single_stream_input_and_output.jl --- test/test_single_stream_input_and_output.jl | 96 --------------------- 1 file changed, 96 deletions(-) delete mode 100644 test/test_single_stream_input_and_output.jl diff --git a/test/test_single_stream_input_and_output.jl b/test/test_single_stream_input_and_output.jl deleted file mode 100644 index ce9102c..0000000 --- a/test/test_single_stream_input_and_output.jl +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- - -#= -Testing for Julia AudioIO module -Open an input device, record 3 seconds of audio, -cube the values, then output the 3 seconds to an output device -=# - - -using Base.Test - -using AudioIO - -CHUNKSIZE = 2048 -FORMAT = AudioIO.paInt16 -CHANNELS = 2 -SRATE = 44100 -RECORD_SECONDS = 3 - -BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 2) - -""" -choose the devices for 2 channel IO -""" -function choose_input_output() - devices = get_audio_devices() -@test length(devices) > 0 - indev = -1 - outdev = -1 - for aud in devices - if (aud.max_input_channels == CHANNELS) & (indev == -1) - indev = aud.device_index - end - if(aud.max_output_channels == CHANNELS) & (outdev == -1) - outdev = aud.device_index - end - end - if indev == -1 - info("Appropriate input device not found.") - elseif outdev == -1 - info("Appropriate output device not found.") - else - info("Using input device ", bytestring(devices[indev + 1].name), - ", number ", devices[indev + 1].device_index, - " and output device ", bytestring(devices[outdev + 1].name), - ", number ", devices[outdev + 1].device_index) - end -@test indev >= 0 -@test outdev >= 0 - return indev, outdev -end - -""" -read from input -""" -function read_seconds!(devnum, buf = BUFFER, secs = RECORD_SECONDS) - chunksize = Integer(length(buf) / 10) - instream = AudioIO.Pa_AudioStream(devnum, CHANNELS, true, - SRATE, chunksize) - read_pos = 1 - while read_pos < length(buf) - chunk = AudioIO.read(instream) - end_pos = read_pos + chunksize - 1 - buf[read_pos: end_pos] = instream.sbuffer[1:chunksize] - read_pos = end_pos + 1 - end -@test read_pos < length(buf) + 10 -end - -""" -write to output -""" -function write_seconds(devnum, outbuf = BUFFER) - chunksize = Integer(length(outbuf) / 10) - outstream = AudioIO.Pa_AudioStream(devnum, CHANNELS, false, - SRATE, chunksize) - write_pos = 1 - while write_pos < length(outbuf) - end_pos = write_pos + chunksize - 1 - buffer = outbuf[write_pos: end_pos] - AudioIO.write(outstream, buffer) - write_pos = end_pos + 1 - end -@test write_pos < length(outbuf) + 10 -end - -# begin main test portion -INS, OUTS = choose_input_output() - -read_seconds!(INS, BUFFER) -println("Finished reading") - -BUFFER .^= 3 -write_seconds(OUTS) -println("Finished writing") - From 670e8343372deb9d649860248f9bf6313fa89449 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 21 Nov 2015 23:59:36 -1000 Subject: [PATCH 30/54] Create read_and_write.jl --- examples/read_and_write.jl | 159 +++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 examples/read_and_write.jl diff --git a/examples/read_and_write.jl b/examples/read_and_write.jl new file mode 100644 index 0000000..45ec7b0 --- /dev/null +++ b/examples/read_and_write.jl @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +# Examples for Julia AudioIO module + +using AudioIO + +CHUNKSIZE = 40960 +FORMAT = AudioIO.paInt16 +CHANNELS = 2 +SRATE = 44100 +RECORD_SECONDS = 3 + +BUFFER = zeros(Int16, SRATE * RECORD_SECONDS) +BUFSIZE = length(BUFFER) + +""" +choose the devices for 2 channel IO +""" +function choose_input_output() + devices = get_audio_devices() +@test length(devices) > 0 + indev = -1 + outdev = -1 + for aud in devices + if (aud.max_input_channels == CHANNELS) & (indev == -1) + indev = aud.device_index + end + if(aud.max_output_channels == CHANNELS) & (outdev == -1) + outdev = aud.device_index + end + end + if indev == -1 + info("Appropriate input device not found.") + elseif outdev == -1 + info("Appropriate output device not found.") + else + info("Using input device ", bytestring(devices[indev + 1].name), + ", number ", devices[indev + 1].device_index, + " and output device ", bytestring(devices[outdev + 1].name), + ", number ", devices[outdev + 1].device_index) + end +@test indev >= 0 +@test outdev >= 0 + return indev, outdev +end + +""" +read from input +""" +function read_blocking(devnum) + instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE) + BUFFER = AudioIO.read(instream, BUFSIZE) + AudioIO.Pa_CloseStream(instream.stream) +end + +""" +write to output +""" +function write_blocking(devnum) + outstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE) + AudioIO.write(outstream, BUFFER) + AudioIO.Pa_CloseStream(outstream.stream) +end + +""" +read callback function +""" +read_position = 0 +write_position = 0 +function rcallback(buf) + read_size = length(buf) + if read_position + read_size > length(buffer) + read_size = length(buffer) - read_position + end + if read_size > 1 + BUFFER[read_position + 1 : read_position + read_size] = + buf[1: read_size] + read_position += read_size + end + 0 +end + +""" +write callback function +""" +function wcallback(buf) + write_size = CHUNKSIZE * CHANNELS + if write_position + write_size > length(buffer) + write_size = length(buffer) - write_position + end + if write_size < 2 + return Void + end + start_position = write_position + 1 + write_position += write_size + buf = BUFFER[start_position: write_position] +end + +""" +read using callback +""" +function start_read_callback(devnum) + AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, + AudioIO.make_c_callback(rcallback, FORMAT)) +end + +""" +write using callback +""" +function start_write_callback(devnum) + AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, + AudioIO.make_c_callback(wcallback, FORMAT)) +end + +function play_note(frequency, amplitude, duration, srate, ostream) + N = round(Int, srate / frequency) + T = round(Int, frequency * duration) # repeat for T cycles + dt = 1.0 / srate + tone = zeros(Int16, (N + N) * T) + idx = 1 + while idx < (N + N) * T + tone[idx] = round(Int, amplitude * sin(2 * pi * frequency * + idx * dt) * 32767.0) + tone[idx + 1] = tone[idx] + idx += 2 + end + AudioIO.write(ostream, tone) +end + +INS, OUTS = choose_input_output() + +read_blocking(INS) +println("Finished blocking type reading device number $INS") +sleep(3) +write_blocking(OUTS) +println("Finished blocking type writing device number $OUTS") + +start_read_callback(INS) +println("Started callback type reading device number $INS") +sleep(3) +ostream = start_write_callback(OUTS) +println("Started callback type writing device number $OUTS") +sleep(3) +AudioIO.Pa_CloseStream(ostream.stream) + +outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) + +# play the C major scale +scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6] +for note in scale + play_note(note, 0.5, 0.75, SRATE, outstream) +end + +# up an octave +for note in scale[2:8] + play_note(2*note, 0.5, 0.75, SRATE, outstream) +end From 3b37b356136b2e60f3ff33360ea3ad97c39c13c4 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 00:00:25 -1000 Subject: [PATCH 31/54] Update read_and_write.jl --- examples/read_and_write.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/read_and_write.jl b/examples/read_and_write.jl index 45ec7b0..fdd0af3 100644 --- a/examples/read_and_write.jl +++ b/examples/read_and_write.jl @@ -18,7 +18,6 @@ choose the devices for 2 channel IO """ function choose_input_output() devices = get_audio_devices() -@test length(devices) > 0 indev = -1 outdev = -1 for aud in devices @@ -39,8 +38,6 @@ function choose_input_output() " and output device ", bytestring(devices[outdev + 1].name), ", number ", devices[outdev + 1].device_index) end -@test indev >= 0 -@test outdev >= 0 return indev, outdev end From f50df4ad24957dec9b850b4e0a1afaebbeef43d7 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 13:11:48 -1000 Subject: [PATCH 32/54] Update portaudio.jl --- src/portaudio.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 6bc4e8e..07f9376 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -222,9 +222,11 @@ function write(ostream::Pa_AudioStream, buffer) sleep(0.001) end end_cur = cur + write_size - ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), ostream.stream, - buffer[cur + 1: end_cur], Culong(write_size/ostream.channels)) + err = ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), ostream.stream, + buffer[cur + 1: end_cur], + Culong(write_size/ostream.channels)) + handle_status(err, ostream.show_warnings) write_needed -= write_size cur = end_cur end From dba68ff99cfeb0b12cce9f3f7fc8e44d95ee4040 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 13:26:15 -1000 Subject: [PATCH 33/54] Update portaudio.jl --- src/portaudio.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 07f9376..81b91f7 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -274,7 +274,7 @@ Returns a C function of this type: """ function make_c_callback(jul_callback, sample_format::PaSampleFormat) datatype = PaSampleFormat_to_T(sample_format) - julia_callback = jul_callback + global julia_callback = jul_callback const c_callback = cfunction(callback, Cint, (Ptr{Void}, Ptr{Void}, Culong, Ptr{AudioIO.CCallbackTimeInfo}, From 696d0420e47ea25434776c1d58cbdd243eabec63 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 13:34:39 -1000 Subject: [PATCH 34/54] Update portaudio.jl --- src/portaudio.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/portaudio.jl b/src/portaudio.jl index 81b91f7..1ddd9b4 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -199,6 +199,10 @@ function read(stream::Pa_AudioStream, nframes::Int) while read_needed > 0 read_size = min(read_needed, stream.framesPerBuffer) used = read_size * stream.channels + while Pa_GetStreamReadAvailable(stream.stream) < read_size + sleep(0.001) + yield() + end err = ccall((:Pa_ReadStream, libportaudio), PaError, (PaStream, Ptr{Void}, Culong), stream.stream, buf, read_size) @@ -220,6 +224,7 @@ function write(ostream::Pa_AudioStream, buffer) write_size = min(write_needed, ostream.framesPerBuffer) while Pa_GetStreamWriteAvailable(ostream.stream) < write_size sleep(0.001) + yield() end end_cur = cur + write_size err = ccall((:Pa_WriteStream, libportaudio), PaError, From 7e56d86f69485997e08cac234dfa6f7bd9afc086 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 18:33:18 -1000 Subject: [PATCH 35/54] Update portaudio.jl --- src/portaudio.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 1ddd9b4..613acef 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -205,7 +205,7 @@ function read(stream::Pa_AudioStream, nframes::Int) end err = ccall((:Pa_ReadStream, libportaudio), PaError, (PaStream, Ptr{Void}, Culong), - stream.stream, buf, read_size) + stream.stream, buf, read_size) handle_status(err, stream.show_warnings) return_buffer[cur + 1: cur + used] = buf[1:used] cur = cur + used @@ -250,15 +250,18 @@ function callback(output::Ptr{Void}, input::Ptr{Void}, timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, sflags::Culong, udata::Ptr{Void}) global julia_callback + global callback_datatype if input # input stream - julia_callback(pointer_to_array(Ptr{datatype}(input), + julia_callback(pointer_to_array(Ptr{callback_datatype}(input), (frameCount, ))) else # output stream buf = julia_callback(0) - parray = Ptr{datatype}(buf) - obuf = pointer_to_array(parray, (frameCount, )) + bufsize = length(buf) + optr = Ptr{callback_datatype}(output) + oarray = unsafe_pointer_to_objref(optr) + oarray[1:bufsize] = buf[1:bufsize] end Cint(0) end @@ -278,7 +281,7 @@ Returns a C function of this type: void *userData) """ function make_c_callback(jul_callback, sample_format::PaSampleFormat) - datatype = PaSampleFormat_to_T(sample_format) + global callback_datatype = PaSampleFormat_to_T(sample_format) global julia_callback = jul_callback const c_callback = cfunction(callback, Cint, (Ptr{Void}, Ptr{Void}, From b14cd371ce95f9db3bc7c725b7a92a1a58c5f53f Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 18:34:05 -1000 Subject: [PATCH 36/54] Update read_and_write.jl --- examples/read_and_write.jl | 58 +++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/examples/read_and_write.jl b/examples/read_and_write.jl index fdd0af3..1945dea 100644 --- a/examples/read_and_write.jl +++ b/examples/read_and_write.jl @@ -10,8 +10,8 @@ CHANNELS = 2 SRATE = 44100 RECORD_SECONDS = 3 -BUFFER = zeros(Int16, SRATE * RECORD_SECONDS) -BUFSIZE = length(BUFFER) +BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 4) +BUFSIZE = SRATE * RECORD_SECONDS """ choose the devices for 2 channel IO @@ -21,7 +21,8 @@ function choose_input_output() indev = -1 outdev = -1 for aud in devices - if (aud.max_input_channels == CHANNELS) & (indev == -1) + println("$(aud.device_index) $(aud.name)") + if (aud.max_input_channels == CHANNELS) & (indev == -1 ) indev = aud.device_index end if(aud.max_output_channels == CHANNELS) & (outdev == -1) @@ -44,30 +45,34 @@ end """ read from input """ -function read_blocking(devnum) +function read_blocking(devnum, buffer) instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE) - BUFFER = AudioIO.read(instream, BUFSIZE) + buf = AudioIO.read(instream, BUFSIZE) + buflen = length(buf) + buffer[1: buflen] = buf[1: buflen] AudioIO.Pa_CloseStream(instream.stream) end """ write to output """ -function write_blocking(devnum) +function write_blocking(devnum, buffer) outstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE) - AudioIO.write(outstream, BUFFER) + AudioIO.write(outstream, buffer) AudioIO.Pa_CloseStream(outstream.stream) end +read_position = 0 +write_position = 0 """ read callback function """ -read_position = 0 -write_position = 0 function rcallback(buf) + println("In read callback") + global read_position read_size = length(buf) - if read_position + read_size > length(buffer) - read_size = length(buffer) - read_position + if read_position + read_size > length(BUFFER) + read_size = length(BUFFER) - read_position end if read_size > 1 BUFFER[read_position + 1 : read_position + read_size] = @@ -80,10 +85,12 @@ end """ write callback function """ -function wcallback(buf) +function wcallback(buffer) + println("In write callback") + global write_position write_size = CHUNKSIZE * CHANNELS - if write_position + write_size > length(buffer) - write_size = length(buffer) - write_position + if write_position + write_size > length(BUFFER) + write_size = length(BUFFER) - write_position end if write_size < 2 return Void @@ -91,6 +98,7 @@ function wcallback(buf) start_position = write_position + 1 write_position += write_size buf = BUFFER[start_position: write_position] + buf end """ @@ -111,7 +119,7 @@ function start_write_callback(devnum) AudioIO.make_c_callback(wcallback, FORMAT)) end -function play_note(frequency, amplitude, duration, srate, ostream) +function make_note_buffer(frequency, amplitude, duration, srate) N = round(Int, srate / frequency) T = round(Int, frequency * duration) # repeat for T cycles dt = 1.0 / srate @@ -123,23 +131,29 @@ function play_note(frequency, amplitude, duration, srate, ostream) tone[idx + 1] = tone[idx] idx += 2 end - AudioIO.write(ostream, tone) + tone +end + +function play_note(frequency, amplitude, duration, srate, ostream) + note = make_note_buffer(frequency, amplitude, duration, srate) + AudioIO.write(ostream, note) end INS, OUTS = choose_input_output() -read_blocking(INS) +read_blocking(INS, BUFFER) println("Finished blocking type reading device number $INS") +println("Recording volume is $(mean(abs(BUFFER))*(100/32767))% of max") sleep(3) -write_blocking(OUTS) +write_blocking(OUTS, BUFFER) println("Finished blocking type writing device number $OUTS") start_read_callback(INS) println("Started callback type reading device number $INS") -sleep(3) + +BUFFER = make_note_buffer(88.0, 0.4, 3, SRATE) ostream = start_write_callback(OUTS) println("Started callback type writing device number $OUTS") -sleep(3) AudioIO.Pa_CloseStream(ostream.stream) outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) @@ -147,10 +161,10 @@ outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) # play the C major scale scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6] for note in scale - play_note(note, 0.5, 0.75, SRATE, outstream) + play_note(note, 0.1, 0.75, SRATE, outstream) end # up an octave for note in scale[2:8] - play_note(2*note, 0.5, 0.75, SRATE, outstream) + play_note(2*note, 0.1, 0.75, SRATE, outstream) end From c309b955f93be30da6d4bda982832bac9eb264df Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 23:29:37 -1000 Subject: [PATCH 37/54] Update portaudio.jl --- src/portaudio.jl | 46 +--------------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 613acef..b68b5bd 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -103,7 +103,7 @@ function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, ioParameters = Pa_StreamParameters(device, channels, sampleFormat, PaTime(0.001), Ptr{Void}(0)) - streamcallback = Ptr{PaStreamCallback}(0) + streamcallback = Ptr{PaStreamCallback}(callback) if callback != 0 if input else @@ -245,50 +245,6 @@ type CCallbackTimeInfo end typealias PaStreamCallbackFlags Culong -function callback(output::Ptr{Void}, input::Ptr{Void}, - frameCount::Culong, - timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, - sflags::Culong, udata::Ptr{Void}) - global julia_callback - global callback_datatype - if input - # input stream - julia_callback(pointer_to_array(Ptr{callback_datatype}(input), - (frameCount, ))) - else - # output stream - buf = julia_callback(0) - bufsize = length(buf) - optr = Ptr{callback_datatype}(output) - oarray = unsafe_pointer_to_objref(optr) - oarray[1:bufsize] = buf[1:bufsize] - end - Cint(0) -end -""" -Callback helper function -takes a function to process the data when the callback occurs, -plus the number of frames to be passes each callback, -plus an input buffer which is 0 if for output and size framesPerBuffer -if for input. Function should return a buffer of framesPerBuffer frames -if for output, should process the input_buffer argument if for output - -Returns a C function of this type: - typedef int PaStreamCallback(const void *input, void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) -""" -function make_c_callback(jul_callback, sample_format::PaSampleFormat) - global callback_datatype = PaSampleFormat_to_T(sample_format) - global julia_callback = jul_callback - const c_callback = cfunction(callback, Cint, - (Ptr{Void}, Ptr{Void}, - Culong, Ptr{AudioIO.CCallbackTimeInfo}, - Culong, Ptr{Void})) - return c_callback -end ############ Internal Functions ############ From e719088c3cdad17911cf8298568b9ecd23df2f54 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 22 Nov 2015 23:30:48 -1000 Subject: [PATCH 38/54] Update read_and_write.jl --- examples/read_and_write.jl | 70 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/examples/read_and_write.jl b/examples/read_and_write.jl index 1945dea..58e083d 100644 --- a/examples/read_and_write.jl +++ b/examples/read_and_write.jl @@ -6,6 +6,7 @@ using AudioIO CHUNKSIZE = 40960 FORMAT = AudioIO.paInt16 +CALLBACK_DATATYPE = AudioIO.PaSampleFormat_to_T(FORMAT) CHANNELS = 2 SRATE = 44100 RECORD_SECONDS = 3 @@ -63,60 +64,67 @@ function write_blocking(devnum, buffer) end read_position = 0 -write_position = 0 -""" -read callback function -""" -function rcallback(buf) - println("In read callback") +function read_callback(input::Ptr{Void}, output::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, + sflags::Culong, udata::Ptr{Void}) global read_position + buf = pointer_to_array(Ptr{CALLBACK_DATATYPE}(input), + (frameCount * CHANNELS, )) read_size = length(buf) if read_position + read_size > length(BUFFER) read_size = length(BUFFER) - read_position end - if read_size > 1 + if read_size > 0 BUFFER[read_position + 1 : read_position + read_size] = buf[1: read_size] read_position += read_size end 0 end - -""" -write callback function -""" -function wcallback(buffer) - println("In write callback") + +write_position = 0 +function write_callback(input::Ptr{Void}, output::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, + sflags::Culong, udata::Ptr{Void}) global write_position - write_size = CHUNKSIZE * CHANNELS + write_size = frameCount * CHANNELS if write_position + write_size > length(BUFFER) - write_size = length(BUFFER) - write_position - end - if write_size < 2 - return Void + write_position = length(BUFFER) - write_size end start_position = write_position + 1 write_position += write_size buf = BUFFER[start_position: write_position] - buf + ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, Cint), + output, buf, write_size * 2) + 0 end +const r_c_callback = cfunction(read_callback, Cint, (Ptr{Void}, + Ptr{Void}, + Culong, Ptr{AudioIO.CCallbackTimeInfo}, + Culong, Ptr{Void})) + +const w_c_callback = cfunction(write_callback, Cint, (Ptr{Void}, + Ptr{Void}, + Culong, Ptr{AudioIO.CCallbackTimeInfo}, + Culong, Ptr{Void})) + """ read using callback """ function start_read_callback(devnum) - AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE, - false, FORMAT, - AudioIO.make_c_callback(rcallback, FORMAT)) + rstream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, r_c_callback) end """ write using callback """ function start_write_callback(devnum) - AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE, - false, FORMAT, - AudioIO.make_c_callback(wcallback, FORMAT)) + wstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, w_c_callback) end function make_note_buffer(frequency, amplitude, duration, srate) @@ -143,27 +151,29 @@ INS, OUTS = choose_input_output() read_blocking(INS, BUFFER) println("Finished blocking type reading device number $INS") -println("Recording volume is $(mean(abs(BUFFER))*(100/32767))% of max") -sleep(3) +println("Recording volume is $(mean(abs(BUFFER))*(100/16783))% of max") +sleep(2) + write_blocking(OUTS, BUFFER) println("Finished blocking type writing device number $OUTS") -start_read_callback(INS) +istream = start_read_callback(INS) println("Started callback type reading device number $INS") +sleep(3) +AudioIO.Pa_CloseStream(istream.stream) BUFFER = make_note_buffer(88.0, 0.4, 3, SRATE) ostream = start_write_callback(OUTS) println("Started callback type writing device number $OUTS") +sleep(3) AudioIO.Pa_CloseStream(ostream.stream) outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) - # play the C major scale scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6] for note in scale play_note(note, 0.1, 0.75, SRATE, outstream) end - # up an octave for note in scale[2:8] play_note(2*note, 0.1, 0.75, SRATE, outstream) From d67cda8504198b12e2309226a5db3289b79d6f44 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 23 Nov 2015 19:23:16 -1000 Subject: [PATCH 39/54] Update portaudio.jl --- src/portaudio.jl | 502 ++++++++--------------------------------------- 1 file changed, 77 insertions(+), 425 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index b68b5bd..5f27098 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -1,458 +1,110 @@ -typealias PaTime Cdouble -typealias PaError Cint -typealias PaSampleFormat Culong -# PaStream is always used as an opaque type, so we're always dealing -# with the pointer -typealias PaStream Ptr{Void} -typealias PaDeviceIndex Cint -typealias PaHostApiIndex Cint -typealias PaTime Cdouble -typealias PaHostApiTypeId Cint -typealias PaStreamCallback Void -typealias PaStreamFlags Culong +# -*- coding: utf-8 -*- -const PA_NO_ERROR = 0 -const PA_INPUT_OVERFLOWED = -10000 + 19 -const PA_OUTPUT_UNDERFLOWED = -10000 + 20 +# Examples for Julia AudioIO module -const paFloat32 = convert(PaSampleFormat, 0x01) -const paInt32 = convert(PaSampleFormat, 0x02) -const paInt24 = convert(PaSampleFormat, 0x04) -const paInt16 = convert(PaSampleFormat, 0x08) -const paInt8 = convert(PaSampleFormat, 0x10) -const paUInt8 = convert(PaSampleFormat, 0x20) +using AudioIO -# PaHostApiTypeId values -@compat const pa_host_api_names = ( - 0 => "In Development", # use while developing support for a new host API - 1 => "Direct Sound", - 2 => "MME", - 3 => "ASIO", - 4 => "Sound Manager", - 5 => "Core Audio", - 7 => "OSS", - 8 => "ALSA", - 9 => "AL", - 10 => "BeOS", - 11 => "WDMKS", - 12 => "Jack", - 13 => "WASAPI", - 14 => "AudioScience HPI" -) +CHUNKSIZE = 40960 +FORMAT = AudioIO.paInt16 +CHANNELS = 2 +SRATE = 44100 +RECORD_SECONDS = 3 -# track whether we've already inited PortAudio -portaudio_inited = false - -################## Types #################### - -type PortAudioStream <: AudioStream - root::AudioMixer - info::DeviceInfo - show_warnings::Bool - stream::PaStream - - function PortAudioStream(sample_rate::Integer=44100, - buf_size::Integer=1024, - show_warnings::Bool=false) - require_portaudio_init() - stream = Pa_OpenDefaultStream(1, 1, paFloat32, sample_rate, buf_size) - Pa_StartStream(stream) - root = AudioMixer() - this = new(root, DeviceInfo(sample_rate, buf_size), - show_warnings, stream) - info("Scheduling PortAudio Render Task...") - # the task will actually start running the next time the current task yields - @schedule(portaudio_task(this)) - finalizer(this, destroy) - - this - end -end - -function destroy(stream::PortAudioStream) - # in 0.3 we can't print from a finalizer, as STDOUT may have been GC'ed - # already and we get a segfault. See - # https://github.com/JuliaLang/julia/issues/6075 - #info("Cleaning up stream") - Pa_StopStream(stream.stream) - Pa_CloseStream(stream.stream) - # we only have 1 stream at a time, so if we're closing out we can just - # terminate PortAudio. - Pa_Terminate() - portaudio_inited = false -end - -type Pa_StreamParameters - device::PaDeviceIndex - channelCount::Cint - sampleFormat::PaSampleFormat - suggestedLatency::PaTime - hostAPISpecificStreamInfo::Ptr{Void} -end +BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 4) +BUFSIZE = SRATE * RECORD_SECONDS """ - Open a single stream, not necessarily the default one - The stream is unidirectional, either input or default output - see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html +choose the devices for 2 channel IO """ -function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, - input::Bool, sampleFormat::PaSampleFormat, - sampleRate::Cdouble, framesPerBuffer::Culong, - callback) - streamPtr::Array{PaStream} = PaStream[1] - ioParameters = Pa_StreamParameters(device, channels, - sampleFormat, PaTime(0.001), - Ptr{Void}(0)) - streamcallback = Ptr{PaStreamCallback}(callback) - if callback != 0 - if input - else +function choose_input_output() + devices = get_audio_devices() + indev = -1 + outdev = -1 + for aud in devices + println("$(aud.device_index) $(aud.name)") + if (aud.max_input_channels == CHANNELS) & (indev == -1 ) + indev = aud.device_index + end + if(aud.max_output_channels == CHANNELS) & (outdev == -1) + outdev = aud.device_index end end - if input - err = ccall((:Pa_OpenStream, libportaudio), PaError, - (PaStream, Ref{Pa_StreamParameters}, Ptr{Void}, - Cdouble, Culong, Culong, - Ptr{PaStreamCallback}, Ptr{Void}), - streamPtr, ioParameters, Ptr{Void}(0), - sampleRate, framesPerBuffer, 0, - streamcallback, Ptr{Void}(0)) + if indev == -1 + info("Appropriate input device not found.") + elseif outdev == -1 + info("Appropriate output device not found.") else - err = ccall((:Pa_OpenStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Ref{Pa_StreamParameters}, - Cdouble, Culong, Culong, - Ptr{PaStreamCallback}, Ptr{Void}), - streamPtr, Ptr{Void}(0), ioParameters, - sampleRate, framesPerBuffer, 0, - streamcallback, Ptr{Void}(0)) - end - handle_status(err) - streamPtr[1] -end - -type Pa_AudioStream <: AudioStream - root::AudioMixer - info::DeviceInfo - show_warnings::Bool - stream::PaStream - sformat::PaSampleFormat - channels::Integer - framesPerBuffer::Integer - - """ - Get device parameters needed for opening with portaudio - default is output as 44100/16bit int, same as CD audio type input - callback is optional, otherwise use blocing read and write functions - the callback must be a function that, if for writing, can return as an argument - a buffer the size and type same as stream.sbuffer, or if for output should - """ - function Pa_AudioStream(device_index, channels=2, input=false, - sample_rate::Integer=44100, - framesPerBuffer::Integer=2048, - show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16, - callback=0) - require_portaudio_init() - stream = Pa_OpenStream(device_index, channels, input, sample_format, - Cdouble(sample_rate), Culong(framesPerBuffer), - callback) - Pa_StartStream(stream) - root = AudioMixer() - this = new(root, DeviceInfo(sample_rate, framesPerBuffer), - show_warnings, stream, sample_format, - channels, framesPerBuffer) + info("Using input device ", bytestring(devices[indev + 1].name), + ", number ", devices[indev + 1].device_index, + " and output device ", bytestring(devices[outdev + 1].name), + ", number ", devices[outdev + 1].device_index) end + return indev, outdev end -function open_read(device_index, channels=2, - sample_rate::Integer=44100, - framesPerBuffer::Integer=2048, - show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16, - callback=0) - Pa_AudioStream(device_index, channels, true, sample_rate, - framesPerBuffer, show_warnings, - sample_format, callback) -end - -function open_write(device_index, channels=2, - sample_rate::Integer=44100, - framesPerBuffer::Integer=2048, - show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16, - callback=0) - Pa_AudioStream(device_index, channels, false, sample_rate, - framesPerBuffer, show_warnings, - sample_format, callback) -end - - """ -Blocking read from a Pa_AudioStream that is open as input +read from input """ -function read(stream::Pa_AudioStream, nframes::Int) - datatype = PaSampleFormat_to_T(stream.sformat) - buf = zeros(datatype, stream.framesPerBuffer * stream.channels) - return_buffer = zeros(datatype, nframes * stream.channels) - cur = 0 - read_needed = nframes - while read_needed > 0 - read_size = min(read_needed, stream.framesPerBuffer) - used = read_size * stream.channels - while Pa_GetStreamReadAvailable(stream.stream) < read_size - sleep(0.001) - yield() - end - err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), - stream.stream, buf, read_size) - handle_status(err, stream.show_warnings) - return_buffer[cur + 1: cur + used] = buf[1:used] - cur = cur + used - read_needed -= read_size - end - return_buffer +function read_blocking(devnum, buffer) + instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE) + buf = AudioIO.read(instream, BUFSIZE) + buflen = length(buf) + buffer[1: buflen] = buf[1: buflen] + AudioIO.Pa_CloseStream(instream.stream) end """ -Blocking write to a Pa_AudioStream that is open for output +write to output """ -function write(ostream::Pa_AudioStream, buffer) - cur = 0 - write_needed = length(buffer) - while write_needed > 0 - write_size = min(write_needed, ostream.framesPerBuffer) - while Pa_GetStreamWriteAvailable(ostream.stream) < write_size - sleep(0.001) - yield() - end - end_cur = cur + write_size - err = ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), ostream.stream, - buffer[cur + 1: end_cur], - Culong(write_size/ostream.channels)) - handle_status(err, ostream.show_warnings) - write_needed -= write_size - cur = end_cur - end - 0 -end - -type CCallbackTimeInfo - inAdc::Cdouble - current::Cdouble - outAdc::Cdouble -end -typealias PaStreamCallbackFlags Culong - - -############ Internal Functions ############ - -function portaudio_task(stream::PortAudioStream) - info("PortAudio Render Task Running...") - n = bufsize(stream) - buffer = zeros(AudioSample, n) - try - while true - while Pa_GetStreamReadAvailable(stream.stream) < n - sleep(0.005) - end - Pa_ReadStream(stream.stream, buffer, n, stream.show_warnings) - # assume the root is always active - rendered = render(stream.root.renderer, buffer, stream.info)::AudioBuf - for i in 1:length(rendered) - buffer[i] = rendered[i] - end - for i in (length(rendered)+1):n - buffer[i] = 0.0 - end - while Pa_GetStreamWriteAvailable(stream.stream) < n - sleep(0.005) - end - Pa_WriteStream(stream.stream, buffer, n, stream.show_warnings) - end - catch ex - warn("Audio Task died with exception: $ex") - Base.show_backtrace(STDOUT, catch_backtrace()) - end +function write_blocking(devnum, buffer) + outstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE) + AudioIO.write(outstream, buffer) + AudioIO.Pa_CloseStream(outstream.stream) end """ -Helper function to make the right type of buffer for various -sample formats. Converts PaSampleFormat to a typeof +Create a string of numbers representing a sinewave audio tone """ -function PaSampleFormat_to_T(fmt::PaSampleFormat) - retval = UInt8(0x0) - if fmt == 1 - retval = Float32(1.0) - elseif fmt == 2 - retval = Int32(0x02) - elseif fmt == 4 - retval = Int24(0x04) - elseif fmt == 8 - retval = Int16(0x08) - elseif fmt == 16 - retval = Int8(0x10) - elseif fmt == 32 - retval = UInt8(0x20) - else - info("Flawed input to PaSampleFormat_to_T, primitive unknown") - end - typeof(retval) -end - -type PaDeviceInfo - struct_version::Cint - name::Ptr{Cchar} - host_api::PaHostApiIndex - max_input_channels::Cint - max_output_channels::Cint - default_low_input_latency::PaTime - default_low_output_latency::PaTime - default_high_input_latency::PaTime - default_high_output_latency::PaTime - default_sample_rate::Cdouble -end - -type PaHostApiInfo - struct_version::Cint - api_type::PaHostApiTypeId - name::Ptr{Cchar} - deviceCount::Cint - defaultInputDevice::PaDeviceIndex - defaultOutputDevice::PaDeviceIndex -end - -@compat type PortAudioInterface <: AudioInterface - name::AbstractString - host_api::AbstractString - max_input_channels::Int - max_output_channels::Int - device_index::PaDeviceIndex -end - -function get_portaudio_devices() - require_portaudio_init() - device_count = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) - pa_devices = [ [Pa_GetDeviceInfo(i), i] for i in 0:(device_count - 1)] - [PortAudioInterface(bytestring(d[1].name), - bytestring(Pa_GetHostApiInfo(d[1].host_api).name), - d[1].max_input_channels, - d[1].max_output_channels, - d[2]) - for d in pa_devices] -end - -function require_portaudio_init() - # can be called multiple times with no effect - global portaudio_inited - if !portaudio_inited - info("Initializing PortAudio. Expect errors as we scan devices") - Pa_Initialize() - portaudio_inited = true +function make_note_buffer(frequency, amplitude, duration, srate) + N = round(Int, srate / frequency) + T = round(Int, frequency * duration) # repeat for T cycles + dt = 1.0 / srate + tone = zeros(Int16, (N + N) * T) + idx = 1 + while idx < (N + N) * T + tone[idx] = round(Int, amplitude * sin(2 * pi * frequency * + idx * dt) * 32767.0) + tone[idx + 1] = tone[idx] + idx += 2 end + tone end -# Low-level wrappers for Portaudio calls -Pa_GetDeviceInfo(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), - Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) -Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), - Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) - -function Pa_Initialize() - err = ccall((:Pa_Initialize, libportaudio), PaError, ()) - handle_status(err) -end - -function Pa_Terminate() - err = ccall((:Pa_Terminate, libportaudio), PaError, ()) - handle_status(err) -end - -function Pa_StartStream(stream::PaStream) - err = ccall((:Pa_StartStream, libportaudio), PaError, - (PaStream,), stream) - handle_status(err) -end - -function Pa_StopStream(stream::PaStream) - err = ccall((:Pa_StopStream, libportaudio), PaError, - (PaStream,), stream) - handle_status(err) -end - -function Pa_CloseStream(stream::PaStream) - err = ccall((:Pa_CloseStream, libportaudio), PaError, - (PaStream,), stream) - handle_status(err) -end - -function Pa_GetStreamReadAvailable(stream::PaStream) - avail = ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, - (PaStream,), stream) - avail >= 0 || handle_status(avail) - avail -end - -function Pa_GetStreamWriteAvailable(stream::PaStream) - avail = ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, - (PaStream,), stream) - avail >= 0 || handle_status(avail) - avail -end - -function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf), - show_warnings::Bool=true) - frames <= length(buf) || error("Need a buffer at least $frames long") - err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), - stream, buf, frames) - handle_status(err, show_warnings) - buf -end - -function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf), - show_warnings::Bool=true) - frames <= length(buf) || error("Need a buffer at least $frames long") - err = ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), - stream, buf, frames) - handle_status(err, show_warnings) - nothing +""" +Write a note to output device +""" +function play_note(frequency, amplitude, duration, srate, ostream) + note = make_note_buffer(frequency, amplitude, duration, srate) + AudioIO.write(ostream, note) end -Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ()) +INS, OUTS = choose_input_output() -function Pa_GetVersionText() - versionPtr = ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) - bytestring(versionPtr) -end +read_blocking(INS, BUFFER) +println("Finished blocking type reading device number $INS") +println("Recording volume is $(mean(abs(BUFFER))*(100/16783))% of max") +sleep(2) -function Pa_OpenDefaultStream(inChannels::Integer, outChannels::Integer, - sampleFormat::PaSampleFormat, - sampleRate::Real, framesPerBuffer::Integer) - streamPtr::Array{PaStream} = PaStream[0] - err = ccall((:Pa_OpenDefaultStream, libportaudio), - PaError, (Ptr{PaStream}, Cint, Cint, - PaSampleFormat, Cdouble, Culong, - Ptr{PaStreamCallback}, Ptr{Void}), - streamPtr, inChannels, outChannels, sampleFormat, sampleRate, - framesPerBuffer, 0, 0) - handle_status(err) +write_blocking(OUTS, BUFFER) +println("Finished blocking type writing device number $OUTS") - streamPtr[1] +outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) +# play the C major scale +scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6] +for note in scale + play_note(note, 0.1, 0.75, SRATE, outstream) end - -function handle_status(err::PaError, show_warnings::Bool=true) - if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED - if show_warnings - msg = ccall((:Pa_GetErrorText, libportaudio), - Ptr{Cchar}, (PaError,), err) - warn("libportaudio: " * bytestring(msg)) - end - elseif err != PA_NO_ERROR - msg = ccall((:Pa_GetErrorText, libportaudio), - Ptr{Cchar}, (PaError,), err) - error("libportaudio: " * bytestring(msg)) - end +# up an octave +for note in scale[2:8] + play_note(2*note, 0.1, 0.75, SRATE, outstream) end From f9c0b5068545cd3abfef3bb840a969ba0d755c58 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 23 Nov 2015 19:24:12 -1000 Subject: [PATCH 40/54] Update read_and_write.jl --- examples/read_and_write.jl | 78 ++------------------------------------ 1 file changed, 4 insertions(+), 74 deletions(-) diff --git a/examples/read_and_write.jl b/examples/read_and_write.jl index 58e083d..5f27098 100644 --- a/examples/read_and_write.jl +++ b/examples/read_and_write.jl @@ -6,7 +6,6 @@ using AudioIO CHUNKSIZE = 40960 FORMAT = AudioIO.paInt16 -CALLBACK_DATATYPE = AudioIO.PaSampleFormat_to_T(FORMAT) CHANNELS = 2 SRATE = 44100 RECORD_SECONDS = 3 @@ -63,70 +62,9 @@ function write_blocking(devnum, buffer) AudioIO.Pa_CloseStream(outstream.stream) end -read_position = 0 -function read_callback(input::Ptr{Void}, output::Ptr{Void}, - frameCount::Culong, - timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, - sflags::Culong, udata::Ptr{Void}) - global read_position - buf = pointer_to_array(Ptr{CALLBACK_DATATYPE}(input), - (frameCount * CHANNELS, )) - read_size = length(buf) - if read_position + read_size > length(BUFFER) - read_size = length(BUFFER) - read_position - end - if read_size > 0 - BUFFER[read_position + 1 : read_position + read_size] = - buf[1: read_size] - read_position += read_size - end - 0 -end - -write_position = 0 -function write_callback(input::Ptr{Void}, output::Ptr{Void}, - frameCount::Culong, - timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, - sflags::Culong, udata::Ptr{Void}) - global write_position - write_size = frameCount * CHANNELS - if write_position + write_size > length(BUFFER) - write_position = length(BUFFER) - write_size - end - start_position = write_position + 1 - write_position += write_size - buf = BUFFER[start_position: write_position] - ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, Cint), - output, buf, write_size * 2) - 0 -end - -const r_c_callback = cfunction(read_callback, Cint, (Ptr{Void}, - Ptr{Void}, - Culong, Ptr{AudioIO.CCallbackTimeInfo}, - Culong, Ptr{Void})) - -const w_c_callback = cfunction(write_callback, Cint, (Ptr{Void}, - Ptr{Void}, - Culong, Ptr{AudioIO.CCallbackTimeInfo}, - Culong, Ptr{Void})) - """ -read using callback +Create a string of numbers representing a sinewave audio tone """ -function start_read_callback(devnum) - rstream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE, - false, FORMAT, r_c_callback) -end - -""" -write using callback -""" -function start_write_callback(devnum) - wstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE, - false, FORMAT, w_c_callback) -end - function make_note_buffer(frequency, amplitude, duration, srate) N = round(Int, srate / frequency) T = round(Int, frequency * duration) # repeat for T cycles @@ -142,6 +80,9 @@ function make_note_buffer(frequency, amplitude, duration, srate) tone end +""" +Write a note to output device +""" function play_note(frequency, amplitude, duration, srate, ostream) note = make_note_buffer(frequency, amplitude, duration, srate) AudioIO.write(ostream, note) @@ -157,17 +98,6 @@ sleep(2) write_blocking(OUTS, BUFFER) println("Finished blocking type writing device number $OUTS") -istream = start_read_callback(INS) -println("Started callback type reading device number $INS") -sleep(3) -AudioIO.Pa_CloseStream(istream.stream) - -BUFFER = make_note_buffer(88.0, 0.4, 3, SRATE) -ostream = start_write_callback(OUTS) -println("Started callback type writing device number $OUTS") -sleep(3) -AudioIO.Pa_CloseStream(ostream.stream) - outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) # play the C major scale scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6] From 3e5fc73fa0e6d5b2399e86b9584ae54b8e5ce731 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 23 Nov 2015 19:29:40 -1000 Subject: [PATCH 41/54] Create callback_read_write.jl --- examples/callback_read_write.jl | 135 ++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 examples/callback_read_write.jl diff --git a/examples/callback_read_write.jl b/examples/callback_read_write.jl new file mode 100644 index 0000000..bb7ca0b --- /dev/null +++ b/examples/callback_read_write.jl @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +# Examples for Julia AudioIO module + +using AudioIO + +CHUNKSIZE = 40960 +FORMAT = AudioIO.paInt16 +CALLBACK_DATATYPE = AudioIO.PaSampleFormat_to_T(FORMAT) +CHANNELS = 2 +SRATE = 44100 +RECORD_SECONDS = 3 + +BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 4) +BUFSIZE = SRATE * RECORD_SECONDS + +""" +choose the devices for 2 channel IO +""" +function choose_input_output() + devices = get_audio_devices() + indev = -1 + outdev = -1 + for aud in devices + println("$(aud.device_index) $(aud.name)") + if (aud.max_input_channels == CHANNELS) & (indev == -1 ) + indev = aud.device_index + end + if(aud.max_output_channels == CHANNELS) & (outdev == -1) + outdev = aud.device_index + end + end + if indev == -1 + info("Appropriate input device not found.") + elseif outdev == -1 + info("Appropriate output device not found.") + else + info("Using input device ", bytestring(devices[indev + 1].name), + ", number ", devices[indev + 1].device_index, + " and output device ", bytestring(devices[outdev + 1].name), + ", number ", devices[outdev + 1].device_index) + end + return indev, outdev +end + +read_position = 0 +""" +Custom callback for read +Must process frameCount frames from the input buffer +Return 0 to ask for more data, 1 to ask for no more callbacks +""" +function store_read(input::Ptr{Void}, output::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, + sflags::Culong, udata::Ptr{Void}) + retval = 0 + buf = pointer_to_array(Ptr{CALLBACK_DATATYPE}(input), + (frameCount * CHANNELS, )) + read_size = length(buf) + global read_position + if read_position + read_size > length(BUFFER) + read_size = length(BUFFER) - read_position + end + if read_size > 0 + BUFFER[read_position + 1 : read_position + read_size] = + buf[1: read_size] + read_position += read_size + else + retval = 1 + end + Cint(retval) +end + +write_position = 0 +""" +Custom callback for write +Must write frameCount frames to the output buffer +Return 0 to continue to write data, 1 to ask for no more callbacks +""" +function get_writeable(input::Ptr{Void}, output::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, + sflags::Culong, udata::Ptr{Void}) + retval = 0 + write_size = frameCount * CHANNELS + global write_position + if write_position + write_size > length(BUFFER) + write_position = length(BUFFER) - write_size + retval = 1 + end + start_position = write_position + 1 + write_position += write_size + buf = BUFFER[start_position: write_position] + ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, Cint), + output, buf, length(buf) * sizeof(buf[1])) + Cint(retval) +end + +const rcallback = cfunction(store_read, Cint, (Ptr{Void}, + Ptr{Void}, + Culong, Ptr{AudioIO.CCallbackTimeInfo}, + Culong, Ptr{Void})) + +const wcallback = cfunction(get_writeable, Cint, (Ptr{Void}, + Ptr{Void}, + Culong, Ptr{AudioIO.CCallbackTimeInfo}, + Culong, Ptr{Void})) + +""" +Start read using callback +""" +function start_read_callback(devnum) + rstream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, rcallback) +end + +""" +Start write using callback +""" +function start_write_callback(devnum) + wstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, wcallback) +end + +INS, OUTS = choose_input_output() + +istream = start_read_callback(INS) +println("Started callback type reading device number $INS") +sleep(5) +AudioIO.Pa_CloseStream(istream.stream) + +ostream = start_write_callback(OUTS) +println("Started callback type writing device number $OUTS") +sleep(3) +AudioIO.Pa_CloseStream(ostream.stream) From a77195867af5666fa7ade2d2f1123d2c2fed0e85 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 23 Nov 2015 19:35:35 -1000 Subject: [PATCH 42/54] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f245c8..b822d82 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,8 @@ for streams opened for reading and writing, respectively. If nonblocking I/O is needed, a callback may be passed to the open_read or open_write methods, which is provided by the user and should return a buffer of frames if used for output and should -handle a buffer of frames as an argument if used as input. +handle a buffer of frames as an argument if used as input, as shown in the examples folder in +this package. Installation ------------ From 78230b1427e2c66ac913fb1a47a95e487ef5026f Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 24 Nov 2015 21:57:19 -1000 Subject: [PATCH 43/54] Update README.md --- README.md | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b822d82..1175182 100644 --- a/README.md +++ b/README.md @@ -115,26 +115,21 @@ sound card, the stream calls the `render` method on the root audio mixer, which will in turn call the `render` methods on any input AudioNodes that are set up as inputs. -For opening an audio device for either reading or writing but not both, the functions -open_read(device_index, channels=2, sample_rate::Integer=44100, - framesPerBuffer::Integer=2048, show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16, callback=0) - -and -open_write(device_index, channels=2, sample_rate::Integer=44100, - framesPerBuffer::Integer=2048, show_warnings::Bool=false, - sample_format::PaSampleFormat=paInt16, callback=0) - -are provided. These return a Pa_Audiostream which can be used with the blocking I/O functions -read(stream::Pa_AudioStream, nframes::Int) (returns an array of frames) -and -write(stream::Pa_AudioStream, buffer) -for streams opened for reading and writing, respectively. - -If nonblocking I/O is needed, a callback may be passed to the open_read or open_write methods, +When a Pa_AudioStream is created via a call to +Pa_AudioStream(device_index, channels=2, input=false, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0) +a single stream is opened for reading or writing. This returns a Pa_AudioStream on success, +which is used either via the functions AudioIO.read(::Pa_AudioStream) or +AudioIO.write(::Pa_AudioStream, buffer) for read and write respectively. + +If nonblocking I/O is needed, a callback may be passed to the Pa_AudioStream constructor, which is provided by the user and should return a buffer of frames if used for output and should -handle a buffer of frames as an argument if used as input, as shown in the examples folder in -this package. +handle a buffer of frames as an argument if used as input. This is similar +to the methods used by PyAudio's python interface to PortAudio. See the examples also. Installation ------------ From 19447767e6321fafdb9e6f8c686b610affaf4cd6 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 24 Nov 2015 21:58:16 -1000 Subject: [PATCH 44/54] Update callback_read_write.jl --- examples/callback_read_write.jl | 63 ++++++--------------------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/examples/callback_read_write.jl b/examples/callback_read_write.jl index bb7ca0b..ca7fa42 100644 --- a/examples/callback_read_write.jl +++ b/examples/callback_read_write.jl @@ -45,17 +45,9 @@ end read_position = 0 """ -Custom callback for read -Must process frameCount frames from the input buffer -Return 0 to ask for more data, 1 to ask for no more callbacks +Custom callback for read, processes passed data array """ -function store_read(input::Ptr{Void}, output::Ptr{Void}, - frameCount::Culong, - timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, - sflags::Culong, udata::Ptr{Void}) - retval = 0 - buf = pointer_to_array(Ptr{CALLBACK_DATATYPE}(input), - (frameCount * CHANNELS, )) +function store_read(buf) read_size = length(buf) global read_position if read_position + read_size > length(BUFFER) @@ -65,24 +57,15 @@ function store_read(input::Ptr{Void}, output::Ptr{Void}, BUFFER[read_position + 1 : read_position + read_size] = buf[1: read_size] read_position += read_size - else - retval = 1 end - Cint(retval) end write_position = 0 """ -Custom callback for write -Must write frameCount frames to the output buffer -Return 0 to continue to write data, 1 to ask for no more callbacks +Custom callback for write, returns data array """ -function get_writeable(input::Ptr{Void}, output::Ptr{Void}, - frameCount::Culong, - timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, - sflags::Culong, udata::Ptr{Void}) - retval = 0 - write_size = frameCount * CHANNELS +function get_writeable() + write_size = CHUNKSIZE * CHANNELS global write_position if write_position + write_size > length(BUFFER) write_position = length(BUFFER) - write_size @@ -91,45 +74,19 @@ function get_writeable(input::Ptr{Void}, output::Ptr{Void}, start_position = write_position + 1 write_position += write_size buf = BUFFER[start_position: write_position] - ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, Cint), - output, buf, length(buf) * sizeof(buf[1])) - Cint(retval) -end - -const rcallback = cfunction(store_read, Cint, (Ptr{Void}, - Ptr{Void}, - Culong, Ptr{AudioIO.CCallbackTimeInfo}, - Culong, Ptr{Void})) - -const wcallback = cfunction(get_writeable, Cint, (Ptr{Void}, - Ptr{Void}, - Culong, Ptr{AudioIO.CCallbackTimeInfo}, - Culong, Ptr{Void})) - -""" -Start read using callback -""" -function start_read_callback(devnum) - rstream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE, - false, FORMAT, rcallback) -end - -""" -Start write using callback -""" -function start_write_callback(devnum) - wstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE, - false, FORMAT, wcallback) + buf end INS, OUTS = choose_input_output() -istream = start_read_callback(INS) +istream = AudioIO.open_read(INS, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, store_read) println("Started callback type reading device number $INS") sleep(5) AudioIO.Pa_CloseStream(istream.stream) -ostream = start_write_callback(OUTS) +ostream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, get_writeable) println("Started callback type writing device number $OUTS") sleep(3) AudioIO.Pa_CloseStream(ostream.stream) From e54ecba511dfdb3e2845b7d729caf3b87b50c3a8 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 24 Nov 2015 21:59:42 -1000 Subject: [PATCH 45/54] Update portaudio.jl --- src/portaudio.jl | 535 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 458 insertions(+), 77 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 5f27098..33fc3d2 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -1,110 +1,491 @@ -# -*- coding: utf-8 -*- +typealias PaTime Cdouble +typealias PaError Cint +typealias PaSampleFormat Culong +# PaStream is always used as an opaque type, so we're always dealing +# with the pointer +typealias PaStream Ptr{Void} +typealias PaDeviceIndex Cint +typealias PaHostApiIndex Cint +typealias PaTime Cdouble +typealias PaHostApiTypeId Cint +typealias PaStreamCallback Void +typealias PaStreamFlags Culong -# Examples for Julia AudioIO module +const PA_NO_ERROR = 0 +const PA_INPUT_OVERFLOWED = -10000 + 19 +const PA_OUTPUT_UNDERFLOWED = -10000 + 20 -using AudioIO +const paFloat32 = convert(PaSampleFormat, 0x01) +const paInt32 = convert(PaSampleFormat, 0x02) +const paInt24 = convert(PaSampleFormat, 0x04) +const paInt16 = convert(PaSampleFormat, 0x08) +const paInt8 = convert(PaSampleFormat, 0x10) +const paUInt8 = convert(PaSampleFormat, 0x20) -CHUNKSIZE = 40960 -FORMAT = AudioIO.paInt16 -CHANNELS = 2 -SRATE = 44100 -RECORD_SECONDS = 3 +# PaHostApiTypeId values +@compat const pa_host_api_names = ( + 0 => "In Development", # use while developing support for a new host API + 1 => "Direct Sound", + 2 => "MME", + 3 => "ASIO", + 4 => "Sound Manager", + 5 => "Core Audio", + 7 => "OSS", + 8 => "ALSA", + 9 => "AL", + 10 => "BeOS", + 11 => "WDMKS", + 12 => "Jack", + 13 => "WASAPI", + 14 => "AudioScience HPI" +) -BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 4) -BUFSIZE = SRATE * RECORD_SECONDS +# track whether we've already inited PortAudio +portaudio_inited = false + +################## Types #################### + +type PortAudioStream <: AudioStream + root::AudioMixer + info::DeviceInfo + show_warnings::Bool + stream::PaStream + + function PortAudioStream(sample_rate::Integer=44100, + buf_size::Integer=1024, + show_warnings::Bool=false) + require_portaudio_init() + stream = Pa_OpenDefaultStream(1, 1, paFloat32, sample_rate, buf_size) + Pa_StartStream(stream) + root = AudioMixer() + this = new(root, DeviceInfo(sample_rate, buf_size), + show_warnings, stream) + info("Scheduling PortAudio Render Task...") + # the task will actually start running the next time the current task yields + @schedule(portaudio_task(this)) + finalizer(this, destroy) + + this + end +end + +function destroy(stream::PortAudioStream) + # in 0.3 we can't print from a finalizer, as STDOUT may have been GC'ed + # already and we get a segfault. See + # https://github.com/JuliaLang/julia/issues/6075 + #info("Cleaning up stream") + Pa_StopStream(stream.stream) + Pa_CloseStream(stream.stream) + # we only have 1 stream at a time, so if we're closing out we can just + # terminate PortAudio. + Pa_Terminate() + portaudio_inited = false +end + +type CCallbackTimeInfo + inAdc::Cdouble + current::Cdouble + outAdc::Cdouble +end + +typealias PaStreamCallbackFlags Culong + +type CCallbackUdata + func::Function + sample_format::PaSampleFormat + channels::Cint + is_read::Bool +end + +type Pa_StreamParameters + device::PaDeviceIndex + channelCount::Cint + sampleFormat::PaSampleFormat + suggestedLatency::PaTime + hostAPISpecificStreamInfo::Ptr{Void} +end """ -choose the devices for 2 channel IO + Open a single stream, not necessarily the default one + The stream is unidirectional, either input or default output + see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html """ -function choose_input_output() - devices = get_audio_devices() - indev = -1 - outdev = -1 - for aud in devices - println("$(aud.device_index) $(aud.name)") - if (aud.max_input_channels == CHANNELS) & (indev == -1 ) - indev = aud.device_index - end - if(aud.max_output_channels == CHANNELS) & (outdev == -1) - outdev = aud.device_index - end - end - if indev == -1 - info("Appropriate input device not found.") - elseif outdev == -1 - info("Appropriate output device not found.") +function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, + input::Bool, sampleFormat::PaSampleFormat, + sampleRate::Cdouble, framesPerBuffer::Culong, + callback, udata) + streamPtr::Array{PaStream} = PaStream[1] + ioParameters = Pa_StreamParameters(device, channels, + sampleFormat, PaTime(0.001), + Ptr{Void}(0)) + streamcallback = Ptr{PaStreamCallback}(callback) + if input + err = ccall((:Pa_OpenStream, libportaudio), PaError, + (PaStream, Ref{Pa_StreamParameters}, Ptr{Void}, + Cdouble, Culong, Culong, + Ptr{PaStreamCallback}, Ptr{Void}), + streamPtr, ioParameters, Ptr{Void}(0), + sampleRate, framesPerBuffer, 0, + streamcallback, Ptr{Void}(udata)) else - info("Using input device ", bytestring(devices[indev + 1].name), - ", number ", devices[indev + 1].device_index, - " and output device ", bytestring(devices[outdev + 1].name), - ", number ", devices[outdev + 1].device_index) + err = ccall((:Pa_OpenStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Ref{Pa_StreamParameters}, + Cdouble, Culong, Culong, + Ptr{PaStreamCallback}, Ptr{Void}), + streamPtr, Ptr{Void}(0), ioParameters, + sampleRate, framesPerBuffer, 0, + streamcallback, Ptr{Void}(udata)) + end + handle_status(err) + streamPtr[1] +end + +type Pa_AudioStream <: AudioStream + root::AudioMixer + info::DeviceInfo + show_warnings::Bool + stream::PaStream + sformat::PaSampleFormat + channels::Integer + framesPerBuffer::Integer + + """ + Get device parameters needed for opening with portaudio + default is output as 44100/16bit int, same as CD audio type input + callback is optional, otherwise use blocing read and write functions + the callback must be a function that, if for writing, can return as an argument + a buffer the size and type same as stream.sbuffer, or if for output should + """ + function Pa_AudioStream(device_index, channels=2, input=false, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0, udata=0) + require_portaudio_init() + if (callback != 0) & (udata == 0) + # need to make a callback udata package + udata = CCallbackUdata(callback, sample_format, channels, input) + callback = pa_callback + end + stream = Pa_OpenStream(device_index, channels, input, sample_format, + Cdouble(sample_rate), Culong(framesPerBuffer), + callback, pointer_from_objref(udata)) + Pa_StartStream(stream) + root = AudioMixer() + this = new(root, DeviceInfo(sample_rate, framesPerBuffer), + show_warnings, stream, sample_format, + channels, framesPerBuffer) end - return indev, outdev +end + +function open_read(device_index, channels=2, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0) + Pa_AudioStream(device_index, channels, true, sample_rate, + framesPerBuffer, show_warnings, + sample_format, callback) +end + +function open_write(device_index, channels=2, + sample_rate::Integer=44100, + framesPerBuffer::Integer=2048, + show_warnings::Bool=false, + sample_format::PaSampleFormat=paInt16, + callback=0) + Pa_AudioStream(device_index, channels, false, sample_rate, + framesPerBuffer, show_warnings, + sample_format, callback) end """ -read from input +Blocking read from a Pa_AudioStream that is open as input """ -function read_blocking(devnum, buffer) - instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE) - buf = AudioIO.read(instream, BUFSIZE) - buflen = length(buf) - buffer[1: buflen] = buf[1: buflen] - AudioIO.Pa_CloseStream(instream.stream) +function read(stream::Pa_AudioStream, nframes::Int) + datatype = PaSampleFormat_to_T(stream.sformat) + buf = zeros(datatype, stream.framesPerBuffer * stream.channels) + return_buffer = zeros(datatype, nframes * stream.channels) + cur = 0 + read_needed = nframes + while read_needed > 0 + read_size = min(read_needed, stream.framesPerBuffer) + used = read_size * stream.channels + while Pa_GetStreamReadAvailable(stream.stream) < read_size + sleep(0.001) + yield() + end + err = ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), + stream.stream, buf, read_size) + handle_status(err, stream.show_warnings) + return_buffer[cur + 1: cur + used] = buf[1:used] + cur = cur + used + read_needed -= read_size + end + return_buffer end """ -write to output +Blocking write to a Pa_AudioStream that is open for output """ -function write_blocking(devnum, buffer) - outstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE) - AudioIO.write(outstream, buffer) - AudioIO.Pa_CloseStream(outstream.stream) +function write(ostream::Pa_AudioStream, buffer) + cur = 0 + write_needed = length(buffer) + while write_needed > 0 + write_size = min(write_needed, ostream.framesPerBuffer) + while Pa_GetStreamWriteAvailable(ostream.stream) < write_size + sleep(0.001) + yield() + end + end_cur = cur + write_size + err = ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), ostream.stream, + buffer[cur + 1: end_cur], + Culong(write_size/ostream.channels)) + handle_status(err, ostream.show_warnings) + write_needed -= write_size + cur = end_cur + end + 0 end """ -Create a string of numbers representing a sinewave audio tone +Take the c callback, restore types, pass to the callback functions """ -function make_note_buffer(frequency, amplitude, duration, srate) - N = round(Int, srate / frequency) - T = round(Int, frequency * duration) # repeat for T cycles - dt = 1.0 / srate - tone = zeros(Int16, (N + N) * T) - idx = 1 - while idx < (N + N) * T - tone[idx] = round(Int, amplitude * sin(2 * pi * frequency * - idx * dt) * 32767.0) - tone[idx + 1] = tone[idx] - idx += 2 +function callback_wrapper(input::Ptr{Void}, output::Ptr{Void}, + frameCount::Culong, + timeInfo::Ptr{AudioIO.CCallbackTimeInfo}, + sflags::Culong, udata::Ptr{Void}) + ("$udata") # workaround, get julia to prep for the dereference + cudata = unsafe_pointer_to_objref(Ptr{CCallbackUdata}(udata)) + datatype = PaSampleFormat_to_T(cudata.sample_format) + if cudata.is_read + indata = pointer_to_array(Ptr{datatype}(input), + (frameCount * cudata.channels, )) + cudata.func(indata) + else + outdata = (cudata.func)() + ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, Cint), + output, outdata, length(outdata) * sizeof(outdata[1])) + end + 0 +end + +const pa_callback = cfunction(callback_wrapper, Cint, (Ptr{Void}, + Ptr{Void}, + Culong, Ptr{AudioIO.CCallbackTimeInfo}, + Culong, Ptr{Void})) + +############ Internal Functions ############ + +function portaudio_task(stream::PortAudioStream) + info("PortAudio Render Task Running...") + n = bufsize(stream) + buffer = zeros(AudioSample, n) + try + while true + while Pa_GetStreamReadAvailable(stream.stream) < n + sleep(0.005) + end + Pa_ReadStream(stream.stream, buffer, n, stream.show_warnings) + # assume the root is always active + rendered = render(stream.root.renderer, buffer, stream.info)::AudioBuf + for i in 1:length(rendered) + buffer[i] = rendered[i] + end + for i in (length(rendered)+1):n + buffer[i] = 0.0 + end + while Pa_GetStreamWriteAvailable(stream.stream) < n + sleep(0.005) + end + Pa_WriteStream(stream.stream, buffer, n, stream.show_warnings) + end + catch ex + warn("Audio Task died with exception: $ex") + Base.show_backtrace(STDOUT, catch_backtrace()) end - tone end """ -Write a note to output device +Helper function to make the right type of buffer for various +sample formats. Converts PaSampleFormat to a typeof """ -function play_note(frequency, amplitude, duration, srate, ostream) - note = make_note_buffer(frequency, amplitude, duration, srate) - AudioIO.write(ostream, note) +function PaSampleFormat_to_T(fmt::PaSampleFormat) + retval = UInt8(0x0) + if fmt == 1 + retval = Float32(1.0) + elseif fmt == 2 + retval = Int32(0x02) + elseif fmt == 4 + retval = Int24(0x04) + elseif fmt == 8 + retval = Int16(0x08) + elseif fmt == 16 + retval = Int8(0x10) + elseif fmt == 32 + retval = UInt8(0x20) + else + info("Flawed input to PaSampleFormat_to_T, primitive unknown") + end + typeof(retval) +end + +type PaDeviceInfo + struct_version::Cint + name::Ptr{Cchar} + host_api::PaHostApiIndex + max_input_channels::Cint + max_output_channels::Cint + default_low_input_latency::PaTime + default_low_output_latency::PaTime + default_high_input_latency::PaTime + default_high_output_latency::PaTime + default_sample_rate::Cdouble +end + +type PaHostApiInfo + struct_version::Cint + api_type::PaHostApiTypeId + name::Ptr{Cchar} + deviceCount::Cint + defaultInputDevice::PaDeviceIndex + defaultOutputDevice::PaDeviceIndex +end + +@compat type PortAudioInterface <: AudioInterface + name::AbstractString + host_api::AbstractString + max_input_channels::Int + max_output_channels::Int + device_index::PaDeviceIndex +end + +function get_portaudio_devices() + require_portaudio_init() + device_count = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) + pa_devices = [ [Pa_GetDeviceInfo(i), i] for i in 0:(device_count - 1)] + [PortAudioInterface(bytestring(d[1].name), + bytestring(Pa_GetHostApiInfo(d[1].host_api).name), + d[1].max_input_channels, + d[1].max_output_channels, + d[2]) + for d in pa_devices] +end + +function require_portaudio_init() + # can be called multiple times with no effect + global portaudio_inited + if !portaudio_inited + info("Initializing PortAudio. Expect errors as we scan devices") + Pa_Initialize() + portaudio_inited = true + end +end + +# Low-level wrappers for Portaudio calls +Pa_GetDeviceInfo(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), + Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) +Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), + Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) + +function Pa_Initialize() + err = ccall((:Pa_Initialize, libportaudio), PaError, ()) + handle_status(err) +end + +function Pa_Terminate() + err = ccall((:Pa_Terminate, libportaudio), PaError, ()) + handle_status(err) end -INS, OUTS = choose_input_output() +function Pa_StartStream(stream::PaStream) + err = ccall((:Pa_StartStream, libportaudio), PaError, + (PaStream,), stream) + handle_status(err) +end + +function Pa_StopStream(stream::PaStream) + err = ccall((:Pa_StopStream, libportaudio), PaError, + (PaStream,), stream) + handle_status(err) +end + +function Pa_CloseStream(stream::PaStream) + err = ccall((:Pa_CloseStream, libportaudio), PaError, + (PaStream,), stream) + handle_status(err) +end + +function Pa_GetStreamReadAvailable(stream::PaStream) + avail = ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, + (PaStream,), stream) + avail >= 0 || handle_status(avail) + avail +end + +function Pa_GetStreamWriteAvailable(stream::PaStream) + avail = ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, + (PaStream,), stream) + avail >= 0 || handle_status(avail) + avail +end -read_blocking(INS, BUFFER) -println("Finished blocking type reading device number $INS") -println("Recording volume is $(mean(abs(BUFFER))*(100/16783))% of max") -sleep(2) +function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf), + show_warnings::Bool=true) + frames <= length(buf) || error("Need a buffer at least $frames long") + err = ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), + stream, buf, frames) + handle_status(err, show_warnings) + buf +end + +function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf), + show_warnings::Bool=true) + frames <= length(buf) || error("Need a buffer at least $frames long") + err = ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), + stream, buf, frames) + handle_status(err, show_warnings) + nothing +end -write_blocking(OUTS, BUFFER) -println("Finished blocking type writing device number $OUTS") +Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ()) -outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE) -# play the C major scale -scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6] -for note in scale - play_note(note, 0.1, 0.75, SRATE, outstream) +function Pa_GetVersionText() + versionPtr = ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) + bytestring(versionPtr) end -# up an octave -for note in scale[2:8] - play_note(2*note, 0.1, 0.75, SRATE, outstream) + +function Pa_OpenDefaultStream(inChannels::Integer, outChannels::Integer, + sampleFormat::PaSampleFormat, + sampleRate::Real, framesPerBuffer::Integer) + streamPtr::Array{PaStream} = PaStream[0] + err = ccall((:Pa_OpenDefaultStream, libportaudio), + PaError, (Ptr{PaStream}, Cint, Cint, + PaSampleFormat, Cdouble, Culong, + Ptr{PaStreamCallback}, Ptr{Void}), + streamPtr, inChannels, outChannels, sampleFormat, sampleRate, + framesPerBuffer, 0, 0) + handle_status(err) + + streamPtr[1] +end + +function handle_status(err::PaError, show_warnings::Bool=true) + if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED + if show_warnings + msg = ccall((:Pa_GetErrorText, libportaudio), + Ptr{Cchar}, (PaError,), err) + warn("libportaudio: " * bytestring(msg)) + end + elseif err != PA_NO_ERROR + msg = ccall((:Pa_GetErrorText, libportaudio), + Ptr{Cchar}, (PaError,), err) + error("libportaudio: " * bytestring(msg)) + end end From e3a5e3082156516455aa5f5c2323335eced410fc Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 25 Nov 2015 16:18:06 -1000 Subject: [PATCH 46/54] Update portaudio.jl --- src/portaudio.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index 33fc3d2..f3e7216 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -271,7 +271,7 @@ function callback_wrapper(input::Ptr{Void}, output::Ptr{Void}, ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, Cint), output, outdata, length(outdata) * sizeof(outdata[1])) end - 0 + Cint(0) end const pa_callback = cfunction(callback_wrapper, Cint, (Ptr{Void}, From 87fc0a562078ee4ef5d316b3b347eb479a41c6dc Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 25 Nov 2015 18:40:53 -1000 Subject: [PATCH 47/54] Update portaudio.jl --- src/portaudio.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/portaudio.jl b/src/portaudio.jl index f3e7216..72e2ccd 100644 --- a/src/portaudio.jl +++ b/src/portaudio.jl @@ -110,12 +110,12 @@ end The stream is unidirectional, either input or default output see http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html """ -function Pa_OpenStream(device::PaDeviceIndex, channels::Cint, +function Pa_OpenStream(device::PaDeviceIndex, channels::Integer, input::Bool, sampleFormat::PaSampleFormat, sampleRate::Cdouble, framesPerBuffer::Culong, callback, udata) streamPtr::Array{PaStream} = PaStream[1] - ioParameters = Pa_StreamParameters(device, channels, + ioParameters = Pa_StreamParameters(device, Cint(channels), sampleFormat, PaTime(0.001), Ptr{Void}(0)) streamcallback = Ptr{PaStreamCallback}(callback) From 3b01c91373b3c506920a8b50c5e39984680c5f4f Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 29 Nov 2015 22:19:50 -1000 Subject: [PATCH 48/54] Create record_to_wav.jl --- examples/record_to_wav.jl | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 examples/record_to_wav.jl diff --git a/examples/record_to_wav.jl b/examples/record_to_wav.jl new file mode 100644 index 0000000..c14c19c --- /dev/null +++ b/examples/record_to_wav.jl @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Example for Julia AudioIO module +# uses the WAV.jl package + +using AudioIO +using WAV + +CHUNKSIZE = 40960 +FORMAT = AudioIO.paInt16 +CHANNELS = 2 +SRATE = 44100 +RECORD_SECONDS = 20 + +""" +choose the devices for 2 channel IO +""" +function choose_input_output() + devices = get_audio_devices() + indev = -1 + outdev = -1 + for aud in devices + println("$(aud.device_index) $(aud.name)") + if (aud.max_input_channels == CHANNELS) & (indev == -1 ) + indev = aud.device_index + end + if(aud.max_output_channels == CHANNELS) & (outdev == -1) + outdev = aud.device_index + end + end + if indev == -1 + info("Appropriate input device not found.") + elseif outdev == -1 + info("Appropriate output device not found.") + else + info("Using input device ", bytestring(devices[indev + 1].name), + ", number ", devices[indev + 1].device_index, + " and output device ", bytestring(devices[outdev + 1].name), + ", number ", devices[outdev + 1].device_index) + end + return indev, outdev +end + +""" +read from input +""" +function record_audio(devnum, seconds) + instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE) + bufsize = seconds * SRATE * CHANNELS + buf = AudioIO.read(instream, bufsize) + AudioIO.Pa_CloseStream(instream.stream) + buf +end + +""" +write to WAV file +""" +function write_as_WAV(buffer, filename="temp.WAV") + fio = open(filename, "w") + WAV.wavwrite(buffer, fio, Fs=SRATE*CHANNELS) +end + + +INS, OUTS = choose_input_output() + +println("Starting recording...") +BUF = record_audio(INS, RECORD_SECONDS) +println("Finished reading from device number $INS") +println("Recording volume was $(mean(abs(BUF))*(100/16783))% of max") + +write_as_WAV(BUF) +println("Finished writing to WAV file.") + From ce4554fea8768b42baf2c066a5afd09f7c8bca88 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 30 Nov 2015 21:20:01 -1000 Subject: [PATCH 49/54] Update record_to_wav.jl --- examples/record_to_wav.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/record_to_wav.jl b/examples/record_to_wav.jl index c14c19c..a01dc21 100644 --- a/examples/record_to_wav.jl +++ b/examples/record_to_wav.jl @@ -66,7 +66,7 @@ INS, OUTS = choose_input_output() println("Starting recording...") BUF = record_audio(INS, RECORD_SECONDS) println("Finished reading from device number $INS") -println("Recording volume was $(mean(abs(BUF))*(100/16783))% of max") +println("Mean recording volume was $(mean(abs(BUF))*(100/16783))% of max") write_as_WAV(BUF) println("Finished writing to WAV file.") From 1296639b65afdfa08de9c542c9464fa032819cbc Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 30 Nov 2015 21:38:54 -1000 Subject: [PATCH 50/54] Update record_to_wav.jl --- examples/record_to_wav.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/record_to_wav.jl b/examples/record_to_wav.jl index a01dc21..9d6e98b 100644 --- a/examples/record_to_wav.jl +++ b/examples/record_to_wav.jl @@ -46,7 +46,7 @@ read from input """ function record_audio(devnum, seconds) instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE) - bufsize = seconds * SRATE * CHANNELS + bufsize = seconds * SRATE buf = AudioIO.read(instream, bufsize) AudioIO.Pa_CloseStream(instream.stream) buf From 70eb2778fa9a8e0b155fb9e23097caac8176e419 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 1 Dec 2015 18:24:30 -1000 Subject: [PATCH 51/54] Create callback_bands.jl --- examples/callback_bands.jl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/callback_bands.jl diff --git a/examples/callback_bands.jl b/examples/callback_bands.jl new file mode 100644 index 0000000..5b1b007 --- /dev/null +++ b/examples/callback_bands.jl @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Examples for Julia AudioIO module + +# display relative (not absolute) dB of bass, mid, treble bands + +using AudioIO, DSP + +CHUNKSIZE = 40960 +FORMAT = AudioIO.paInt16 +CALLBACK_DATATYPE = AudioIO.PaSampleFormat_to_T(FORMAT) +CHANNELS = Cint(2) +SRATE = 44100 + +read_position = 0 +""" +Custom callback for read, processes passed data array +""" +function process_read(buf) + fchunk = map(Float64, buf) + pgram = welch_pgram(fchunk, 4096, 0, fs=SRATE*CHANNELS) + pxx = power(pgram) + frqs = freq(pgram) + bass = log(sum(abs(pxx[(frqs .<= 640.0) & (frqs .> 100.0)]))) + midrange = log(sum(abs(pxx[(frqs .<= 5120.0) & (frqs .> 640.0)]))) + treble = log(sum(abs(pxx[(frqs .<= 20480.0) & (frqs .> 5120.0)]))) + println("bass $bass, midrange $midrange, treble $treble") +end + +istream = AudioIO.open_read(Cint(0), CHANNELS, SRATE, CHUNKSIZE, + false, FORMAT, process_read) +println("Starting callback type reading, control-C to stop") + +while true + sleep(20) +end From 743418ec970342088c97cbcc3737db6774eb99db Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 2 Dec 2015 08:09:09 -1000 Subject: [PATCH 52/54] Update callback_bands.jl --- examples/callback_bands.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/callback_bands.jl b/examples/callback_bands.jl index 5b1b007..c7bea8f 100644 --- a/examples/callback_bands.jl +++ b/examples/callback_bands.jl @@ -8,11 +8,9 @@ using AudioIO, DSP CHUNKSIZE = 40960 FORMAT = AudioIO.paInt16 -CALLBACK_DATATYPE = AudioIO.PaSampleFormat_to_T(FORMAT) CHANNELS = Cint(2) SRATE = 44100 -read_position = 0 """ Custom callback for read, processes passed data array """ From 6a03fe356ce84239f80edd72ca414c1ef2092216 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 2 Dec 2015 08:09:48 -1000 Subject: [PATCH 53/54] Update callback_bands.jl --- examples/callback_bands.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/callback_bands.jl b/examples/callback_bands.jl index c7bea8f..e770720 100644 --- a/examples/callback_bands.jl +++ b/examples/callback_bands.jl @@ -6,10 +6,6 @@ using AudioIO, DSP -CHUNKSIZE = 40960 -FORMAT = AudioIO.paInt16 -CHANNELS = Cint(2) -SRATE = 44100 """ Custom callback for read, processes passed data array @@ -25,6 +21,10 @@ function process_read(buf) println("bass $bass, midrange $midrange, treble $treble") end +CHUNKSIZE = 40960 +FORMAT = AudioIO.paInt16 +CHANNELS = Cint(2) +SRATE = 44100 istream = AudioIO.open_read(Cint(0), CHANNELS, SRATE, CHUNKSIZE, false, FORMAT, process_read) println("Starting callback type reading, control-C to stop") From 17578e923b7e105a04f4878d873f10afe8d1f339 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 2 Dec 2015 09:50:12 -1000 Subject: [PATCH 54/54] Update record_to_wav.jl --- examples/record_to_wav.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/record_to_wav.jl b/examples/record_to_wav.jl index 9d6e98b..3d9bde8 100644 --- a/examples/record_to_wav.jl +++ b/examples/record_to_wav.jl @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -# Example for Julia AudioIO module -# uses the WAV.jl package +# Examples for Julia AudioIO module using AudioIO using WAV @@ -57,17 +56,23 @@ write to WAV file """ function write_as_WAV(buffer, filename="temp.WAV") fio = open(filename, "w") - WAV.wavwrite(buffer, fio, Fs=SRATE*CHANNELS) + WAV.wavwrite(deinterlace_stereo(buffer), fio, Fs=SRATE) end +""" +interlaced to noninterlaced stereo data +""" +function deinterlace_stereo(buffer) + reshape([buffer[1:2:end]; buffer[2:2:end]], + (floor(Int, length(buffer)/2), 2)) +end INS, OUTS = choose_input_output() println("Starting recording...") BUF = record_audio(INS, RECORD_SECONDS) println("Finished reading from device number $INS") -println("Mean recording volume was $(mean(abs(BUF))*(100/16783))% of max") +println("Recording volume was $(mean(abs(BUF))*(100/16783))% of max") write_as_WAV(BUF) println("Finished writing to WAV file.") -