PortAudio.jl/src/portaudio.jl

255 lines
7.9 KiB
Julia
Raw Normal View History

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
const PA_NO_ERROR = 0
const PA_INPUT_OVERFLOWED = -10000 + 19
const PA_OUTPUT_UNDERFLOWED = -10000 + 20
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)
# PaHostApiTypeId values
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"
}
# track whether we've already inited PortAudio
portaudio_inited = false
################## Types ####################
type PortAudioStream <: AudioStream
root::AudioMixer
info::DeviceInfo
stream::PaStream
function PortAudioStream(sample_rate::Integer=44100, buf_size::Integer=1024)
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), 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
############ 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)
# 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)
end
catch ex
warn("Audio Task died with exception: $ex")
Base.show_backtrace(STDOUT, catch_backtrace())
end
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
2014-05-23 20:59:22 -04:00
type PortAudioInterface <: AudioInterface
name::String
host_api::String
max_input_channels::Int
max_output_channels::Int
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)]
2014-05-23 20:59:22 -04:00
[PortAudioInterface(bytestring(d.name),
bytestring(Pa_GetHostApiInfo(d.host_api).name),
2014-05-23 20:59:22 -04:00
d.max_input_channels,
d.max_output_channels)
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
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))
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)
buf
end
function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf))
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)
nothing
end
Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ())
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)
if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED
msg = ccall((:Pa_GetErrorText, libportaudio),
Ptr{Cchar}, (PaError,), err)
warn("libportaudio: " * bytestring(msg))
elseif err != PA_NO_ERROR
msg = ccall((:Pa_GetErrorText, libportaudio),
Ptr{Cchar}, (PaError,), err)
error("libportaudio: " * bytestring(msg))
end
end