#!/bin/bash
#set -n		# read but do not execute commands (check on syntax errors)
#
# File: /usr/local/bin/audiofilter-dynvol
#
# Apply the dynaudnorm filter and the volume filter.
#
#    Copyright (c) 2022  Juergen Kaesmann (JK)
#
##############################################################################
# GPL NOTICE
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
##############################################################################
# FIND SAMPLES
#
##############################################################################
# FILTER SAMPLES
#
# To avoid spaces (shell) surround filter and arguments with ""
# There are two filter types:
# a) Simple filter
#	-filter:a|-filter:v "<filter>=<arguments_list>"
#	or
#	-af|-vf "<filter>=<arguments_list>"
# b) Complex filter
#	-filter_complex "[<arguments_list>]<filter>=<arguments_list>"
#
#-----------------------------------------------------------------------------
# SETPTS filter explanation:
# setpts	Set timestamps.
#
# generate video timestamps from zero:
# -filter:v "trim" -filter:v "setpts=PTS-STARTPTS"
#
# generate audio timestamps from zero:
# -filter:a "atrim" -filter:a "asetpts=PTS-STARTPTS"
#
#-----------------------------------------------------------------------------
# VOLUME filter explanation:
# volume	Adjust the volume. Do not set the volume.
#
# increase volume by 150%:
# -filter:a "volume=1.5"
#
# increase volume by 10dB:
# -filter:a "volume=10dB"
#
# reduce volume by 5dB, use a negative value:
# -filter:a "volume=-5dB"
#
#-----------------------------------------------------------------------------
# PEAK and RMS Normalization:
# volume	Adjust the volume to a peak or RMS level.
#
# To normalize the volume to a given peak or RMS level, the file first has to
# be analyzed with filter a) or b):
# a) -filter:a volumedetect
# b) -filter_complex [0:a]ebur128=peak=sample
#
# Read the output values from the command line log (relative to max value)
# and calculate the required offset to adjust max_volume/PEAK to 0.
# Then change the volume with the filter:
# -filter:a "volume=<offset>dB"
#
# or normalize the file with:
# -af "dynaudnorm=<...>"
#
#-----------------------------------------------------------------------------
# COMPANDER explanation:
# compand	Compress or expand the audio's dynamic range.
#
# Show compand params - separate every PART with ':'
# PARTS		DEFAULTS		MODE "s"
# attack lst	0.3 0.3			0 0
# decay lst	0.8 0.8			1 1
# points lst	-70/-70 -60/-60 1/0	-90/-900 -70/-70 -30/-9 0/-3
# soft-knee	0.01			6
# gain		0			0
# volume	0			0
# delay		0			0
#
# Using simple audiofilter:
# -af "aformat=channel_layouts=stereo, compand=0 0:1 1:-90/-900 -70/-70 -30/-9 0/-3:6:0:0:0"
#
# Using complex audiofilter:
# -filter_complex "compand=0 0:1 1:-90/-900 -70/-70 -30/-9 0/-3:6:0:0:0"
#
# same as above but avoid SPACES:
# -filter_complex compand=0|0:1|1:-90/-900|-70/-70|-30/-9|0/-3:6:0:0:0
#
#-----------------------------------------------------------------------------
# EQUALIZER explanation:
# equalizer	Apply a two-pole peaking equalisation (EQ) filter.
#
# With this filter, the signal-level at and around a selected frequency can
# be increased or decreased, whilst (unlike bandpass and bandreject filters)
# that at all other frequencies signal is unchanged.
#
# In order to produce complex equalisation curves, this filter can be
# given several times, each with a different central frequency and separated
# with a comma.
#
# The filter accepts the following options:
# frequency, f	Set the filter's central frequency in Hz.
# width_type	Set method to specify band-width of filter.
# 	h	Hz
# 	q	Q-Factor
# 	o	octave
# 	s	slope
# width, w	Specify the band-width of a filter in width_type units.
#		Have in mind that the frequency is limited to 999 Hz.
# gain, g	Set the required gain or attenuation in dB.
# 		Beware of clipping when using a positive gain.
#
# Examples:
# Attenuate 10 dB at 1000 Hz, with a bandwidth of 200 Hz:
# -af equalizer=f=1000:width_type=h:width=200:g=-10
#
# Apply 2 dB gain at 1000 Hz with Q 1 and attenuate 5 dB at 100 Hz with Q 2:
# -af equalizer=f=1000:width_type=q:width=1:g=2,equalizer=f=100:wid
#
# Apply 6 dB gain at 200 Hz and width 300 Hz:
# -af equalizer=f=200:width_type=h:w=300:g=6
#
# Sound Distribution:
# BAND		TYPE
# 16-100	Sub Bass
# 50-250	Bass
# --------------------------
# 250-2k	Low Midd
# 2k-4k		High Midd
# --------------------------
# 4k-6k		Präsenz
# 6k-16k	Brillanz
#
# Template EQ with 18 bands non-overlaping
# BAND		f	w
# 50-100	75	50
# 100-250	175	150
# 250-1k	625	750
# 1k-2k		1500	999
# 2k-3k		2500	999
# 3k-4k		3500	999
# 4k-5k		4500	999
# 5k-6k		5500	999
# 6k-7k		6500	999
# 7k-8k		7500	999
# 8k-9k		8500	999
# 9k-10k	9500	999
# 10k-11k	10500	999
# 11k-12k	11500	999
# 12k-13k	12500	999
# 13k-14k	13500	999
# 14k-15k	14500	999
# 15k-16k	15500	999
# -af equalizer=f=75:width_type=h:w=50:g=0,equalizer=f=175:width_type=h:w=150:g=0,equalizer=f=625:width_type=h:w=750:g=0,equalizer=f=1500:width_type=h:w=999:g=0,equalizer=f=2500:width_type=h:w=999:g=0,equalizer=f=3500:width_type=h:w=999:g=0,equalizer=f=4500:width_type=h:w=999:g=0,equalizer=f=5500:width_type=h:w=999:g=0,equalizer=f=6500:width_type=h:w=999:g=0,equalizer=f=7500:width_type=h:w=999:g=0,equalizer=f=8500:width_type=h:w=999:g=0,equalizer=f=9500:width_type=h:w=999:g=0,equalizer=f=10500:width_type=h:w=999:g=0,equalizer=f=11500:width_type=h:w=999:g=0,equalizer=f=12500:width_type=h:w=999:g=0,equalizer=f=13500:width_type=h:w=999:g=0,equalizer=f=14500:width_type=h:w=999:g=0,equalizer=f=15500:width_type=h:w=999:g=0
#
#-----------------------------------------------------------------------------
# ANEQUALIZER explanation:
# anequalizer	High-order parametric multiband equalizer for each channel.
#
# This option string is in format: "c<chn> f=<cf> w=<bw> g=<bg> t=<ft> | ..."
# Each equalizer band is separated by '|'.
#
# chn	Channel number to which equalization will be applied (0|1 for stereo).
#	If input doesn't have that channel the entry is ignored.
# cf	Central frequency for band.  If input doesn't have that
#	frequency the entry is ignored.
# bw	Band width in hertz.
# bg	Band gain in dB.
# ft	Filter type for band, optional, can be:
#	0	Butterworth, this is default.
#	1	Chebyshev type 1.
#	2	Chebyshev type 2.
#
# Examples:
# Lower gain by 10 of central frequency 200Hz and width 100 Hz for
# first 2 channels using Chebyshev type 1 filter:
# -af "anequalizer=c0 f=200 w=100 g=-10 t=1|c1 f=200 w=100 g=-10 t=1"
#
# Rise gain by 6 dB of central frequency 200 Hz and width 300 Hz for
# first 2 channels using Butterworth type 0 filter:
# -af "anequalizer=c0 f=200 w=300 g=6 t=0|c1 f=200 w=300 g=6 t=0"
#
#-----------------------------------------------------------------------------
# AFADE filter explanation:
# afade		Apply fade-in/out effect to input audio.
#
# type, t
#   Specify the effect type, can be either "in" for fade-in, or "out"
#   for a fade-out effect. Default is "in".
#
# start_time, st
#   Specify the start time of the fade effect. Default is 0.  The value
#   must be specified as a time duration; see the Time duration section
#   in the ffmpeg-utils(1) manual for the accepted syntax.
#
# duration, d
#   Specify the duration of the fade effect. See the Time duration
#   section in the ffmpeg-utils(1) manual for the accepted syntax.  At
#   the end of the fade-in effect the output audio will have the same
#   volume as the input audio, at the end of the fade-out transition
#   the output audio will be silence.
#
# curve
#   Set curve for fade transition.
#   It accepts the following values:
#     tri	select triangular, linear slope (default)
#     qsin	select quarter of sine wave
#     hsin	select half of sine wave
#     esin	select exponential sine wave
#     log	select logarithmic
#     ipar	select inverted parabola
#     qua	select quadratic
#     cub	select cubic
#     squ	select square root
#     cbr	select cubic root
#     par	select parabola
#     exp	select exponential
#     iqsin	select inverted quarter of sine wave
#     ihsin	select inverted half of sine wave
#     dese	select double-exponential seat
#     desi	select double-exponential sigmoid
#
# Examples:
# Fade in first 1 second of audio:
# -af "afade=t=in:st=0:d=1"
# Fade out last 3 seconds of a 180 seconds audio:
# -af "afade=t=out:st=177:d=3"
#
#-----------------------------------------------------------------------------
# ATEMPO filter explanation:
# atempo - Adjust audio tempo.
#
# The filter accepts exactly one parameter, the audio tempo. If not
# specified then the filter will assume nominal 1.0 tempo. Tempo must be
# in the [0.5, 2.0] range.
#
# Examples:
# Slow down audio to 80% tempo:
# -af "atempo=0.8"
# To speed up audio to 125% tempo:
# -af atempo=1.25
#
##############################################################################
# AWK SAMPLES
#
##############################################################################
# SED SAMPLES
#
##############################################################################
# RSYNC OPTIONS
#
##############################################################################
# DIALOG SAMPLES
#
##############################################################################
# REPLACE STRINGS
#
##############################################################################
# EXIT CODES

##############################################################################
# SOURCE FILES
#
. /usr/local/etc/main.conf				# config main
#. $LLIBDIR/libfuncerr-sh				# functions error
. $LLIBDIR/libfuncstd-sh				# functions standard
. $LLIBDIR/libfuncmath-sh				# functions math
#. $LLIBDIR/libfuncip-sh				# functions IP
#. $LLIBDIR/libfunchart-sh				# functions chart
#. $LLIBDIR/libfunccddvd-sh				# functions cd-dvd
. /usr/local/etc/multimedia.conf			# config multimedia
#. /usr/local/etc/sample.conf				# config global

#if [ ! -e ~/.samplerc ]; then				# install config user
#	update-sample
#	exit 0
#fi
#. ~/.samplerc						# config user

# Select config global OR config user. Config user gets priority.
#if [ -e ~/.samplerc ]; then
#	. ~/.samplerc					# config user
#else
#	. /usr/local/etc/sample.conf			# config global
#fi

##############################################################################
# DECLARATION OF STANDARD CONSTANTS AND VARIABLES
#
readonly package=Multimedia				# package name
readonly package_file=ksm-multimedia			# package file name
readonly vinfopath="/usr/local/share/$package_file"	# path to package dir
readonly version=$(/bin/cat $vinfopath/VERSION)		# package version
readonly release=$(/bin/cat $vinfopath/RELEASE)		# package release
readonly scriptname=$(basename $0)			# name of script
#readonly tempfile=$TEMPDIR/$scriptname.$UID.tmp	# temporary file name
#tempfile=$(mktemp $TEMPDIR/$scriptname.$UID.XXXXXX)	# temporary file name
#readonly lockfile=$TEMPDIR/lock/$scriptname.$UID.lock	# lock file name
readonly debuglog="/usr/bin/logger -p user.debug -t $scriptname -- DEBUG:"	# log dbg msg
readonly errlog="/usr/bin/logger -p user.err -t $scriptname -- ERROR:"		# log err msg
readonly infolog="/usr/bin/logger -p user.info -t $scriptname -- INFO:"		# log inf msg
cmd=''							# sub command
debug=no						# debug=yes/no
verbose=''						# verbose=yes/''/no
err=${_ERR_OK_}						# error code

##############################################################################
# USER FILL IN AT FIRST RUN

##############################################################################
# SYSTEM

##############################################################################
# USER-CONSTANTS

##############################################################################
# USER-VARIABLES
#
# STRINGS
#DynProf[n]				from multimedia.conf

#args=''				# all args from cmdline
arg1=''					# 1. arg from cmdline
arg2=''					# 2. arg from cmdline
arg3=''					# 3. arg from cmdline
arg4=''					# 4. arg from cmdline
#opt_o=''				# option -o (yes)
					# option --output (yes)
#opt_i=''				# option -i (arg)
					# option --infile (arg)
#opt_o=''				# option -o (arg)
					# option --outfile (arg)
#opt_p=''				# option -p (arg)
					# option --pages (arg)
f=''

# NUMBERS
#p_first=0				# first page
#p_last=0				# last page

declare -i profile=0

# ARRAYS
# Indexed arrays are always zero based string arrays.
# If index starts with 1 and all members are assigned then
# count of members of array = last index of array
#declare -a arr
#maxarr="${#arr[@]}"			# get number of elements in arr
#unset arr[@]				# remove the entire array

#declare -a DynProf			# implizit done in local config
maxDynProf="${#DynProf[@]}"		# get number of elements in arr
maxprof=$[$maxDynProf - 1]		# range = 0 - n-1 = max idx

##############################################################################
# SIGNAL HANDLING

##############################################################################
# FUNCTIONS
##############################################################################
#
##############################################################################
# HELP
#
#=============================================================================
function helptext ()
{
# Output helptext
# Uses cat, echo, logger
# Return codes:
# Standard

if [ "$debug" == "yes" ]; then				# DEBUG
	echo "DEBUG helptext: $# $@" >&2
	$debuglog helptext: $# $@
fi

cat <<EOT
NAME
$scriptname - Apply 2 filters at once - dynaudnorm and volume.

SYNOPSIS
$scriptname [options] [prof vol inf [outf]]

<AUDIOSTREAM> | $scriptname [options] prof vol inf outf

DESCRIPTION
Apply the dynaudnorm filter and the volume filter to an audio file or
to an audio stream at once.

If <inf> is given but <outf> is not given then the outfile is generated
from <inf> prepended with 'n'.

If <inf> is a '-' then we process an incoming audio stream (type nut)
from STDIN.

If <outf> is a '-' then we stream the audio to STDOUT (stream type is
nut).

DYNAUDNORM FILTER
Dynamic Audio Normalizer.

dynaudnorm=<f>:<g>:<p>:<m>:<r>:<n>:<c>:<b>:<s>

This filter applies a certain amount of gain to the input audio in
order to bring its peak magnitude to a target level (e.g. 0 dBFS).
However, in contrast to more "simple" normalization algorithms, the
Dynamic Audio Normalizer *dynamically* re-adjusts the gain factor to
the input audio. This allows for applying extra gain to the "quiet"
sections of the audio while avoiding distortions or clipping the "loud"
sections. In other words: The Dynamic Audio Normalizer will "even out"
the volume of quiet and loud sections, in the sense that the volume of
each section is brought to the same target level. Note, however, that
the Dynamic Audio Normalizer achieves this goal *without* applying
"dynamic range compressing". It will retain 100% of the dynamic range
*within* each section of the audio file.

The dynaudnorm filter flags and their meanings:

  f Set the frame length in milliseconds. In range from 10 to 8000
    milliseconds. Default is 500 milliseconds. The Dynamic Audio
    Normalizer processes the input audio in small chunks, referred to
    as frames. This is required, because a peak magnitude has no
    meaning for just a single sample value. Instead, we need to
    determine the peak magnitude for a contiguous sequence of sample
    values. While a "standard" normalizer would simply use the peak
    magnitude of the complete file, the Dynamic Audio Normalizer
    determines the peak magnitude individually for each frame. The
    length of a frame is specified in milliseconds. By default, the
    Dynamic Audio Normalizer uses a frame length of 500 milliseconds,
    which has been found to give good results with most files. Note
    that the exact frame length, in number of samples, will be
    determined automatically, based on the sampling rate of the
    individual input audio file.

  g Set the Gaussian filter window size. In range from 3 to 301, must
    be odd number. Default is 31. Probably the most important
    parameter of the Dynamic Audio Normalizer is the "window size" of
    the Gaussian smoothing filter. The filter's window size is
    specified in frames, centered around the current frame. For the
    sake of simplicity, this must be an odd number. Consequently, the
    default value of 31 takes into account the current frame, as well
    as the 15 preceding frames and the 15 subsequent frames. Using a
    larger window results in a stronger smoothing effect and thus in
    less gain variation, i.e. slower gain adaptation. Conversely, using
    a smaller window results in a weaker smoothing effect and thus in
    more gain variation, i.e. faster gain adaptation. In other words,
    the more you increase this value, the more the Dynamic Audio
    Normalizer will behave like a "traditional" normalization filter.
    On the contrary, the more you decrease this value, the more the
    Dynamic Audio Normalizer will behave like a dynamic range
    compressor.

  p Set the target peak value. This specifies the highest permissible
    magnitude level for the normalized audio input. This filter will
    try to approach the target peak magnitude as closely as possible,
    but at the same time it also makes sure that the normalized signal
    will never exceed the peak magnitude. A frame's maximum local gain
    factor is imposed directly by the target peak magnitude. The
    default value is 0.95 and thus leaves a headroom of 5%*. It is not
    recommended to go above this value.
         
  m Set the maximum gain factor. In range from 1.0 to 100.0. Default is
    10.0. The Dynamic Audio Normalizer determines the maximum possible
    (local) gain factor for each input frame, i.e. the maximum gain
    factor that does not result in clipping or distortion. The maximum
    gain factor is determined by the frame's highest magnitude sample.
    However, the Dynamic Audio Normalizer additionally bounds the
    frame's maximum gain factor by a predetermined (global) maximum
    gain factor. This is done in order to avoid excessive gain factors
    in "silent" or almost silent frames. By default, the maximum gain
    factor is 10.0, For most inputs the default value should be
    sufficient and it usually is not recommended to increase this
    value. Though, for input with an extremely low overall volume
    level, it may be necessary to allow even higher gain factors. Note,
    however, that the Dynamic Audio Normalizer does not simply apply a
    "hard" threshold (i.e. cut off values above the threshold).
    Instead, a "sigmoid" threshold function will be applied. This way,
    the gain factors will smoothly approach the threshold value, but
    never exceed that value.

  r Set the target RMS. In range from 0.0 to 1.0. Default is 0.0 -
    disabled. By default, the Dynamic Audio Normalizer performs "peak"
    normalization. This means that the maximum local gain factor for
    each frame is defined (only) by the frame's highest magnitude
    sample. This way, the samples can be amplified as much as possible
    without exceeding the maximum signal level, i.e. without clipping.
    Optionally, however, the Dynamic Audio Normalizer can also take
    into account the frame's root mean square, abbreviated RMS. In
    electrical engineering, the RMS is commonly used to determine the
    power of a time-varying signal. It is therefore considered that the
    RMS is a better approximation of the "perceived loudness" than just
    looking at the signal's peak magnitude. Consequently, by adjusting
    all frames to a constant RMS value, a uniform "perceived loudness"
    can be established. If a target RMS value has been specified, a
    frame's local gain factor is defined as the factor that would
    result in exactly that RMS value. Note, however, that the maximum
    local gain factor is still restricted by the frame's highest
    magnitude sample, in order to prevent clipping.

  n Disable channels coupling. By default is enabled. By default, the
    Dynamic Audio Normalizer will amplify all channels by the same
    amount. This means the same gain factor will be applied to all
    channels, i.e. the maximum possible gain factor is determined by
    the "loudest" channel. However, in some recordings, it may happen
    that the volume of the different channels is uneven, e.g. one
    channel may be "quieter" than the other one(s). In this case, this
    option can be used to disable the channel coupling. This way, the
    gain factor will be determined independently for each channel,
    depending only on the individual channel's highest magnitude
    sample. This allows for harmonizing the volume of the different
    channels.

  c Enable DC bias correction. By default is disabled. An audio signal
    (in the time domain) is a sequence of sample values. In the
    Dynamic Audio Normalizer these sample values are represented in the
    -1.0 to 1.0 range, regardless of the original input format.
    Normally, the audio signal, or "waveform", should be centered
    around the zero point. That means if we calculate the mean value
    of all samples in a file, or in a single frame, then the result
    should be 0.0 or at least very close to that value. If, however,
    there is a significant deviation of the mean value from 0.0, in
    either positive or negative direction, this is referred to as a DC
    bias or DC offset. Since a DC bias is clearly undesirable, the
    Dynamic Audio Normalizer provides optional DC bias correction.
    With DC bias correction enabled, the Dynamic Audio Normalizer will
    determine the mean value, or "DC correction" offset, of each input
    frame and subtract that value from all of the frame's sample values
    which ensures those samples are centered around 0.0 again. Also, in
    order to avoid "gaps" at the frame boundaries, the DC correction
    offset values will be interpolated smoothly between neighbouring
    frames.

  b Enable alternative boundary mode. By default is disabled. The
    Dynamic Audio Normalizer takes into account a certain neighbourhood
    around each frame. This includes the preceding frames as well as
    the subsequent frames. However, for the "boundary" frames, located
    at the very beginning and at the very end of the audio file, not
    all neighbouring frames are available. In particular, for the first
    few frames in the audio file, the preceding frames are not known.
    And, similarly, for the last few frames in the audio file, the
    subsequent frames are not known. Thus, the question arises which
    gain factors should be assumed for the missing frames in the
    "boundary" region. The Dynamic Audio Normalizer implements two
    modes to deal with this situation. The default boundary mode
    assumes a gain factor of exactly 1.0 for the missing frames,
    resulting in a smooth "fade in" and "fade out" at the beginning and
    at the end of the input, respectively.

  s Set the compress factor. In range from 0.0 to 30.0. Default is 0.0.
    By default, the Dynamic Audio Normalizer does not apply
    "traditional" compression. This means that signal peaks will not be
    pruned and thus the full dynamic range will be retained within each
    local neighbourhood. However, in some cases it may be desirable to
    combine the Dynamic Audio Normalizer's normalization algorithm with
    a more "traditional" compression. For this purpose, the Dynamic
    Audio Normalizer provides an optional compression (thresholding)
    function. If (and only if) the compression feature is enabled, all
    input frames will be processed by a soft knee thresholding function
    prior to the actual normalization process. Put simply, the
    thresholding function is going to prune all samples whose magnitude
    exceeds a certain threshold value. However, the Dynamic Audio
    Normalizer does not simply apply a fixed threshold value. Instead,
    the threshold value will be adjusted for each individual frame. In
    general, smaller parameters result in stronger compression, and
    vice versa. Values below 3.0 are not recommended, because audible
    distortion may appear.


The dynaudnorm profiles:

In the config file multimedia.conf setup profiles like:

  DynProf[x]="f:g:p:m:r:n:c:b:s"

The value 'x' has to be a contignous number starting with 0.

For the flags use your values (for the default give a hyphen '-' ).
All nine flags have to be set. Have a look at the config file.


VOLUME FILTER
Adjust the input audio volume:

volume='volume=1.0dB:precision=float:replaygain=drop:replaygain_preamp=0.0:
eval=once:n=?:nb_channels=2:nb_consumed_samples=?:nb_samples=?:pos=?:
pts=?:sample_rate=?:startpts=?:startt=?:t=?:tb=?:volume=?'

It accepts the following parameters:

volume  
    Set audio volume expression.

    Output values are clipped to the maximum value.

    The output audio volume is given by the relation:

     <output_volume> = <volume> * <input_volume>

    The default value for volume is "1.0".

precision  
    This parameter represents the mathematical precision.

    It determines which input sample formats will be allowed, which
    affects the precision of the volume scaling.

    fixed  
      8-bit fixed-point; this limits input sample format to U8, S16,
      and S32.

    float  
      32-bit floating-point; this limits input sample format to FLT.
      (default)

    double  
      64-bit floating-point; this limits input sample format to DBL.

replaygain  
    Choose the behaviour on encountering ReplayGain side data in input
    frames.

    drop  
      Remove ReplayGain side data, ignoring its contents (the
      default).

    ignore  
      Ignore ReplayGain side data, but leave it in the frame.

    track  
      Prefer the track gain, if present.

    album  
      Prefer the album gain, if present.

replaygain_preamp  
    Pre-amplification gain in dB to apply to the selected replaygain
    gain.

    Default value for replaygain_preamp is 0.0.

eval  
    Set when the volume expression is evaluated.

    It accepts the following values:

    once  
      only evaluate expression once during the filter initialization,
      or when the volume command is sent

    frame  
      evaluate expression for each incoming frame

    Default value is once.

The volume expression can contain the following parameters.

n   frame number (starting at zero)

nb_channels  
    number of channels

nb_consumed_samples  
    number of samples consumed by the filter

nb_samples  
    number of samples in the current frame

pos  
    original frame position in the file

pts  
    frame PTS

sample_rate  
    sample rate

startpts  
    PTS at start of stream

startt  
    time at start of stream

t   frame time

tb  timestamp timebase

volume  
    last set volume value

Note that when eval is set to once only the sample_rate and tb
variables are available, all other variables will evaluate to NAN.


Commands

This filter supports the following commands:

volume  
    Modify the volume expression. The command accepts the same syntax
    of the corresponding option.

    If the specified expression is not valid, it is kept at its current
    value.

replaygain_noclip  
    Prevent clipping by limiting the gain applied.

    Default value for replaygain_noclip is 1.


Examples:

Halve the input audio volume:

    volume=volume=0.5
    volume=volume=1/2
    volume=volume=-6.0206dB

    In all the above example the named key for volume can be omitted,
    for example like in:

    volume=0.5

Increase input audio power by 6 decibels using fixed-point precision:

    volume=volume=6dB:precision=fixed

Fade volume after time 10 with an annihilation period of 5 seconds:

    volume='if(lt(t,10),1,max(1-(t-10)/5,0))':eval=frame

OPTIONS
Gnu style options:

  -h|--help    = Output help, then exit.
  -V|--version = Output version info, then exit.
  -d|--debug   = Turn on DEBUG mode.
  -q|--quiet   = Be quiet.
  -v|--verbose = Be verbose.
  -a           = Process all MP3s in actual dir
                 (supply only <prof> and <vol>).
  --           = End of options.

ARGUMENTS
Give if needed:

  prof = The profile to use.
  vol  = The volume to supply in dB or whole parameter string
         6|'volume=1.0dB:replaygain=drop:replaygain_preamp=0.0:...'
  inf  = The input file name or a '-'
  outf = The output file name or a '-'

EXAMPLES
For instance:

  Use the filter with profile=0 and volume=3:
  $scriptname 0 3 INFILE.mp3 OUTFILE.mp3

EXIT STATUS
On program exit:

  Code 0 = OK

  See output of 'showerrmsg'.

FILES
Used directories and files:

  Config global = /usr/local/etc/multimedia.conf

SEE ALSO
Related manpages:

  showerrmsg(1), ffmpeg(1), media2media(1),
  audiofilter-bandpass(1), audiofilter-bandreject(1), 
  audiofilter-bass(1), audiofilter-chorus(1), 
  audiofilter-compand(1), audiofilter-crossfade(1), 
  audiofilter-dynaudnorm(1), 
  audiofilter-earwax(1), audiofilter-echo(1), 
  audiofilter-extrastereo(1), audiofilter-fade(1), 
  audiofilter-flanger(1), audiofilter-gate(1), 
  audiofilter-highpass(1), audiofilter-limiter(1), 
  audiofilter-lowpass(1), audiofilter-phaser(1), 
  audiofilter-pulsator(1), audiofilter-rubberband(1), 
  audiofilter-stereotools(1), audiofilter-stereowiden(1), 
  audiofilter-tempo(1), audiofilter-treble(1), 
  audiofilter-tremolo(1), audiofilter-vibrato(1), 
  audiofilter-volcomp(1), audiofilter-volume(1).

AUTHORS
Juergen Kaesmann <juergen@kaesmann-online.de>
EOT
#  -o|--output  = Output to STDOUT.
#  -i f         = Input from file <f>.
#  --infile=f   = Input from file <f>.
#  -o f         = Output to file <f>.
#  --outfile=f  = Output to file <f>.
#  -p s,e       = Pages s=START e=END.
#  --pages=s,e  = Pages s=START e=END.

return
}

#=============================================================================
function mainproc ()
{
# Apply dynaudnorm filter and volume filter to the given file.
# par1 = dynaudnorm profile
# par2 = vol
# par3 = inf
# Uses echo, logger
# Return codes:
# Standard

if [ "$debug" == "yes" ]; then				# DEBUG
	echo "DEBUG mainproc: $# $@" >&2
	$debuglog mainproc: $# $@
fi

local vol="$2" inf="$3" outf=""
local f="" g="" p="" m="" r="" n="" c="" b="" s="" fstr=""
local profile="$1" ret=0
#local -a arr
#maxarr="${#arr[@]}"			# get number of elements in arr
#unset arr[@]				# remove the entire array

if [ "$inf" != '-' ] && [ -z "$arg4" ]; then
	outf="n${inf}"
else
	outf="$arg4"
fi

# From local config: DynProf[n]="f:g:p:m:r:n:c:b:s"

# get values from profile
f=$(echo "${DynProf[$profile]}" | cut -d ':' -f 1)
g=$(echo "${DynProf[$profile]}" | cut -d ':' -f 2)
p=$(echo "${DynProf[$profile]}" | cut -d ':' -f 3)
m=$(echo "${DynProf[$profile]}" | cut -d ':' -f 4)
r=$(echo "${DynProf[$profile]}" | cut -d ':' -f 5)
n=$(echo "${DynProf[$profile]}" | cut -d ':' -f 6)
c=$(echo "${DynProf[$profile]}" | cut -d ':' -f 7)
b=$(echo "${DynProf[$profile]}" | cut -d ':' -f 8)
s=$(echo "${DynProf[$profile]}" | cut -d ':' -f 9)

# build filterstring
if [ "$f" != "-" ]; then fstr="f=$f"; fi
if [ "$g" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="g=$g"
	else
		fstr="$fstr:g=$g"
	fi
fi
if [ "$p" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="p=$p"
	else
		fstr="$fstr:p=$p"
	fi
fi
if [ "$m" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="m=$m"
	else
		fstr="$fstr:m=$m"
	fi
fi
if [ "$r" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="r=$r"
	else
		fstr="$fstr:r=$r"
	fi
fi
if [ "$n" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="n=$n"
	else
		fstr="$fstr:n=$n"
	fi
fi
if [ "$c" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="c=$c"
	else
		fstr="$fstr:c=$c"
	fi
fi
if [ "$b" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="b=$b"
	else
		fstr="$fstr:b=$b"
	fi
fi
if [ "$s" != "-" ]; then
	if [ -z "$fstr" ]; then
		fstr="s=$s"
	else
		fstr="$fstr:s=$s"
	fi
fi

if [ -n "$fstr" ]; then
	fstr="=$fstr"
fi

IsNumber "$vol"
ret=$?
if [ $ret -eq 0 ]; then					# only vol given
	vol="${vol}dB"
fi

if [ "$inf" != '-' ]; then				# input from file
	if [ "$outf" != '-' ]; then			# store to file
		case "$verbose" in
		    yes)
			echo "Run $scriptname"		# allow STDOUT and STDERR
			ffmpeg -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 $outf
			ret=$?
			;;
		    '')
			#echo "Run $scriptname"		# suppress STDOUT or STDERR
			ffmpeg -hide_banner -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 $outf 2>/dev/null
			#1>/dev/null
			ret=$?
			;;
		    no)					# suppress STDOUT and STDERR
			ffmpeg -hide_banner -y -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 $outf &>/dev/null
			ret=$?
			;;
		esac
	else						# output to STDOUT
		ffmpeg -hide_banner -y -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 -f nut - 2>/dev/null
		ret=$?
	fi
else							# input from STDIN
	if [ "$outf" != '-' ]; then			# store to file
		case "$verbose" in
		    yes)
			echo "Run $scriptname"		# allow STDOUT and STDERR
			ffmpeg -f nut -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 $outf
			ret=$?
			;;
		    '')
			#echo "Run $scriptname"		# suppress STDOUT or STDERR
			ffmpeg -hide_banner -f nut -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 $outf 2>/dev/null
			#1>/dev/null
			ret=$?
			;;
		    no)					# suppress STDOUT and STDERR
			ffmpeg -hide_banner -y -f nut -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 $outf &>/dev/null
			ret=$?
			;;
		esac
	else						# output to STDOUT
		ffmpeg -hide_banner -y -f nut -i $inf -af "dynaudnorm${fstr}, volume=${vol}" -c:v copy -id3v2_version 3 -write_id3v1 1 -f nut - 2>/dev/null
		ret=$?
	fi
fi

if [ "$ret" -ne 0 ]; then				# from ffmpeg
	if [ "$verbose" != no ]; then
		echo "${ErrMsg[${_ERR_GENERAL_}]} $ret from ffmpeg!" >&2
	else
		$errlog ${ErrMsg[${_ERR_GENERAL_}]} $ret from ffmpeg
	fi
	ret=${_ERR_GENERAL_}
fi

return $ret
}

##############################################################################
# GET OPTIONS
##############################################################################
# Give 2 strings and all cmdline_args to function GetOpt
# (POSIX_options Gnu_long_options cmdline_args):
# GetOpt 'hVdqvi:m:' 'help version debug quiet verbose infile= myval=' $@
#
# A ':' after a POSIX option means this option needs an arg.
# A '=' after a Gnu long option means this option needs an arg.
#
# All cmd/options have to be initialised to ''!
#
# POSIX options are classified in 2 categories:
#   a) cmds (mutually exclusive). All cmds are given to var 'cmd'.
#   b) options (as many as you like). Each option is given to it's own var.
# As a general rule: If cmds/options are given multiple then last given wins.
#
# Options can take 1 argument (no whitespace in arg). If you want to give more
# than 1 arg or want to use multiple strings as 1 arg then concatenate them
# together with ',' as delimiter:
# sampleprog -m val1,val2
# sampleprog --myval=val1,val2
# It's at the programmers responsibility to do meaningfull things with the
# content of the options.
#
# If err != 0 then all args are processed, else continue to look for options.
# When GetOpt finishes then all options are removed from cmdline.
#
if [ "$debug" == "yes" ]; then				# DEBUG
	echo "DEBUG Parse Commandline: $# $@" >&2
	$debuglog Parse Commandline: $# $@
fi
#
err=${_ERR_OK_}
while [ "$err" -eq 0 ]; do
	GetOpt 'hVdqva' 'help version debug quiet verbose' $@
	err=$?
	#echo "DEBUG GET OPTIONS: err=${err}  _OPTNAME_=${_OPTNAME_}  _OPTVAL_=${_OPTVAL_}  _OPTPOS_=${_OPTPOS_}  _OPTIDX_=${_OPTIDX_}" >&2
	if [ $err -eq 0 ]; then
		case "$_OPTNAME_" in
		    h|help)	cmd="-h";;
		    V|version)	cmd="-V";;
		    a)		cmd="-${_OPTNAME_}";;
		    d|debug)	debug=yes;;
		    q|quiet)	verbose=no;;
		    v|verbose)	verbose=yes;;
#		    o|output)	opt_o=yes;;
#		    i|infile)	opt_i="${_OPTVAL_}";;
#		    o|outfile)	opt_o="${_OPTVAL_}";;
#		    p|pages)	opt_p="${_OPTVAL_}";;
		    --)		break;;
		    :)	if [ "$verbose" != no ]; then
				echo "${ErrMsg[${_ERR_MISSING_ARG_FOR_OPT_}]} -${_OPTVAL_}" >&2
			else
				$errlog ${ErrMsg[${_ERR_MISSING_ARG_FOR_OPT_}]} -${_OPTVAL_}
			fi; exit ${_ERR_MISSING_ARG_FOR_OPT_};;
		    ?)	if [ "$verbose" != no ]; then
				echo "${ErrMsg[${_ERR_UNKNOWN_OPT_}]} -${_OPTVAL_}" >&2
			else
				$errlog ${ErrMsg[${_ERR_UNKNOWN_OPT_}]} -${_OPTVAL_}
			fi; exit ${_ERR_UNKNOWN_OPT_};;
		    *)	if [ "$verbose" != no ]; then
				echo "${ErrMsg[${_ERR_UNKNOWN_ERR_}]} _OPTNAME_=${_OPTNAME_} _OPTVAL_=${_OPTVAL_}" >&2
			else
				$errlog ${ErrMsg[${_ERR_UNKNOWN_ERR_}]} _OPTNAME_=${_OPTNAME_} _OPTVAL_=${_OPTVAL_}
			fi; exit ${_ERR_UNKNOWN_ERR_};;
		esac
	fi
done
err=${_ERR_OK_}
shift ${_OPTIDX_}				# rm all opts from cmdline
#
# Uncomment if giving a cmd is mandatory
#if [ -z "$cmd" ]; then					# error no cmd given
#	if [ "$verbose" != no ]; then
#		echo "${ErrMsg[7]}" >&2
#	else
#		$errlog ${ErrMsg[7]}
#	fi
#	exit 7
#fi

##############################################################################
# CHECK LINK NAMES

##############################################################################
# CHECK OPTIONS

##############################################################################
# CHECK VERSION

##############################################################################
# CHECK ARGUMENTS
#
case "$cmd" in
    '')
	if [ "$#" -eq 3 ]; then				# parameter count ok
		arg1="$1"
		arg2="$2"
		arg3="$3"
		shift 3
		#args="$@"
		#shift $#
	elif [ "$#" -eq 4 ]; then			# parameter count ok
		arg1="$1"
		arg2="$2"
		arg3="$3"
		arg4="$4"
		shift 4
	else						# error in param count
		if [ "$verbose" != no ]; then
			echo "${ErrMsg[${_ERR_WRONG_PAR_COUNT_}]}" >&2
		else
			$errlog ${ErrMsg[${_ERR_WRONG_PAR_COUNT_}]}
		fi
		exit ${_ERR_WRONG_PAR_COUNT_}
	fi
	;;
    -a)
	if [ "$#" -eq 2 ]; then				# parameter count ok
		arg1="$1"
		arg2="$2"
		shift 2
	else						# error in param count
		if [ "$verbose" != no ]; then
			echo "${ErrMsg[${_ERR_WRONG_PAR_COUNT_}]}" >&2
		else
			$errlog ${ErrMsg[${_ERR_WRONG_PAR_COUNT_}]}
		fi
		exit ${_ERR_WRONG_PAR_COUNT_}
	fi
	;;
    *)
	if [ "$#" -ne 0 ]; then				# error in param count
		if [ "$verbose" != no ]; then
			echo "${ErrMsg[${_ERR_WRONG_PAR_COUNT_}]}" >&2
		else
			$errlog ${ErrMsg[${_ERR_WRONG_PAR_COUNT_}]}
		fi
		exit ${_ERR_WRONG_PAR_COUNT_}
	fi
	;;
esac

##############################################################################
# LOCK

##############################################################################
# MAINPROGRAM
##############################################################################
#
if [ "$debug" == "yes" ]; then				# DEBUG
	echo "DEBUG Main: $# $@" >&2
	$debuglog Main: $# $@
fi

profile="$arg1"
if [ "$profile" -lt 0 ] || [ "$profile" -gt $maxprof ]; then
	if [ "$verbose" != no ]; then
		echo "${ErrMsg[9]} profile ${profile} !" >&2
	else
		$errlog ${ErrMsg[9]} profile ${profile}
	fi
	exit 9
fi

# Check what to do (sub commands, options and args are removed from cmdline)
case "$cmd" in
    '')
	mainproc $arg1 $arg2 $arg3
	err=$?
	;;
    -h)
	helptext
	exit 0
	;;
    -V)
	echo "${scriptname}: (${package}) $package_file ${version}-${release}"
	exit 0
	;;
    -a)
	for f in *.mp3; do
		mainproc $arg1 $arg2 "$f"
	done
	err=$?
	;;
    *)							# error
	if [ "$verbose" != no ]; then
		echo "${ErrMsg[${_ERR_SUB_CMD_NOT_FOUND_}]}" >&2
	else
		$errlog ${ErrMsg[${_ERR_SUB_CMD_NOT_FOUND_}]}
	fi
	err=${_ERR_SUB_CMD_NOT_FOUND_}
	;;
esac

#rm -f $lockfile

exit $err
