2014-01-03 11:47:38 -08:00
typealias PaTime Cdouble
typealias PaError Cint
typealias PaSampleFormat Culong
typealias PaStream Void
2014-05-02 16:10:54 -04:00
typealias PaDeviceIndex Cint
typealias PaHostApiIndex Cint
typealias PaTime Cdouble
typealias PaHostApiTypeId Cint
2014-01-03 11:47:38 -08:00
const PA_NO_ERROR = 0
2014-08-28 13:36:38 -04:00
# expected shim revision, so we can notify if it gets out of sync
const SHIM_REVISION = 2
2014-01-03 11:47:38 -08:00
const libportaudio_shim = find_library ( [ " libportaudio_shim " , ] ,
[ Pkg . dir ( " AudioIO " , " deps " , " usr " , " lib " ) , ] )
2014-05-02 16:10:54 -04:00
# 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 "
}
2014-01-03 11:47:38 -08:00
# track whether we've already inited PortAudio
portaudio_inited = false
################## Types ####################
type PortAudioStream <: AudioStream
2014-06-23 02:10:35 -04:00
root :: AudioMixer
2014-01-03 11:47:38 -08:00
info :: DeviceInfo
function PortAudioStream ( sample_rate :: Int = 44100 , buf_size :: Int = 1024 )
2014-05-02 16:10:54 -04:00
init_portaudio ( )
2014-06-23 02:10:35 -04:00
root = AudioMixer ( )
stream = new ( root , DeviceInfo ( sample_rate , buf_size ) )
2014-01-03 11:47:38 -08:00
# we need to start up the stream with the portaudio library
open_portaudio_stream ( stream )
return stream
end
end
############ Internal Functions ############
2014-03-25 12:35:04 +01:00
function synchronize_buffer ( buffer )
ccall ( ( :synchronize_buffer , libportaudio_shim ) , Void , ( Ptr { Void } , ) , buffer )
2014-01-03 11:47:38 -08:00
end
function open_portaudio_stream ( stream :: PortAudioStream )
# starts up a stream with the portaudio library and associates it with the
# given AudioIO PortAudioStream
# TODO: handle more streams
2014-08-28 13:36:38 -04:00
shim_rev = ccall ( ( :get_shim_revision , libportaudio_shim ) , Cint , ( ) )
if shim_rev != SHIM_REVISION
error ( " Expected shim revision $SHIM_REVISION , got $shim_rev . Run 'make' from AudioIO/deps/src " )
end
2014-01-03 11:47:38 -08:00
fd = ccall ( ( :make_pipe , libportaudio_shim ) , Cint , ( ) )
info ( " Launching PortAudio Task... " )
2014-05-02 16:10:54 -04:00
schedule ( Task ( ( ) -> portaudio_task ( fd , stream ) ) )
2014-01-03 11:47:38 -08:00
# TODO: test not yielding here
yield ( )
info ( " Audio Task Yielded, starting the stream... " )
err = ccall ( ( :open_stream , libportaudio_shim ) , PaError ,
( Cuint , Cuint ) ,
stream . info . sample_rate , stream . info . buf_size )
handle_status ( err )
info ( " Portaudio stream started. " )
end
function handle_status ( err :: PaError )
if err != PA_NO_ERROR
2014-07-27 13:51:02 -04:00
msg = ccall ( ( :Pa_GetErrorText , libportaudio ) ,
2014-01-03 11:47:38 -08:00
Ptr { Cchar } , ( PaError , ) , err )
error ( " libportaudio: " * bytestring ( msg ) )
end
end
function portaudio_task ( jl_filedesc :: Integer , stream :: PortAudioStream )
2014-03-25 12:35:04 +01:00
buffer = zeros ( AudioSample , stream . info . buf_size )
2014-01-03 11:47:38 -08:00
desc_bytes = Cchar [ 0 ]
jl_stream = fdio ( jl_filedesc )
jl_rawfd = RawFD ( jl_filedesc )
2014-01-05 22:50:56 -05:00
try
while true
2014-06-23 02:10:35 -04:00
# assume the root is always active
2014-06-26 15:36:52 -05:00
rendered = render ( stream . root . renderer , buffer , stream . info ) :: AudioBuf
2014-06-23 02:10:35 -04:00
for i in 1 : length ( rendered )
buffer [ i ] = rendered [ i ]
end
for i in ( length ( rendered ) + 1 ) : length ( buffer )
buffer [ i ] = 0.0
end
2014-03-25 12:35:04 +01:00
2014-01-05 22:50:56 -05:00
# wake the C code so it knows we've given it some more data
2014-03-25 12:35:04 +01:00
synchronize_buffer ( buffer )
2014-01-05 22:50:56 -05:00
# wait for new data to be available from the sound card (and for it
# to have processed our last frame of data). At some point we
# should do something with the data we get from the callback
wait ( jl_rawfd , readable = true )
# read from the file descriptor so that it's empty. We're using
# ccall here because readbytes() was blocking the whole julia
# thread. This shouldn't block at all because we just waited on it
ccall ( :read , Clong , ( Cint , Ptr { Void } , Culong ) ,
jl_filedesc , desc_bytes , 1 )
end
2014-06-24 03:30:38 -04:00
catch ex
warn ( " Audio Task died with exception: $ex " )
Base . show_backtrace ( STDOUT , catch_backtrace ( ) )
2014-01-05 22:50:56 -05:00
finally
# TODO: we need to close the stream here. Otherwise the audio callback
# will segfault accessing the output array if there were exceptions
# thrown in the render loop
2014-01-03 11:47:38 -08:00
end
end
2014-05-02 16:10:54 -04:00
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
2014-05-02 16:10:54 -04:00
name :: String
host_api :: String
max_input_channels :: Int
max_output_channels :: Int
end
# some thin wrappers to portaudio calls
2014-07-27 13:51:02 -04:00
get_device_info ( i ) = unsafe_load ( ccall ( ( :Pa_GetDeviceInfo , libportaudio ) ,
2014-05-02 16:10:54 -04:00
Ptr { PaDeviceInfo } , ( PaDeviceIndex , ) , i ) )
2014-07-27 13:51:02 -04:00
get_host_api_info ( i ) = unsafe_load ( ccall ( ( :Pa_GetHostApiInfo , libportaudio ) ,
2014-05-02 16:10:54 -04:00
Ptr { PaHostApiInfo } , ( PaHostApiIndex , ) , i ) )
function get_portaudio_devices ( )
init_portaudio ( )
2014-07-27 13:51:02 -04:00
device_count = ccall ( ( :Pa_GetDeviceCount , libportaudio ) , PaDeviceIndex , ( ) )
2014-05-02 16:10:54 -04:00
pa_devices = [ get_device_info ( i ) for i in 0 : ( device_count - 1 ) ]
2014-05-23 20:59:22 -04:00
[ PortAudioInterface ( bytestring ( d . name ) ,
bytestring ( get_host_api_info ( d . host_api ) . name ) ,
d . max_input_channels ,
d . max_output_channels )
2014-05-02 16:10:54 -04:00
for d in pa_devices ]
end
function init_portaudio ( )
# can be called multiple times with no effect
global portaudio_inited
if ! portaudio_inited
@assert ( libportaudio_shim != " " , " Failed to find required library libportaudio_shim. Try re-running the package script using Pkg.build( \" AudioIO \" ), then reloading with reload( \" AudioIO \" ) " )
info ( " Initializing PortAudio. Expect errors as we scan devices " )
2014-07-27 13:51:02 -04:00
err = ccall ( ( :Pa_Initialize , libportaudio ) , PaError , ( ) )
2014-05-02 16:10:54 -04:00
handle_status ( err )
portaudio_inited = true
end
end