diff --git a/CMakeLists.txt b/CMakeLists.txt index cbd02f45..ff1b15f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory(src/eid) add_subdirectory(src/esdru) add_subdirectory(src/fir) add_subdirectory(src/freqresp) +add_subdirectory(src/gain_chk) add_subdirectory(src/g711) add_subdirectory(src/g711iplc) add_subdirectory(src/g722) diff --git a/doc/manual/Makefile b/doc/manual/Makefile index 5b907ea4..b9a48712 100644 --- a/doc/manual/Makefile +++ b/doc/manual/Makefile @@ -1,7 +1,7 @@ BIBTEX = bibtex PDFLATEX = pdflatex -PARTS = STLmanual.tex intro.tex g711.tex g711iplc.tex g726.tex g727.tex g728.tex g722.tex rpe.tex rate.tex eid.tex mnru.tex sv56.tex reverb.tex truncate.tex freqresp.tex stereoop.tex esdru.tex bs1770demo.tex basop.tex utl.tex wmc_tool.tex +PARTS = STLmanual.tex intro.tex g711.tex g711iplc.tex g726.tex g727.tex g728.tex g722.tex rpe.tex rate.tex eid.tex mnru.tex sv56.tex reverb.tex truncate.tex freqresp.tex stereoop.tex esdru.tex bs1770demo.tex gain_chk.tex basop.tex utl.tex wmc_tool.tex STLmanual.pdf : $(PARTS) bibliography.bib $(PDFLATEX) $(PARTS) diff --git a/doc/manual/STLmanual.tex b/doc/manual/STLmanual.tex index a9cc935a..6d93605c 100644 --- a/doc/manual/STLmanual.tex +++ b/doc/manual/STLmanual.tex @@ -182,6 +182,11 @@ %============================================================================= \include{freqresp} +%============================================================================= +% chapter gain_chk: Gain amplification verification tool +%============================================================================= +\include{gain_chk} + %============================================================================= % chapter stereoop: Stereo processing tool %============================================================================= diff --git a/doc/manual/gain_chk.tex b/doc/manual/gain_chk.tex new file mode 100644 index 00000000..aa44a00c --- /dev/null +++ b/doc/manual/gain_chk.tex @@ -0,0 +1,228 @@ +%============================================================================= +% THIS IS chapter{Gain amplification verification tool} +% +% May 2026 - created for STL integration +%============================================================================= +\chapter{Gain amplification verification tool} +\label{ch:gain_chk} +%============================================================================= + +\section{Introduction} +\label{sec:gain_chk-intro} + +The \texttt{gain\_chk} tool compares a reference signal and a processed +signal and reports short-time gain differences per frequency band. The +tool is intended for objective verification of spectral gain behavior in +speech and audio processing chains. + +Both inputs are raw 16-bit PCM files. The processing is frame-based with +20 ms frames and supports sampling frequencies 8, 16, 32 and 48 kHz. +Optionally, a VAD file can be provided to separate statistics for active +and inactive frames. + +\section{Description of the algorithm} +\label{sec:gain_chk-algorithm} + +\subsection{Frame and spectrum analysis} + +Let $x_1[n]$ be the reference frame and $x_2[n]$ be the processed frame, +with frame length $L=\frac{F_s}{50}$ samples (20 ms). Each frame is +windowed with a Hamming window +\begin{equation} +w[n] = 0.54 - 0.46\cos\left(\frac{2\pi n}{L-1}\right), \quad n=0,\ldots,L-1. +\end{equation} + +The tool computes the real FFT of each windowed frame after zero-padding +to the next power-of-two length. For each frequency bin $k$, the spectral +energy is +\begin{equation} +P_i[k] = \Re\{X_i[k]\}^2 + \Im\{X_i[k]\}^2, \quad i \in \{1,2\}. +\end{equation} + +\subsection{Band energy accumulation} + +The analysis bands are defined by upper frequency limits in the +\texttt{bands[]} table. Values in $(0,1]$ are interpreted as normalized +fractions of Nyquist ($F_s/2$). For each band $b$, the tool accumulates +average bin energy: +\begin{equation} +E_i^{(b)} = \frac{1}{N_b}\sum_{k \in \mathcal{B}_b} P_i[k], +\end{equation} +where $\mathcal{B}_b$ is the set of bins inside the band and $N_b$ its +number of bins. + +For each frame class (all/active/inactive), band energies are accumulated +across frames: +\begin{equation} +\bar{E}_i^{(b)} = \sum_{\mbox{\scriptsize frames}} E_i^{(b)}. +\end{equation} + +\subsection{Reported gain measures} + +For each band, the gain ratio is +\begin{equation} +R^{(b)} = \frac{\bar{E}_2^{(b)} + E_{\min}}{\bar{E}_1^{(b)} + E_{\min}}, +\end{equation} +where $E_{\min}$ is a small offset used for numerical robustness. + +The reported outputs are: +\begin{equation} +G_{\mathrm{dB}}^{(b)} = 10\log_{10}\left(R^{(b)}\right), +\end{equation} +\begin{equation} +G_{\%}^{(b)} = \left(R^{(b)} - 1\right)\cdot 100. +\end{equation} + +The same ratio is also computed on the total energy over all bands. + +\subsection{VAD-conditioned processing} + +When a VAD file is present (\texttt{-v}), one 16-bit flag is read per +frame. Frames with non-zero VAD are treated as active. Frames with zero +VAD are treated as inactive. + +For inactive frames, a low-energy gate is applied on the reference frame: +if mean time-domain energy is below a fixed threshold, that frame is not +included in spectral statistics. This avoids unstable ratios in near-silent +segments. + +\subsection{Threshold check mode} + +When threshold options are enabled, two checks are available: +\begin{itemize} +\item \texttt{-a}: maximum allowed positive gain (amplification) in dB for active frames; +\item \texttt{-s}: maximum allowed attenuation in dB for inactive frames (requires VAD). +\end{itemize} + +The tool returns a numeric code intended for automation scripts: +\begin{itemize} +\item 1: active fail and inactive fail; +\item 2: active pass and inactive fail; +\item 3: active fail and inactive pass; +\item 4: active pass and inactive pass. +\end{itemize} + +\section{Implementation} +\label{sec:gain_chk-impl} + +\subsection{Data and file format} + +The tool expects 16-bit PCM input files in native host byte order, +consistent with other STL command-line tools. + +Mandatory input/output arguments: +\begin{itemize} +\item \texttt{-i} reference PCM file; +\item \texttt{-o} processed PCM file; +\item \texttt{-t} output text file; +\item \texttt{-r} sampling frequency in Hz. +\end{itemize} + +Optional arguments: +\begin{itemize} +\item \texttt{-v} VAD file (one 16-bit flag per frame); +\item \texttt{-a} active-frame amplification threshold (dB); +\item \texttt{-s} inactive-frame attenuation threshold (dB). +\end{itemize} + +\subsection{Output file format} + +The results text file (\texttt{-t}) is opened in append mode. For each +invocation, the tool writes: + +\begin{enumerate} +\item The command line used. +\item A header line indicating the frame class (\texttt{For all Frames}, + \texttt{For Active Frames}, or \texttt{For Inactive Frames}). +\item One line per frequency band: +\begin{verbatim} +Ener Ratio (Out/In) for critical band ( Hz - Hz) = dB ( %) +\end{verbatim} +\item A full-spectrum summary line covering band $0$--$F_s/2$. +\item A separator line of dashes. +\end{enumerate} + +\noindent When a VAD file is provided, the above block is repeated +separately for active and inactive frame classes. + +\subsection{Command line syntax} + +{\tt {\small +\begin{tabular}{llll} +gain\_chk & -i ref.pcm & -o proc.pcm & -t results.txt \\ + & -r Fs & [-v vad.bin] & [-a A\_dB] [-s S\_dB] \\ +\end{tabular} +}} + +\subsection{Processing flow} + +Pseudo code: +{\tt\small +\begin{verbatim} +Read and validate command line +Initialize frame length, bands, and Hamming window +for each complete 20 ms frame in both input files: + read reference and processed PCM frame + read VAD flag if provided (else active by default) + optionally skip low-energy inactive frame + apply Hamming window and zero padding + compute FFT of both frames + for each frequency band: + accumulate average spectral energy in reference/processed +After loop: + print per-band and total gain in dB and percent + optionally evaluate thresholds and return status code +\end{verbatim} +} + +\section{Tests and portability} +\label{sec:gain_chk-tests} + +The STL CMake integration defines regression tests for \texttt{gain\_chk} +using bundled PCM material. Tests run at 48 kHz and compare expected tool +output against reference files. + +The implementation is ANSI C style and uses standard C library calls plus +math functions (\texttt{log}, \texttt{log10}, \texttt{cos}). It is portable +across common Unix-like and Windows toolchains supported by STL. + +\section{Example code} +\label{sec:gain_chk-example} + +The demonstration program is \texttt{gain\_chk.c}. It implements command +line parsing, frame-based spectral analysis, optional VAD-conditioned +statistics, report generation, and threshold-based return codes. + +A typical invocation comparing a reference and processed file at 48~kHz: + +{\tt {\small +\begin{verbatim} +$ gain_chk -i ref.pcm -o processed.pcm -t results.txt -r 48000 +\end{verbatim} +}} + +\noindent Sample output written to \texttt{results.txt}: + +{\tt {\small +\begin{verbatim} +For all Frames +Ener Ratio (Out/In) for critical band 0 ( 0 Hz - 4000 Hz) = -1.49 dB ( -29.02%) +Ener Ratio (Out/In) for critical band 1 ( 4000 Hz - 8000 Hz) = -6.58 dB ( -78.00%) +Ener Ratio (Out/In) for critical band 2 ( 8000 Hz - 16000 Hz) = -35.58 dB ( -99.97%) +Ener Ratio (Out/In) for critical band 3 (16000 Hz - 20000 Hz) = -16.10 dB ( -97.55%) +Ener Ratio (Out/In) for the Full Spectrum( 0 Hz - 20000 Hz) = -1.55 dB ( -30.02%) +\end{verbatim} +}} + +\noindent With threshold checking enabled for scripted pass/fail: + +{\tt {\small +\begin{verbatim} +$ gain_chk -i ref.pcm -o processed.pcm -t results.txt -r 48000 \ + -v vad.bin -a 3.0 -s 6.0 +$ echo $? +4 +\end{verbatim} +}} + +\noindent Exit code~4 indicates both active and inactive thresholds passed. diff --git a/src/gain_chk/CMakeLists.txt b/src/gain_chk/CMakeLists.txt new file mode 100644 index 00000000..5f2cf407 --- /dev/null +++ b/src/gain_chk/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(gain_chk gain_chk.c) +target_link_libraries(gain_chk ${M_LIBRARY}) + +set(_gain_chk_testdir "${CMAKE_CURRENT_BINARY_DIR}/gain_chk_ctest_scratch") +file(MAKE_DIRECTORY "${_gain_chk_testdir}") + +# Reference PCM: input48c.pcm; processed: syn.{a,b,c,d}.pcm; stderr vs gain_chk_stderr_*.ref +foreach(_case self a b c d) + add_test( + NAME gain_chk_${_case} + COMMAND ${CMAKE_COMMAND} + -DGAIN_CHK=$ + -DPCM_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test_data + -DWORKDIR=${_gain_chk_testdir} + -DCASE=${_case} + -P ${CMAKE_CURRENT_SOURCE_DIR}/test_data/gain_chk_ctest.cmake + ) +endforeach() diff --git a/src/gain_chk/Gain_Chk_Release_Notes.txt b/src/gain_chk/Gain_Chk_Release_Notes.txt new file mode 100644 index 00000000..58e73855 --- /dev/null +++ b/src/gain_chk/Gain_Chk_Release_Notes.txt @@ -0,0 +1,70 @@ +/*---------------------------------------------------------------------------* + * Gain Amplification Verification tool * + * ------------------------------------ * + * * + * Version: 3.0 * + * Revision Date: October 17, 2012 * + *---------------------------------------------------------------------------*/ + + +Changes from V2.0 to V3.0 +========================= + +1) #define OUTPUT_VALUES + This activates the output of values to the screen, printed without headers. + They are printed as is (either 4 or 8 values if VAD file is used). + +2) #define THRESHOLD_CHECK + This introduces the two new switches "-a XX" "-s YY". + The switch -a specifies active signal max amplification XX in dB. + The switch -i specifies incatve (silence) signal max attenuation YY + in dB. The results from the gain gain calculation are compared against + the specified thresholds XX and YY. + + When defined, the tool returns one of the follwoing codes: + 0: Error During Processing + 1: Active Signal Amp above Threshold, Inactive Signal Att below Threshold (Both Fail) + 2: Inactive Signal Att below Threshold (Inactive Sig Fail, Active Sig Pass) + 3: Active Signal Amp above Threshold (Inactive Sig Pass, Active Sig Fail) + 4: Active Signal Amp below Threshold, Inactive Signal Att above Threshold (Both Pass) + + +Both changes simplifies a handling of results inside the gain check script. + + +Changes from V1.0 to V2.0 +========================= + +1) Better Protection of 'Division by' 0 and 'Log of 0'. + Now all this checking is done in the 'print_bands()' function. + +2) Bands can now be Specified as a Fraction of Fs/2. + +3) Energy Calculations and Printout are Seperated when using the VAD Flag File. + In the previous version, frames with VAD=0 were skipped. + In this version, the energy ratio (in % and dB) of all Bands Specified will be + calculated for Active and Inactive Frames. The results will be printed for each. + +4) Printout of the Bands Energy is now in the 'print_bands()' function. + +5) Better Checking of the Frequency Bands Validity. + The previous version had a bug that caused it to crash + with some combinations of Freq Bands. + +6) Removed the 'E_MIN' addition in the energy calculation + (for Both the Original and Processed Files). + This addition was not necessary; the check for Energy < E_MIN is now done + only once now in the 'print_bands()' function. + +7) An energy threshold has been added. + When the VAD file is used and a frame is Inactive (VAD=0) + The energy of the original is calculted to see if it is meaningful + to include the frame in the frequency energy calculation. + The threshold is set that if the energy is lower than the + energy of a sine wave of 2 in amplitude, the frame is skipped. + The amplitude of 2 means that the sine wave varies between -2 to +2 + if quantized by a 16 bits short. + +8) Total Energy (Full Spectrum) will be printed for both Active and Inactive frames. + The corresponds to the Band 0-Fs/2. + \ No newline at end of file diff --git a/src/gain_chk/README.md b/src/gain_chk/README.md new file mode 100644 index 00000000..b9ed1901 --- /dev/null +++ b/src/gain_chk/README.md @@ -0,0 +1,143 @@ +# gain_chk — gain amplification verification + +## Copyright + +This software is protected by copyright law and by international treaties. The source code, and all of its derivations, is provided by VoiceAge Corporation under the "ITU-T Software Tools' General Public License". Please, read the license file or refer to ITU-T Recommendation G.191 on "SOFTWARE TOOLS FOR SPEECH AND AUDIO CODING STANDARDS". Any use of this software is permitted provided that this notice is not removed and that neither the authors nor VoiceAge Corporation are deemed to have made any representations as to the suitability of this software for any purpose nor are held responsible for any defects of this software. THERE IS NO WARRANTY FOR THIS SOFTWARE. + +(C) 2026 copyright VoiceAge Corporation. All Rights Reserved. + +## Description + +`gain_chk` compares reference and processed 16-bit PCM streams in 20 ms frames, +performs short-time spectral analysis, and reports per-band energy ratios (dB +and percent). Optional VAD flags split statistics into active and inactive +frames. Optional threshold checks (`-a`, `-s`) return a numeric exit status for +scripting. + +## Files + +``` +gain_chk.c ........................ main program +test_data/ ........................ regression PCM and CMake driver for CTest +Gain_Chk_Release_Notes.txt ....... revision notes (VoiceAge V3.0 history) +``` + +## Build + +`gain_chk` is not built on its own: it is a target of the **top-level STL** CMake +project (see the repository root `README.md` for toolchain prerequisites). + +From the **STL repository root** (the directory that contains the top-level +`CMakeLists.txt`): + +1. **Configure** the build (example: out-of-tree build directory `build`): + + ```shell + cmake -S . -B build + ``` + + You can use another build path or generator (for example `cmake -G Ninja -S . -B build`). + +2. **Build only** the `gain_chk` executable: + + ```shell + cmake --build build --target gain_chk + ``` + +3. The binary is written to **`build/bin/gain_chk`** (the project sets + `CMAKE_RUNTIME_OUTPUT_DIRECTORY` to `${CMAKE_BINARY_DIR}/bin`). + +To build **all** STL targets, omit `--target gain_chk` and run +`cmake --build build` (or your chosen build directory). + +## Tests (CTest) + +CMake registers **five** tests in `src/gain_chk/CMakeLists.txt`: + +| CTest name | Reference (`-i`) | Processed (`-o`) | Golden stderr | +|-----------------|--------------------|--------------------|---------------------------------------| +| `gain_chk_self` | `input48c.pcm` | `input48c.pcm` | `test_data/gain_chk_stderr_self.ref` | +| `gain_chk_a` | `input48c.pcm` | `syn.a.pcm` | `test_data/gain_chk_stderr_a.ref` | +| `gain_chk_b` | `input48c.pcm` | `syn.b.pcm` | `test_data/gain_chk_stderr_b.ref` | +| `gain_chk_c` | `input48c.pcm` | `syn.c.pcm` | `test_data/gain_chk_stderr_c.ref` | +| `gain_chk_d` | `input48c.pcm` | `syn.d.pcm` | `test_data/gain_chk_stderr_d.ref` | + +Each test runs `gain_chk` at **48 kHz** (`-r 48000`), captures **stderr** to a scratch +file under the build tree, and compares it **byte-for-byte** to the matching `*.ref` +in `src/gain_chk/test_data/` (two-decimal band summary line plus the expected +end-of-file warning when the PCM length is an exact multiple of the frame size). + +### Running the tests + +All commands below assume your **current working directory is the STL repository +root** (the folder that contains the top-level `CMakeLists.txt`). The examples use +an out-of-tree build directory named **`build`**; use the same name everywhere, or +replace it with your own path. + +**Full chain in one line** (configure, build `gain_chk`, run only the five +`gain_chk_*` tests): + +```shell +cmake -S . -B build && cmake --build build --target gain_chk && ctest --test-dir build -R '^gain_chk_' --output-on-failure +``` + +**If the build tree is already configured**, rebuild the tool and run the same tests: + +```shell +cmake --build build --target gain_chk && ctest --test-dir build -R '^gain_chk_' --output-on-failure +``` + +**Same steps split** (equivalent to the one-liner): + +1. `cmake -S . -B build` +2. `cmake --build build --target gain_chk` +3. `ctest --test-dir build -R '^gain_chk_' --output-on-failure` + +**If you prefer to run `ctest` from inside the build directory**: + +```shell +cd build +ctest -R '^gain_chk_' --output-on-failure +``` + +**Verbose** output (shows the exact `cmake -P …/gain_chk_ctest.cmake` command line +for each test): + +```shell +ctest --test-dir build -R '^gain_chk_' -V +``` + +`CTestTestfile.cmake` (and related CTest files) under **`build/`** are **generated +by CMake** from `add_test`; they are not edited by hand (same idea as +`src/truncate/`). + +### Updating the golden `*.ref` files + +If you change `gain_chk`, the floating-point path, or the PCM files under +`test_data/`, the stderr text may change. In that case, regenerate the corresponding +`gain_chk_stderr_*.ref` (for example by running `gain_chk` once per case, redirecting +stderr to a file, and replacing the ref) and commit the updated refs after you have +verified the new output is correct. + +## Usage (summary) + +``` +gain_chk -i original_f -o processed_f -t results_f -r Fs [-v VAD_f] [-a val] [-s val] +``` + +- `-i` reference binary (16-bit samples, frame length implied by `Fs`) +- `-o` processed binary (same layout as reference) +- `-t` results text file (append mode) +- `-r` sampling frequency in Hz: 8000, 16000, 32000, or 48000 +- `-v` optional VAD file (one 16-bit flag per frame) +- `-a` optional active-frame maximum amplification threshold (dB), requires build with `THRESHOLD_CHECK` +- `-s` optional inactive-frame maximum attenuation threshold (dB); requires `-v` + +Frequency bands analysed are configured in the `bands[]` table in `gain_chk.c` +(terminate the list with `0.0f`; values in (0,1] are interpreted as fractions of +`Fs/2`). + +## Formatting + +C sources in this tree follow `FORMATTING.md` at the repository root (GNU +`indent` options; spaces instead of tab characters). diff --git a/src/gain_chk/gain_chk.c b/src/gain_chk/gain_chk.c new file mode 100644 index 00000000..3f0a6b16 --- /dev/null +++ b/src/gain_chk/gain_chk.c @@ -0,0 +1,834 @@ +/* + * (C) 2026 copyright VoiceAge Corporation. All Rights Reserved. + * + * This software is protected by copyright law and by international treaties. The source code, and all of its derivations, + * is provided by VoiceAge Corporation under the "ITU-T Software Tools' General Public License". Please, read the license file + * or refer to ITU-T Recommendation G.191 on "SOFTWARE TOOLS FOR SPEECH AND AUDIO CODING STANDARDS". + * + * Any use of this software is permitted provided that this notice is not removed and that neither the authors nor + * VoiceAge Corporation are deemed to have made any representations as to the suitability of this software + * for any purpose nor are held responsible for any defects of this software. THERE IS NO WARRANTY FOR THIS SOFTWARE. + * + * gain_chk: gain amplification verification tool (V3.0). Revision history: Gain_Chk_Release_Notes.txt in this folder. + * Authors: Guy Richard, Tommy Vaillancourt (Tommy.Vaillancourt@USherbrooke.ca) + */ + +#include +#include +#include +#include + +#define PI2 6.2831853f +#define NB_BANDS 25 +#define L_MAX 1024 +#define N_FRM_TYPE 2 /* Active / Inactive */ +#define E_MIN 0.03f /* To Avoid Division by 0 */ +#define RATIO_MIN 0.00001f /* to Avoid Log of 0 */ + + +/* define end frequencies of frequency bands to be analysed (Terminate list with a 0) */ +/* Using Value 'N' such as 0.0 < N <= 1.0 Specifies the Freq Band as N * (Fs/2) */ +static float bands[NB_BANDS] = { + /* // critical bands + 100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720, 2000, + 2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500 + */ + 4000, 8000, 16000, 20000, 0.0f +}; + +#define THRESHOLD_CHECK +#define OUTPUT_VALUES + +static void usage( void ) +{ + printf( " Usage: gain_chk -i original_f -o processed_f -t results_f -r Fs [-v VAD_f]\n\n" ); + printf( " -i original_f - reference binary file\n" ); + printf( " -o processed_f - evaluated binary file\n" ); + printf( " -t results_f - results text file\n" ); + printf( " -r Fs - sampling frequency [8000, 16000, 32000, 48000] in Hz\n" ); + printf( " -v VAD_f - VAD Flag binary file (One 16 Bits Flag per Frame), optional\n" ); +#ifdef THRESHOLD_CHECK + printf( " -a val - Active Signal Amplification Threshold (in dB)\n" ); + printf( " -s val - Inactive Signal Attenuation Threshold (in dB)\n" ); +#endif + + exit(0); +} + + +/*---------------------------------------------------------------------* + * + * FUNCTION NAME fft_rel + * Computes the split-radix FFT in place for the real-valued + * signal x of length n. The algorithm has been ported from + * the Fortran code of [1]. + * + * References + * [1] H.V. Sorensen, D.L. Jones, M.T. Heideman, C.S. Burrus, + * "Real-valued fast Fourier transform algorithm," IEEE + * Trans. on Signal Processing, Vol.35, No.6, pp 849-863, + * 1987. + * + * INPUT + * x[0:n-1] Input sequence. + * n Number of samples in the sequence (need to be power of 2). + * m m = log2(n). + * + * OUTPUT + * x[0:n-1] Transform coeffients in the order re[0], re[1], + * ..., re[n/2], im[n/2-1], ..., im[1]. + *---------------------------------------------------------------------*/ + +static void fft_rel( float x[], short n, short m ) +{ + short i, j, k, n1, n2, n4; + short i1, i2, i3, i4; + float xt, e, a, cc, ss, t1, t2; + + /* Digit reverse counter */ + j=0; + for ( i=0; i>1; + } + j +=k; + } + + /* Length two butterflies */ + for ( i=0; i gain) + { + /* Yes (Fail) */ + return 0; + } + i++; + } + } + + /* All Passed */ + return 1; +} +#endif + +#ifdef OUTPUT_VALUES +/*---------------------------------------------------------------------------* + * print_bands_val_stderr * + * ~~~~~~~~~~~~~ * + * Output the Results to 'stderr'. * + *--------------------------------------------------------------------------*/ + +static void print_bands_val( float *ener1, float *ener2, FILE *file) +{ + int i; + float ratio; + #define FMT "% 8.2f " + + i=0; + while ( bands[i] != 0.0f ) + { + ratio = (ener2[i] + E_MIN) / (ener1[i] + E_MIN); + if (ratio < RATIO_MIN) ratio = RATIO_MIN; + fprintf( file, FMT, 10.0*log10(ratio) ); + i++; + } + + /* 3 Because we want do Always Print 3 Bands (NB, WB, SWB) */ + while (i++ < 3) + { + fprintf( file, " =NA() " ); + } + + /* Print "full Band" Energy Ratio */ + /* The Full Spectrum Energy is in the Last Value of the Array */ + ratio = (ener2[NB_BANDS+1] + E_MIN) / (ener1[NB_BANDS+1] + E_MIN); + if (ratio < RATIO_MIN) ratio = RATIO_MIN; + fprintf( file, FMT, 10.0*log10(ratio) ); +} +#endif + +/*---------------------------------------------------------------------------* + * main function * + * ~~~~~~~~~~~~~ * + * Gain Amplification Verification. * + *--------------------------------------------------------------------------*/ + +int main( int argc, char *argv[] ) +{ + + long frame; + long frames_analysed; + short i,j, L_FFT, LOG2_L_FFT, L_FRAME, cnt, VAD_Flag; +#ifdef THRESHOLD_CHECK + float Active_Amp_Thres=99999.0f, Inactive_Att_Thres=99999.0f; + int Active_Pass=1, Inactive_Pass=1; +#endif + short Skip_Ener_Calc; + short data1[L_MAX], data2[L_MAX]; + float original_buf[L_MAX], processed_buf[L_MAX]; + float fft_buf_orig[L_MAX], fft_buf_proc[L_MAX], win[L_MAX]; + float Fs, f_bin, freq, tmp, band_ener1, band_ener2; + float band_ener_tot1[N_FRM_TYPE][NB_BANDS+2], band_ener_tot2[N_FRM_TYPE][NB_BANDS+2]; + float *ptR1, *ptI1, *ptR2, *ptI2; + + FILE *file1, *file2, *file3, *file4; + + printf("===================================================================\n"); + printf("Gain Amplification Verification tool, V3.0\n"); + printf("------------------------------------------\n"); + printf("===================================================================\n"); + + if( argc < 5 ) + { + fprintf( stderr, "Error: too few command-line arguments (got %d; need at least -i, -o, -t, -r and their values).\n", + argc - 1 ); + usage(); + } + + else + { + i = 1; + Fs = -1.0f; + file1 = NULL; + file2 = NULL; + file3 = NULL; + file4 = NULL; + + /* Process input arguments */ + /* ~~~~~~~~~~~~~~~~~~~~~~~ */ + + while( i < argc ) + { + if(argv[i][0] == '-') + { + switch(argv[i][1]) + { + case 'i': i++; if ( i >= argc || *argv[i] == '\0' ) + { + fprintf( stderr, "Error: -i requires a reference PCM file path (argument missing or empty).\n" ); + usage(); + } + + if ( (file1 = fopen( argv[i], "rb" )) == NULL) + { + fprintf( stderr, "Error: cannot open reference input file (-i): %s\n", argv[i] ); + exit( 1 ); + } + printf( "\nReference file: %s\n", argv[i] ); + + i++; + + break; + + case 'o': i++; if ( i >= argc || *argv[i] == '\0' ) + { + fprintf( stderr, "Error: -o requires an evaluated PCM file path (argument missing or empty).\n" ); + usage(); + } + + if ( (file2 = fopen( argv[i], "rb" )) == NULL) + { + fprintf( stderr, "Error: cannot open evaluated input file (-o): %s\n", argv[i] ); + exit( 1 ); + } + printf("Evaluated file: %s\n", argv[i]); + + i++; + + break; + + case 't': i++; if ( i >= argc || *argv[i] == '\0' ) + { + fprintf( stderr, "Error: -t requires a results text file path (argument missing or empty).\n" ); + usage(); + } + + if ( (file4 = fopen( argv[i], "at" )) == NULL) + { + fprintf( stderr, "Error: cannot open results text file (-t): %s\n", argv[i] ); + exit( 1 ); + } + printf( "Results text file: %s\n ", argv[i]); + + i++; + + break; + + case 'r': if ( ++i >= argc ) + { + fprintf( stderr, "Error: -r requires a sampling frequency in Hz (argument missing).\n" ); + usage(); + } + + Fs = (float)atof( argv[i] ); + printf( "Sampling frequency = %5.0f Hz\n\n", Fs ); + + i++; + break; + + /* Optional input argument */ + case 'v': i++; if ( i >= argc || *argv[i] == '\0' ) + { + fprintf( stderr, "Error: -v requires a VAD flags file path (argument missing or empty).\n" ); + usage(); + } + + if ( (file3 = fopen( argv[i], "rb" )) == NULL) + { + fprintf( stderr, "Error: cannot open VAD input file (-v): %s\n", argv[i] ); + exit( 1 ); + } + printf( "VAD File: %s\n\n", argv[i] ); + + i++; + break; +#ifdef THRESHOLD_CHECK + case 'a': if ( ++i >= argc ) + { + fprintf( stderr, "Error: -a requires the active-frame max amplification threshold in dB (argument missing).\n" ); + usage(); + } + + Active_Amp_Thres = (float)atof( argv[i] ); + if (Active_Amp_Thres <= 0.0f) + { + fprintf( stderr, "Error: -a value must be strictly positive (got %g).\n", (double)Active_Amp_Thres ); + exit(0); + } + printf( "Active Signal Max Amplification Threshold = %5.3f dB\n\n", Active_Amp_Thres ); + + i++; + break; + + case 's': if ( ++i >= argc ) + { + fprintf( stderr, "Error: -s requires the inactive-frame max attenuation threshold in dB (argument missing).\n" ); + usage(); + } + + Inactive_Att_Thres = (float)atof( argv[i] ); + if (Inactive_Att_Thres <= 0.0f) + { + fprintf( stderr, "Error: -s value must be strictly positive (got %g).\n", (double)Inactive_Att_Thres ); + exit(0); + } + printf( "Inactive Signal Max Attenuation Threshold = %5.3f dB\n\n", Inactive_Att_Thres ); + + i++; + break; +#endif + + default: + + if ( argv[i][0] == '-' && argv[i][1] == '\0' ) + { + fprintf( stderr, "Error: lone '-' is not a valid option.\n" ); + } + else if ( argv[i][0] == '-' && argv[i][2] != '\0' ) + { + fprintf( stderr, "Error: multi-character option '%s' is not supported (use single-letter flags like -i, -o).\n", + argv[i] ); + } + else + { + fprintf( stderr, "Error: unknown option flag '-%c' in '%s'.\n", + (int)(unsigned char)argv[i][1], argv[i] ); + } + usage(); + break; + } + } + else + { + fprintf( stderr, "Error: unexpected argument '%s' (all parameters must use -flag value form).\n", argv[i] ); + usage(); + } + } + } + + if ( file1 == NULL || file2 == NULL || file4 == NULL ) + { + if ( file1 == NULL ) + { + fputs( "Error: missing required option -i .\n", stderr ); + } + if ( file2 == NULL ) + { + fputs( "Error: missing required option -o .\n", stderr ); + } + if ( file4 == NULL ) + { + fputs( "Error: missing required option -t .\n", stderr ); + } + usage(); + } +#ifdef THRESHOLD_CHECK + if ( file3 == NULL && Inactive_Att_Thres != 99999.0f) + { + fputs( "Error: -s (inactive attenuation threshold) requires a VAD file with -v .\n", stderr ); + exit(0); + } + +#endif + if ( Fs!=8000 && Fs!=16000 && Fs!=32000 && Fs!=48000 ) + { + if ( Fs < 0.0f ) + { + fputs( "Error: missing required option -r (sampling frequency in Hz).\n", stderr ); + } + else + { + fprintf( stderr, "Error: invalid sampling frequency %.0f Hz for -r (allowed: 8000, 16000, 32000, 48000).\n", + (double)Fs ); + } + usage(); + } + + L_FRAME = (short)(Fs / 50 + 0.5f); /* number of samples in frame of 20 ms */ + + /* Frequency bands management */ + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + for ( j = 0; j < N_FRM_TYPE; j++ ) + { + for ( i = 0; i < NB_BANDS+2; i++ ) + { + band_ener_tot1[j][i] = 0.0f; + band_ener_tot2[j][i] = 0.0f; + } + } + + /* Check Bands (and convert) */ + for ( i=0; i 0 ) + { + /* Check Frequency Bands Ordering */ + if (bands[i] <= bands[i-1]) + { + fprintf( stderr, "Error: bad order of frequency bands (%6.1f Hz, %6.1f Hz); aborting.\n", + (double)bands[i-1], (double)bands[i] ); + exit( 1 ); + } + } + /* Limit Frequency to Fs/2 */ + if ( bands[i] > Fs/2.0f ) + { + bands[i] = Fs/2.0f; + if ( i > 0 ) + { + if (bands[i] == bands[i-1]) + { + bands[i] = 0.0f; + break; + } + } + } + } + + if ( i == 0 ) + { + fprintf( stderr, "Error: no frequency bands defined in bands[]; aborting.\n" ); + exit( 1 ); + } + if ( i == NB_BANDS ) + { + fprintf( stderr, "Error: frequency band list must end with 0.0f; aborting.\n" ); + exit( 1 ); + } + + if ( 2.0 * Fs / L_FRAME > bands[0] ) + { + fprintf( stderr, "Error: insufficient frequency resolution for the configured bands (Fs=%.0f Hz); aborting.\n", + (double)Fs ); + exit( 1 ); + } + + /* Initializations */ + for ( i = 0; i < L_MAX; i++ ) + { + original_buf[i] = 0.0f; + processed_buf[i] = 0.0f; + fft_buf_orig[i] = 0.0f; + fft_buf_proc[i] = 0.0f; + } + + /* prepare hamming window for frequency analysis */ + tmp = (float)(PI2/(L_FRAME-1)); + for ( i=0; i< L_FRAME; i++ ) + { + win[i] = 0.54f - 0.46f * (float)cos( tmp * i ); + } + + /* set fft length after zero padding */ + LOG2_L_FFT = (short)( log( L_FRAME - 0.5 ) / log( 2.0 ) ) + 1; + L_FFT = 1<