defmodule Membrane.Audiometer.Peakmeter do
@moduledoc """
This element computes peaks in each channel of the given signal at
regular time intervals, regardless if it receives data or not.
It uses erlang's `:timer.send_interval/2` which might not provide
perfect accuracy.
It accepts audio samples in any format supported by `Membrane.RawAudio`
module.
It will periodically emit notifications, of the following format:
* `{:audiometer, :underrun}` - if there were not enough data to
compute audio level within given interval,
* `{:audiometer, {:measurement, measurement}}` - where `measurement`
is a `Membrane.Audiometer.Peakmeter.Notification.Measurement`
struct containing computed audio levels. See its documentation for
more details about the actual value format.
See `options/0` for available options.
"""
use Membrane.Filter
alias __MODULE__.Amplitude
alias Membrane.Element.PadData
alias Membrane.RawAudio
@type amplitude_t :: [number | :infinity | :clip]
def_input_pad :input,
availability: :always,
accepted_format: RawAudio
def_output_pad :output,
availability: :always,
accepted_format: RawAudio
def_options interval: [
spec: Membrane.Time.t(),
description: """
How often peakmeter should emit messages containing sound level (in Membrane.Time units).
""",
default: Membrane.Time.milliseconds(50),
inspector: &Membrane.Time.inspect/1
]
# Private API
@impl true
def handle_init(_ctx, %__MODULE__{interval: interval}) do
state = %{
interval: interval,
queue: <<>>
}
{[], state}
end
@impl true
def handle_playing(_ctx, state) do
{[start_timer: {:timer, state.interval}], state}
end
@impl true
def handle_buffer(
:input,
%Membrane.Buffer{payload: payload} = buffer,
_ctx,
state
) do
new_state = %{state | queue: state.queue <> payload}
{[buffer: {:output, buffer}], new_state}
end
@impl true
def handle_tick(:timer, %{pads: %{input: %PadData{stream_format: nil}}}, state) do
{[notify_parent: :underrun], state}
end
def handle_tick(:timer, %{pads: %{input: %PadData{stream_format: stream_format}}}, state) do
frame_size = RawAudio.frame_size(stream_format)
if byte_size(state.queue) < frame_size do
{[notify_parent: {:audiometer, :underrun}], state}
else
{:ok, {amplitudes, rest}} = Amplitude.find_amplitudes(state.queue, stream_format)
{[notify_parent: {:amplitudes, amplitudes}], %{state | queue: rest}}
end
end
end