Current section
Files
Jump to
Current section
Files
src/erlaudio.erl
-module(erlaudio).
-include_lib("erlaudio.hrl").
-export([
default_input_device/0,
default_output_device/0,
device/1,
devices/0
]).
-export([
default_input_params/1,
default_output_params/1,
input_device_params/2,
output_device_params/2,
device_params/4
]).
-export([
stream_format_supported/3,
stream_open/4,
stream_open/5,
stream_start/1,
stream_info/1,
stream_read/1,
stream_write/2,
stream_abort/1,
stream_stop/1,
stream_close/1,
stream_active/1,
stream_stopped/1,
stream_write_available/1,
stream_write_available/2,
stream_writebuffer_size/1
]).
-export([
portaudio_version/0
]).
-type pa_error() :: {error, atom()}.
-type stream_option() :: noclip | nodither | nodropinput.
%% @doc Gets you the default input device
-spec default_input_device() -> #erlaudio_device{}.
default_input_device() ->
erlaudio_drv:get_device(erlaudio_drv:get_default_input_device_index()).
%% @doc Gets you the default output device
-spec default_output_device() -> #erlaudio_device{}.
default_output_device() ->
erlaudio_drv:get_device(erlaudio_drv:get_default_output_device_index()).
%% @doc Gets you a device for some integer
%%
%% On some platforms, device offsets are static, on others they're not.
%% You can try to store a device's ID and see if the name remains constant,
%% Though it's recommended that you look at a device's actual name in devices()
-spec device(Index :: integer()) -> #erlaudio_device{} | {error, nodevice}.
device(Index) when Index >= 0 ->
DeviceCount = erlaudio_drv:get_device_count(),
case Index of
Index when Index < DeviceCount ->
erlaudio_drv:get_device(Index);
_ ->
{error, nodevice}
end.
%% @doc Gets you a list of devices
-spec devices() -> [#erlaudio_device{}].
devices() ->
[ erlaudio_drv:get_device(Idx) || Idx <- lists:seq(0, erlaudio_drv:get_device_count() - 1) ].
%% @doc Gets you device parameters for the default input device
-spec default_input_params(SampleFormat :: format()) -> #erlaudio_device_params{}.
default_input_params(SampleFormat) ->
input_device_params(erlaudio_drv:get_default_input_device_index(), SampleFormat).
%% @doc Gets you device parameters for the default output device
-spec default_output_params(SampleFormat :: format()) -> #erlaudio_device_params{}.
default_output_params(SampleFormat) ->
output_device_params(erlaudio_drv:get_default_output_device_index(), SampleFormat).
%% @doc Gets you reasonable defaults for an input device
-spec input_device_params(Device :: #erlaudio_device{} | integer(),
SampleFormat :: format()) -> #erlaudio_device_params{}.
input_device_params(Index, SampleFormat) when is_integer(Index) ->
input_device_params(erlaudio_drv:get_device(Index), SampleFormat);
input_device_params(#erlaudio_device{index=Index} = Device, SampleFormat) ->
device_params(Index, Device#erlaudio_device.max_input_channels,
SampleFormat, Device#erlaudio_device.default_low_input_latency).
%% @doc Gets you reasonable defaults for an output device
-spec output_device_params(Device :: #erlaudio_device{} | integer(),
SampleFormat :: format()) -> #erlaudio_device_params{}.
output_device_params(Index, SampleFormat) when is_integer(Index) ->
output_device_params(erlaudio_drv:get_device(Index), SampleFormat);
output_device_params(#erlaudio_device{index=Index} = Device, SampleFormat) ->
device_params(Index, Device#erlaudio_device.max_output_channels,
SampleFormat, Device#erlaudio_device.default_low_output_latency).
%% @doc Just a convenience type-check'd function for returning a device params record
-spec device_params(Device :: #erlaudio_device{} | integer(), Channels :: integer(),
SampleFormat :: format(), SuggestedLatency :: float()) -> #erlaudio_device_params{}.
device_params(Index, Channels, SampleFormat, SuggestedLatency) when
is_integer(Index) andalso is_integer(Channels) andalso
is_atom(SampleFormat) andalso is_float(SuggestedLatency) ->
#erlaudio_device_params{
index=Index,
channel_count=Channels,
sample_format=SampleFormat,
suggested_latency=SuggestedLatency
};
device_params(#erlaudio_device{index=Index}=_Device, Channels, SampleFormat, SuggestedLatency) when
is_integer(Channels) andalso is_atom(SampleFormat) andalso is_float(SuggestedLatency) ->
#erlaudio_device_params{
index=Index,
channel_count=Channels,
sample_format=SampleFormat,
suggested_latency=SuggestedLatency
}.
stream_write(Handle, Data) ->
erlaudio_drv:stream_write(Handle, Data).
stream_read(Handle) ->
receive
{erlaudio_pcmdata, Handle, Data} -> {ok, Data};
{erlaudio, Handle, closed} -> eof;
{erlaudio_error, Handle, Error} -> {error, Error}
% As a hack, every second we don't have data, we should probably check to see
% if we've still got a listening handle
after 2000 ->
case erlaudio_drv:stream_is_active(Handle) of
true -> stream_read(Handle); % We loop forever.
false -> {error, stream_closed};
{error, Reason} -> {error, Reason}
end
end.
%% @doc Check if your combination of parameters is supported
-spec stream_format_supported(
Input :: #erlaudio_device_params{} | null | undefined,
Output :: #erlaudio_device_params{} | null | undefined,
SampleRate :: float()
) -> ok | pa_error().
stream_format_supported(Input, Output, SampleRate) ->
erlaudio_drv:stream_format_supported(Input, Output, SampleRate).
stream_open(Input, Output, SampleRate, FramesPerBuffer) ->
stream_open(Input, Output, SampleRate, FramesPerBuffer, []).
-spec stream_open(
Input :: #erlaudio_device_params{} | default | null | undefined,
Output :: #erlaudio_device_params{} | default | null | undefined,
SampleRate :: float(),
FramesPerBuffer :: integer(),
Flags :: [stream_option()]
) -> {ok, pid()} | {error, any()}.
stream_open(default, Output, SampleRate, FramesPerBuffer, Flags) ->
Input = default_input_params(int16),
stream_open(Input, Output, SampleRate, FramesPerBuffer, Flags);
stream_open(Input, default, SampleRate, FramesPerBuffer, Flags) ->
Output = default_output_params(int16),
stream_open(Input, Output, SampleRate, FramesPerBuffer, Flags);
%% @doc start a stream server
stream_open(Input, Output, SampleRate, FramesPerBuffer, Flags) ->
erlaudio_drv:stream_open(Input, Output, SampleRate, FramesPerBuffer, Flags).
stream_start(Handle) ->
erlaudio_drv:stream_start(Handle),
receive
{erlaudio, Handle, started} -> ok
after 30000 -> {error, timeout}
end.
%% @doc stop a stream, ignoring any remaining buffers
stream_abort(Handle) ->
erlaudio_drv:stream_abort(Handle),
drain(Handle).
%% @doc stop/destroy a stream
stream_close(Handle) ->
erlaudio_drv:stream_close(Handle),
drain(Handle).
%% @doc stop a stream, block until audio flushes out the write buffers.
stream_stop(Handle) ->
case erlaudio_drv:stream_stop(Handle) of
ok ->
receive
{erlaudio, Handle, closed} -> drain(Handle)
after 30000 -> drain(Handle), {error, timeout}
end;
Error ->
Error
end.
%% @doc get stream info
stream_info(Handle) -> erlaudio_drv:stream_info(Handle).
stream_active(Handle) -> erlaudio_drv:stream_is_active(Handle).
stream_stopped(Handle) -> erlaudio_drv:stream_is_stopped(Handle).
stream_writebuffer_size(Handle) -> erlaudio_drv:stream_writebuffer_size(Handle).
stream_write_available(Handle) -> stream_write_available(Handle, available).
stream_write_available(Handle, available) ->
case erlaudio_drv:stream_write_available(Handle) of
{ok, Avail, _} -> Avail;
Else -> Else
end;
stream_write_available(Handle, internal) ->
case erlaudio_drv:stream_write_available(Handle) of
{ok, _, Internal} -> Internal;
Else -> Else
end;
stream_write_available(Handle, total) ->
case erlaudio_drv:stream_write_available(Handle) of
{ok, Avail, Internal} -> Avail + Internal;
Else -> Else
end.
%% @doc Return version information for the portaudio we're linked to
-spec portaudio_version() -> {integer(), binary()}.
portaudio_version() -> erlaudio_drv:get_pa_version().
drain(Handle) ->
Res = receive
{erlaudio_pcmdata, Handle, _} -> ok;
{erlaudio, Handle, Msg} when Msg/=closed -> ok;
{erlaudio_error, Handle, _} -> ok;
{erlaudio, Handle, closed} -> done
after 2000 -> done
end,
case Res of
ok -> drain(Handle);
done -> ok
end.