diff --git a/.gitignore b/.gitignore index 9a028291..da5e493a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,8 +47,6 @@ src/is54/test_data/voice.bec src/is54/test_data/voice.hbs !src/is54/test_data/voice.log src/is54/test_data/voice.rek -src/sv56/test_data/voice.prc -src/sv56/test_data/voice.rms src/unsup/test_data/astrip.blk src/unsup/test_data/undo.txt src/utl/test_data/voice.rnp @@ -97,3 +95,8 @@ bld/ .vs/ *vcxproj* *.sln* +.vscode/ +/build +*.double +stderr.txt +stdout.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index cbd02f45..282068b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.10) + project(STL2023 VERSION 2023) -add_definitions( -DVERSION_STL="${CMAKE_PROJECT_NAME}" ) + +add_definitions( -DVERSION_STL="${CMAKE_PROJECT_NAME}" -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/cmake/CompareTextFiles.cmake b/cmake/CompareTextFiles.cmake new file mode 100644 index 00000000..562ca046 --- /dev/null +++ b/cmake/CompareTextFiles.cmake @@ -0,0 +1,25 @@ +# cmake/CompareTextFiles.cmake +# Compare two text files, ignoring line-ending differences (CRLF vs LF). +# +# Usage (from a -P script): +# include(/path/to/CompareTextFiles.cmake) +# compare_text_files(GOT EXPECTED [LABEL ]) + +function(compare_text_files) + cmake_parse_arguments(_CTF "" "GOT;EXPECTED;LABEL" "" ${ARGN}) + + file(READ "${_CTF_GOT}" _got) + string(REPLACE "\r\n" "\n" _got "${_got}") + + file(READ "${_CTF_EXPECTED}" _want) + string(REPLACE "\r\n" "\n" _want "${_want}") + + if(NOT _got STREQUAL _want) + message(FATAL_ERROR "Text file mismatch: ${_CTF_LABEL}\n--- got ---\n${_got}\n--- expected ---\n${_want}") + endif() +endfunction() + +# Auto-invoke when called via cmake -P with -D variables +if(DEFINED GOT AND DEFINED EXPECTED) + compare_text_files(GOT "${GOT}" EXPECTED "${EXPECTED}" LABEL "${LABEL}") +endif() diff --git a/doc/manual/sv56.tex b/doc/manual/sv56.tex index b17938c6..24905375 100644 --- a/doc/manual/sv56.tex +++ b/doc/manual/sv56.tex @@ -220,13 +220,15 @@ \section{Implementation} needed in this approach. These state variables have been arranged as fields of a structure whose name is {\tt SVP56\_state}. The fields of the structure are\footnote{\SF All the fields are \double, except the -\float\ $f$ and the {\tt unsigned long} {\em a[]}, {\em hang[]}, and -{\em n}.}: +\float\ $f$, the {\tt int} {\em thres\_no} and {\em bitno}, and the +{\tt unsigned long} {\em a[]}, {\em hang[]}, and {\em n}.}: \begin{quote} \normalsize {\em f} \hfill \parbox{100mm}{\SF Sampling frequency, in Hz }\\ - {\em a[15]} \hfill \parbox{100mm}{\SF Activity count }\\ - {\em c[15]} \hfill \parbox{100mm}{\SF Threshold level }\\ - {\em hang[15]} \hfill \parbox{100mm}{\SF Hangover count }\\ + {\em a[]} \hfill \parbox{100mm}{\SF Activity count (size: {\em thres\_no}) }\\ + {\em c[]} \hfill \parbox{100mm}{\SF Threshold level (size: {\em thres\_no}) }\\ + {\em hang[]} \hfill \parbox{100mm}{\SF Hangover count (size: {\em thres\_no}) }\\ + {\em thres\_no} \hfill \parbox{100mm}{\SF Number of thresholds in use ($= bitno - 1$) }\\ + {\em bitno} \hfill \parbox{100mm}{\SF Bit depth of input signal (1--32, default 16) }\\ {\em n} \hfill \parbox{100mm}{\SF Number of samples read since last reset }\\ {\em s} \hfill \parbox{100mm}{\SF Sum of all samples since last @@ -270,7 +272,7 @@ \subsection{{\tt init\_speech\_voltmeter}} \#include "sv-p56.h"\\ void init\_speech\_voltmeter \pbox{110mm}{ - (SVP56\_state {\em *state}, double {\em f}); + (SVP56\_state {\em *state}, double {\em f}, int {\em bitno}); } } @@ -280,13 +282,21 @@ \subsection{{\tt init\_speech\_voltmeter}} {\tt init\_speech\_voltmeter} performs the initialization of the speech voltmeter state variables in the structure pointed by {\em -state} to the appropriate initial values. The only value required from -the user is the sampling rate $f$ (in Hz) of the signal that the speech -voltmeter is supposed to measure. Note that when measuring new +state} to the appropriate initial values. The user must specify the +sampling rate $f$ (in Hz) and the bit depth {\em bitno} of the signal +that the speech voltmeter is supposed to measure. The bit depth +determines the number of thresholds used ($B-1$) and must be in the +range 1 to 32. Note that when measuring new speech material, the state variable shall be re-initialized, otherwise accumulation of previous measurements will happen and wrong measurements will be reported. +The header file {\tt sv-p56.h} defines two constants that control the +maximum bit depth supported: +\begin{quote} \normalsize + {\tt SVP56\_MAX\_NO\_BITS} \hfill \parbox{100mm}{\SF Maximum bit depth supported (default: 32). }\\ + {\tt SVP56\_MAX\_THRESHOLDS} \hfill \parbox{100mm}{\SF Maximum number of thresholds ($=$ SVP56\_MAX\_NO\_BITS $-$ 1). } +\end{quote} {\bf Variables: } @@ -298,6 +308,12 @@ \subsection{{\tt init\_speech\_voltmeter}} Is the sampling rate (in Hz) of the signal to be measured in the next calls of {\tt speech\_voltmeter}. If zero or negative, the sampling rate is initialized to 16000 Hz. + +\item[\pbox{20mm}{\em bitno}] %\rulex{1mm}\\ + Is the bit depth (resolution) of the input signal, in the + range 1 to 32. This determines the number of threshold levels + used by the algorithm ($B-1$ thresholds). For standard + telephony, use 16. \end{Descr} {\bf Return value: } @@ -435,25 +451,30 @@ \section{Portability and compliance} input file, which is saved in an aoutput file. Levels are reported in dBov. -In general, input files are in integer representation, 16-bit words, -2's complement (i.e., {\tt short} data). In UGST convention, this -data must be left-adjusted, {\em rather} than right-adjusted. Since -the speech voltmeter uses {\tt float} input data, it is necessary to -convert from {\tt short} (in the mentioned format) to {\tt float}; -this is carried out by the function {\tt sh2fl()}. In addition, the -option to `normalize' the input data to the range -1..+1 is selected. +The example programs support variable bit-width input files from 8 to +32 bits per sample (controlled by the {\em bitno} parameter). Supported +storage widths are 8-bit (1 byte/sample), 16-bit (2 bytes/sample), +24-bit (3 bytes/sample), and 32-bit (4 bytes/sample). For bit depths +between 12 and 15, samples are stored in 2 bytes with the least +significant bits masked to zero. Files use native byte order. + +Since the speech voltmeter uses {\tt float} input data normalized to +the range --1.0..+1.0, the helper functions in {\tt sv56-util.h} handle +the conversion between raw integer samples and normalized floats: +{\tt sv56\_raw2fl()} reads raw bytes and normalizes by dividing by +$2^{bitno-1}$, and {\tt sv56\_fl2raw()} performs the inverse operation +with truncation and hard-clipping. After the equalization factor is found, results are reported on the screen, which varies according to the program used and some of the -command-line options. +command-line options. While program {\tt actlevel.c} stops at this point, program {\tt sv56demo.c} proceeds calling the function {\tt scale()} to carry out the (amplitude) equalization using single (rather than double) float precision. After equalization, the samples are converted back to -integer (short, right-justified) with the routine {\tt fl2sh()} using -truncation, no zero-padding of the least significant bits, -left-justification of data, and hard-clipping of data above the -overload point. After that, data is saved to the user-specified file . +raw integer format with {\tt sv56\_fl2raw()} using +truncation and hard-clipping of data above the +overload point. After that, data is saved to the user-specified file. %-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- @@ -471,7 +492,7 @@ \section{Portability and compliance} #include #include "ugstdemo.h" /* ... UGST demonstration program defs ... */ #include "sv-p56.h" /* ... SV-P56 prototypes & defs ... */ -#include "ugst-utl.h" /* ... UGST utilities ... */ +#include "sv56-util.h" /* ... SV56 I/O utilities ... */ #define BLK_LEN 256 void main(argc, argv) @@ -482,7 +503,8 @@ \section{Portability and compliance} char FileIn[180]; /* input file name */ FILE *Fi; /* input file pointers */ long N=BLK_LEN, l; - short bitno, buffer[BLK_LEN]; + int bitno, bps; + unsigned char raw_buf[BLK_LEN * 4]; float Buf[BLK_LEN]; double ActiveLeveldB, sf, satur; @@ -493,20 +515,21 @@ \section{Portability and compliance} FIND_PAR_D(2, "_Sampling Frequency: .. ", sf, 16000); FIND_PAR_L(3, "_A/D resolution: ...... ", bitno, 16); - /* Calculate overload point in the non-normalized range */ + /* Calculate bytes per sample and overload point */ + bps = sv56_bytes_per_sample(bitno); satur = pow ((double)2.0, (double)(bitno - 1)); /* Reset- variables for speech level measurements */ - init_speech_voltmeter(&state, sf); + init_speech_voltmeter(&state, sf, bitno); /* Opening input file */ Fi = fopen(FileIn, RB); /* Read samples ... */ - while ((l = fread(buffer, N, sizeof(short), Fi)) > 0) + while ((l = fread(raw_buf, bps, N, Fi)) > 0) { - /* ... Convert samples to float, normalizing to +1..-1 */ - sh2fl((long) l, buffer, Buf, (long) state.bitno, 1); + /* ... Convert raw samples to float, normalizing to +1..-1 */ + sv56_raw2fl((long) l, raw_buf, Buf, bitno); /* ... Get the active level */ ActiveLeveldB = speech_voltmeter(Buf, (long) l, &state); @@ -524,7 +547,7 @@ \section{Portability and compliance} printf("\n Maximum negative value: .. %7.0f [PCM]", SVP56_get_neg_max(state) * satur); printf("\n Long-term energy (rms): .. %7.3f [dBov]", - SVP56_get_rms_dB(state); + SVP56_get_rms_dB(state)); printf("\n Active speech level: ..... %7.3f [dBov]", ActiveLeveldB); printf("\n Activity factor: ......... %7.3f [%%]", SVP56_get_activity(state)); diff --git a/src/sv56/.gitignore b/src/sv56/.gitignore new file mode 100644 index 00000000..a4b86269 --- /dev/null +++ b/src/sv56/.gitignore @@ -0,0 +1,3 @@ +**/*.out +test_data/voice.prc +test_data/voice.rms \ No newline at end of file diff --git a/src/sv56/CMakeLists.txt b/src/sv56/CMakeLists.txt index 099a7861..f78caaa3 100644 --- a/src/sv56/CMakeLists.txt +++ b/src/sv56/CMakeLists.txt @@ -1,17 +1,142 @@ include_directories(../g711) include_directories(../utl) -add_executable(sv56demo sv56demo.c sv-p56.c ../utl/ugst-utl.c) +add_executable(sv56demo sv56demo.c sv-p56.c ../utl/ugst-utl.c ../utl/wav_io.c) target_link_libraries(sv56demo ${M_LIBRARY}) -add_executable(actlev actlevel.c sv-p56.c ../utl/ugst-utl.c) +add_executable(actlev actlevel.c sv-p56.c ../utl/ugst-utl.c ../utl/wav_io.c) target_link_libraries(actlev ${M_LIBRARY}) add_test(sv56demo1 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q test_data/voice.src test_data/voice.prc 256 1 0 -30) -add_test(sv56demo1-verify ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cf -q test_data/voice.nrm test_data/voice.prc) +set_tests_properties(sv56demo1 PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo1-verify ${CMAKE_COMMAND} -E compare_files test_data/voice.nrm test_data/voice.prc) +set_tests_properties(sv56demo1-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_test(sv56demo2 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -rms test_data/voice.src test_data/voice.rms 256 1 0 -30) -add_test(sv56demo2-verify ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cf -q test_data/voice.ltl test_data/voice.rms) +set_tests_properties(sv56demo2 PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo2-verify ${CMAKE_COMMAND} -E compare_files test_data/voice.ltl test_data/voice.rms) +set_tests_properties(sv56demo2-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_test(sv56demo3 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/actlev -q test_data/voice.src test_data/voice.nrm test_data/voice.prc test_data/voice.ltl test_data/voice.rms) +set_tests_properties(sv56demo3 PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# 12-bit backward compatibility test +add_test(sv56demo4 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -log test_data/voice_12bit.log test_data/voice.src test_data/voice_12bit.prc 256 1 0 -30 16000 12) +set_tests_properties(sv56demo4 PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_test(sv56demo4-verify ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cf -q test_data/voice_12bit.nrm test_data/voice_12bit.prc) +set_tests_properties(sv56demo4-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS sv56demo4) +add_test(sv56demo4-log-verify ${CMAKE_COMMAND} -E compare_files test_data/voice_12bit.log.ref test_data/voice_12bit.log) +set_tests_properties(sv56demo4-log-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS sv56demo4) + +# WAV integration tests +add_test(sv56demo-wav-24bit-normal ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q test_data/speech_normal_24bit.wav test_data/speech_normal_24bit.out) +set_tests_properties(sv56demo-wav-24bit-normal PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-24bit-normal-verify ${CMAKE_COMMAND} -E compare_files test_data/speech_normal_24bit.ref test_data/speech_normal_24bit.out) +set_tests_properties(sv56demo-wav-24bit-normal-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-32bit ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q test_data/speech_normal_32bit.wav test_data/speech_normal_32bit.out) +set_tests_properties(sv56demo-wav-32bit PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-32bit-verify ${CMAKE_COMMAND} -E compare_files test_data/speech_normal_32bit.ref test_data/speech_normal_32bit.out) +set_tests_properties(sv56demo-wav-32bit-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-24bit ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q test_data/speech_quiet_24bit.wav test_data/speech_quiet_24bit.out) +set_tests_properties(sv56demo-wav-24bit PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-24bit-verify ${CMAKE_COMMAND} -E compare_files test_data/speech_quiet_24bit.ref test_data/speech_quiet_24bit.out) +set_tests_properties(sv56demo-wav-24bit-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-float ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q test_data/speech_quiet_32bit_float.wav test_data/speech_quiet_32bit_float.out) +set_tests_properties(sv56demo-wav-float PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-float-verify ${CMAKE_COMMAND} -E compare_files test_data/speech_quiet_32bit_float.ref test_data/speech_quiet_32bit_float.out) +set_tests_properties(sv56demo-wav-float-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-vquiet-32bit ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q test_data/speech_vquiet_32bit.wav test_data/speech_vquiet_32bit.out) +set_tests_properties(sv56demo-wav-vquiet-32bit PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-vquiet-32bit-verify ${CMAKE_COMMAND} -E compare_files test_data/speech_vquiet_32bit.ref test_data/speech_vquiet_32bit.out) +set_tests_properties(sv56demo-wav-vquiet-32bit-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-vquiet-16bit-adc ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -bits 16 test_data/speech_vquiet_32bit.wav test_data/speech_vquiet_32bit_16bit.out) +set_tests_properties(sv56demo-wav-vquiet-16bit-adc PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-vquiet-16bit-adc-verify ${CMAKE_COMMAND} -E compare_files test_data/speech_vquiet_32bit_16bit.ref test_data/speech_vquiet_32bit_16bit.out) +set_tests_properties(sv56demo-wav-vquiet-16bit-adc-verify PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# WAV text output (statistics) verification tests +add_test(sv56demo-wav-24bit-normal-log ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -log test_data/speech_normal_24bit.log test_data/speech_normal_24bit.wav test_data/speech_normal_24bit.out) +set_tests_properties(sv56demo-wav-24bit-normal-log PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-24bit-normal-log-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_normal_24bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_normal_24bit.log.ref + -DLABEL=sv56demo-wav-24bit-normal-log + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +add_test(sv56demo-wav-32bit-log ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -log test_data/speech_normal_32bit.log test_data/speech_normal_32bit.wav test_data/speech_normal_32bit.out) +set_tests_properties(sv56demo-wav-32bit-log PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-32bit-log-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_normal_32bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_normal_32bit.log.ref + -DLABEL=sv56demo-wav-32bit-log + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +add_test(sv56demo-wav-vquiet-32bit-log ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -log test_data/speech_vquiet_32bit.log test_data/speech_vquiet_32bit.wav test_data/speech_vquiet_32bit.out) +set_tests_properties(sv56demo-wav-vquiet-32bit-log PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-vquiet-32bit-log-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_vquiet_32bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_vquiet_32bit.log.ref + -DLABEL=sv56demo-wav-vquiet-32bit-log + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +add_test(sv56demo-wav-vquiet-16bit-adc-log ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sv56demo -q -log test_data/speech_vquiet_32bit_16bit.log -bits 16 test_data/speech_vquiet_32bit.wav test_data/speech_vquiet_32bit_16bit.out) +set_tests_properties(sv56demo-wav-vquiet-16bit-adc-log PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(sv56demo-wav-vquiet-16bit-adc-log-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_vquiet_32bit_16bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/speech_vquiet_32bit_16bit.log.ref + -DLABEL=sv56demo-wav-vquiet-16bit-adc-log + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +# actlev WAV integration tests +add_test(actlev-wav-24bit ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/actlev -q -log test_data/actlev_normal_24bit.log test_data/speech_normal_24bit.wav) +set_tests_properties(actlev-wav-24bit PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(actlev-wav-24bit-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_normal_24bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_normal_24bit.log.ref + -DLABEL=actlev-wav-24bit + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +add_test(actlev-wav-vquiet-32bit ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/actlev -q -log test_data/actlev_vquiet_32bit.log test_data/speech_vquiet_32bit.wav) +set_tests_properties(actlev-wav-vquiet-32bit PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(actlev-wav-vquiet-32bit-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_vquiet_32bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_vquiet_32bit.log.ref + -DLABEL=actlev-wav-vquiet-32bit + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +add_test(actlev-wav-vquiet-16bit-adc ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/actlev -q -bits 16 -log test_data/actlev_vquiet_32bit_16bit.log test_data/speech_vquiet_32bit.wav) +set_tests_properties(actlev-wav-vquiet-16bit-adc PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +add_test(actlev-wav-vquiet-16bit-adc-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_vquiet_32bit_16bit.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_vquiet_32bit_16bit.log.ref + -DLABEL=actlev-wav-vquiet-16bit-adc + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) + +add_test(actlev-wav-multi ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/actlev -q -log test_data/actlev_multi.log test_data/speech_normal_24bit.wav test_data/speech_vquiet_32bit.wav) +set_tests_properties(actlev-wav-multi PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_test(actlev-wav-multi-verify ${CMAKE_COMMAND} + -DGOT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_multi.log + -DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test_data/actlev_multi.log.ref + -DLABEL=actlev-wav-multi + -P ${CMAKE_SOURCE_DIR}/cmake/CompareTextFiles.cmake) diff --git a/src/sv56/README.md b/src/sv56/README.md index 0cf60760..30dd6898 100644 --- a/src/sv56/README.md +++ b/src/sv56/README.md @@ -6,72 +6,73 @@ CODING STANDARDS". ============================================================= -The UGST P.56 speech voltmeter module, version 3.1 (21/Aug/95), needs the -following files: +# UGST P.56 Speech Voltmeter -# C program code -``` -sv-p56.c ........ the speech voltmeter (SV) module itself; needs the - prototypes in sv-p56.h -sv-p56.h ........ prototypes and definitions needed by the SV module. -``` +Measures and equalizes active speech levels according to ITU-T Recommendation +P.56. Includes `sv56demo` (measure + equalize) and `actlev` (measure only). -# Additional modules needed (see directory ../utl): -``` -ugst-utl.c ...... UGST utilities' module: conversion between float and short - data formats functions/macros and gain/loss function. -ugst-utl.h ...... prototypes and definitions needed by the UGST utilities' - module. -ugstdemo.h ...... prototypes and definitions needed by UGST demo programs. -``` +# WAV file support -# Demo -``` -sv56demo.c ...... Demonstration program for the SV module; needs the files - sv-p56.c, ugst-utl.c, ugst-utl.h, and ugstdemo.h in the - current directory. -actlevel.c ...... Demo program that only measures the level/min/max/etc for - all the files given in the command line. In MSDOS, needs - wildargs.obj when using Borland compilers, in order to - be able to automatically process commands like - actlev *.src - This is not a concern in Unix because wildcard expansion - is included in the shell. Wildcard expansion is *not* - implemented in VMS (sorry). Please mind that the -q option - gives a more compact listing of the file statistics. -``` +`sv56demo` transparently supports WAV files (8/16/24/32-bit PCM and 32-bit +IEEE float). The format is auto-detected from the RIFF header; raw PCM files +continue to work as before (assumed 16-bit, native byte order). + +When a WAV file is detected: +- Bit depth and sample rate are read from the header +- The `-bits` flag overrides the A/D resolution used by the P.56 algorithm + (must be ≤ file bit depth), simulating a lower-resolution ADC/DAC +- Output format matches the input (WAV output if filename ends in `.wav`) + +## Benefits of higher bit depths + +The P.56 speech voltmeter's ability to detect and measure speech depends on +the signal being above the quantization noise floor. Higher bit depths +provide more dynamic range: + +| Bit depth | Dynamic range | Noise floor | +|-----------|--------------|-------------| +| 16-bit | ~96 dB | -96 dBov | +| 24-bit | ~144 dB | -144 dBov | +| 32-bit | ~192 dB | -192 dBov | + +### Test results: very quiet speech (-91 dBov) + +| Resolution | Active level | Activity | Result | +|------------|-------------|----------|--------| +| 32-bit | -91.029 dBov | 90.2% | Speech correctly detected and measured | +| 16-bit ADC | — | 0% | **Signal below noise floor — undetectable** | + +With 32-bit resolution, the speech voltmeter correctly measures a signal at +-91 dBov with 90% activity. With a 16-bit ADC simulation (`-bits 16`), the +same signal falls below the quantization threshold and the algorithm reports +zero activity — it cannot distinguish speech from silence. + +For signals at normal levels (-26 dBov), all bit depths produce identical +measurements, confirming full backward compatibility. -# Makefiles +# Source files -Makefiles have been provided for automatic build-up of the executable program -and to process a test file. ``` -makefile.djc: ... make file for MSDOS port of gcc -makefile.tcc .... DOS make file, for tcc -makefile.unx .... make file for Unix machines. Set up for gcc, may be tailored +sv-p56.c ........ P.56 speech voltmeter module +sv-p56.h ........ prototypes and definitions for the SV module +sv56demo.c ...... demo program: measure active level and equalize +actlevel.c ...... demo program: measure level/min/max for multiple files ``` -# Test file +# Dependencies (from ../utl) -The file `voice.src`, also used for testing the IS54 VSELP, is needed -for testing the sv-p56 demo programs. A reference, normalized file, -is available in the ZIP-compatible archive sv56-tst.zip. [pk]unzip is -necessary to extract the reference processed file. The contents of this -archive file is, as reported by unzip: ``` - Length Method Size Ratio Date Time CRC-32 Name - ------ ------ ---- ----- ---- ---- ------ ---- - 105472 Deflate 79982 24% 01-12-95 10:23 66211f99 voice.nrm - 105472 Deflate 80086 24% 08-21-95 12:25 8d3c67bf voice.ltl - ------ ------ --- ------- - 210944 160068 24% 2 +ugst-utl.c ...... float/short conversion and gain/loss functions +ugst-utl.h ...... prototypes for UGST utilities +ugstdemo.h ...... macros for UGST demo programs +wav_io.c ........ WAV file I/O (auto-detect, read, write) +wav_io.h ........ WAV I/O prototypes ``` -NOTE! This file is in the big-endian (high-byte first) format. Therefore, - before using under MSDOS or VAX/VMS, the files need to be byte-swapped. - See unsupported program sb in the ../unsup directory. If testing is - done using the provided makefiles, they will attempt to carry out - the necessary byte-swapping for voice.nrm. For this, a version of - the utility awk (gawk is preferred) must be available in the path. +# Test data --- -- +The `test_data/` directory contains: +- `voice.src` — raw 16-bit PCM test signal +- `voice.nrm`, `voice.ltl` — reference outputs for regression tests +- `speech_*.wav` — WAV test files at various bit depths and levels +- `*.ref`, `*.log.ref` — reference outputs for binary and text comparison diff --git a/src/sv56/actlevel.c b/src/sv56/actlevel.c index 0fc01e5d..86dac198 100644 --- a/src/sv56/actlevel.c +++ b/src/sv56/actlevel.c @@ -155,6 +155,7 @@ /* ... Include of utilities ... */ #include "ugst-utl.h" +#include "wav_io.h" /* ... Local definitions ... */ #define DEF_BLK_LEN 256 /* samples per block */ @@ -423,16 +424,19 @@ int main (int argc, char *argv[]) { /* File-related variables */ char FileIn[150]; - FILE *Fi; /* input file pointer */ + AUDIO_FILE *Fi; /* input file pointer */ FILE *out = stdout; /* where to print the statistical results */ #ifdef VMS char mrs[15]; #endif /* Other variables */ - short buffer[4096]; + short short_buf[4096]; + long long_buf[4096]; float Buf[4096]; - long start_byte, bitno = 16; + long start_byte, bitno = 16, k; + char user_set_bitno = 0; + int file_bitno, is_float; double sf = 16000; /* Hz */ double ActiveLeveldB, level = 0, gain = 0; static char funny[] = "|/-\\|/-\\", funny_size = sizeof (funny), quiet = 0; @@ -470,8 +474,9 @@ int main (int argc, char *argv[]) { argv++; argc--; } else if (strcmp (argv[1], "-bits") == 0) { - /* Change default sampling frequency */ + /* Change default A/D resolution */ bitno = atoi (argv[2]); + user_set_bitno = 1; /* Update argc/argv to next valid option/argument */ argv += 2; @@ -543,9 +548,6 @@ int main (int argc, char *argv[]) { start_byte *= N * sizeof (short); N2_ori = N2; - /* Overflow (saturation) point */ - Overflow = pow ((double) 2.0, (double) (bitno - 1)); - /* REPEAT FOR ALL FILES IN THE COMMAND LINE */ while (argc > 1) { @@ -554,30 +556,44 @@ int main (int argc, char *argv[]) { argv++; argc--; - /* Reset variables for speech level measurements */ - init_speech_voltmeter (&state, sf); - /* ......... FILE PREPARATION ......... */ /* Opening input file; abort if there's any problem */ -#ifdef VMS - sprintf (mrs, "mrs=%d", 2 * N); -#endif - if ((Fi = fopen (FileIn, RB)) == NULL) + if ((Fi = audio_open_read (FileIn, 0, 0, 0)) == NULL) KILL (FileIn, 2); + /* If WAV, derive file bit depth and sample rate from header */ + if (audio_is_wav (Fi)) { + file_bitno = Fi->bits_per_sample; + if (audio_get_sample_rate (Fi) > 0) + sf = (double) audio_get_sample_rate (Fi); + if (!user_set_bitno) + bitno = file_bitno; + } else { + file_bitno = 16; + } + is_float = (audio_is_wav (Fi) && Fi->audio_format == 3); + if (is_float && !user_set_bitno) + bitno = 32; + + /* Overflow (saturation) point */ + Overflow = is_float ? 1.0 : pow ((double) 2.0, (double) (file_bitno - 1)); + + /* Reset variables for speech level measurements */ + init_speech_voltmeter (&state, sf, (int)bitno); + /* Reinitialize number of blocks as specified initially */ N2 = N2_ori; /* Check if is to process the whole file */ if (N2 == 0) { - struct stat st; - stat (FileIn, &st); - N2 = ceil (st.st_size / (double) (N * sizeof (short))); + int bps = file_bitno / 8; + long data_size = audio_get_data_size (Fi); + N2 = ceil ((data_size - start_byte) / (double) (N * bps)); } /* Move pointer to 1st block of interest */ - if (fseek (Fi, start_byte, 0) < 0l) + if (audio_seek (Fi, start_byte) < 0) KILL (FileIn, 4); @@ -587,19 +603,27 @@ int main (int argc, char *argv[]) { if (!quiet) fprintf (stderr, " Processing \r"); for (i = 0; i < N2; i++) { - if ((l = fread (buffer, sizeof (short), N, Fi)) > 0) { - /* ... Convert samples to float */ - sh2fl ((long) l, buffer, Buf, bitno, 1); - - /* ... Get the active level */ - ActiveLeveldB = speech_voltmeter (Buf, (long) l, &state); - - /* Print progress flag */ - if (!quiet) - fprintf (stderr, "%c\r", funny[i % funny_size]); + if (is_float) { + if ((l = audio_read (Fi, Buf, N)) <= 0) + KILL (FileIn, 5); + } else if (file_bitno <= 16) { + if ((l = audio_read (Fi, short_buf, N)) <= 0) + KILL (FileIn, 5); + for (k = 0; k < l; k++) + Buf[k] = (float) ((double) short_buf[k] / Overflow); } else { - KILL (FileIn, 5); + if ((l = audio_read (Fi, long_buf, N)) <= 0) + KILL (FileIn, 5); + for (k = 0; k < l; k++) + Buf[k] = (float) ((double) long_buf[k] / Overflow); } + + /* ... Get the active level */ + ActiveLeveldB = speech_voltmeter (Buf, (long) l, &state); + + /* Print progress flag */ + if (!quiet) + fprintf (stderr, "%c\r", funny[i % funny_size]); } if (!quiet) fprintf (stderr, "\n"); @@ -681,7 +705,7 @@ int main (int argc, char *argv[]) { #endif /* LOCAL_PRINT */ /* Close current file */ - fclose (Fi); + audio_close (Fi); } /* FINALIZATIONS */ diff --git a/src/sv56/sv-p56.c b/src/sv56/sv-p56.c index 8c0bc420..a09fe7c3 100644 --- a/src/sv56/sv-p56.c +++ b/src/sv56/sv-p56.c @@ -223,21 +223,23 @@ double bin_interp (double upcount, double lwcount, double upthr, double lwthr, d #define M 15.9 /* in [dB] */ #define THRES_NO 15 /* number of thresholds in the speech voltmeter */ -void init_speech_voltmeter (SVP56_state * state, double sampl_freq) { +void init_speech_voltmeter (SVP56_state * state, double sampl_freq, int bitno) { double x; long I, j; /* First initializations */ state->f = sampl_freq; + state->bitno = bitno; + state->thres_no = bitno - 1; I = floor (H * state->f + 0.5); - /* Inicialization of threshold vector */ - for (x = 0.5, j = 1; j <= THRES_NO; j++, x /= 2.0) - state->c[THRES_NO - j] = x; + /* Inicialization of threshold vector: geometric progression from 0.5 down to 2^-(bitno-1) */ + for (x = 0.5, j = 1; j <= state->thres_no; j++, x /= 2.0) + state->c[state->thres_no - j] = x; /* Inicialization of activity and hangover count vectors */ - for (j = 0; j < THRES_NO; j++) { + for (j = 0; j < state->thres_no; j++) { state->a[j] = 0; state->hang[j] = I; } @@ -247,8 +249,8 @@ void init_speech_voltmeter (SVP56_state * state, double sampl_freq) { /* Inicialization of other quantities referring to state variables */ state->max = 0; - state->maxP = -32768.; - state->maxN = 32767.; + state->maxP = -pow(2.0, (double)(bitno - 1)); + state->maxN = pow(2.0, (double)(bitno - 1)) - 1.0; /* Defining the 0 dB reference level in terms of normalized values */ state->refdB = 0 /* dBov */ ; @@ -350,7 +352,7 @@ double speech_voltmeter (float *buffer, long smpno, SVP56_state * state) { int I, j; long k; double g, x, AdB, CdB, AmdB, CmdB, ActiveSpeechLevel; - double LongTermLevel, Delta[15]; + double LongTermLevel, Delta[SVP56_MAX_THRESHOLDS]; /* Some initializations */ @@ -380,7 +382,7 @@ double speech_voltmeter (float *buffer, long smpno, SVP56_state * state) { state->q = g * (state->q) + (1 - g) * (state->p); /* Applies threshold to the envelope q */ - for (j = 0; j < THRES_NO; j++) { + for (j = 0; j < state->thres_no; j++) { if ((state->q) >= state->c[j]) { state->a[j]++; state->hang[j] = 0; @@ -412,7 +414,7 @@ double speech_voltmeter (float *buffer, long smpno, SVP56_state * state) { return (ActiveSpeechLevel); /* Proceed serially for steps 2 and up -- this is the most common case */ - for (j = 1; j < THRES_NO; j++) { + for (j = 1; j < state->thres_no; j++) { if (state->a[j] != 0) { AdB = 10 * log10 (((state->sq) / state->a[j]) + MIN_LOG_OFFSET); CdB = 20 * log10 (((double) state->c[j]) + MIN_LOG_OFFSET); diff --git a/src/sv56/sv-p56.h b/src/sv56/sv-p56.h index c21116f9..5eb9e600 100644 --- a/src/sv56/sv-p56.h +++ b/src/sv56/sv-p56.h @@ -11,7 +11,7 @@ COPYRIGHT NOTE: This source code, and all of its derivations, is subject to the "ITU-T General Public License". Please have it read in the distribution disk, or in the ITU-T - Recommendation G.191 on "SOFTWARE TOOLS FOR SPEECH AND AUDIO + Recommendation G.191 on "SOFTWARE TOOLS FOR SPEECH AND AUDIO CODING STANDARDS". ============================================================= @@ -19,15 +19,15 @@ 28.Feb.92 v1.0 First version 18.May.92 v1.1 Prototype of new init_speech_voltmeter; removal of `init` parameter in speech_voltmeter; - macros for accessing SVP56_state statistics. + macros for accessing SVP56_state statistics. - 01.Sep.95 v2.2 Updated version number to match sv-p56.c and added + 01.Sep.95 v2.2 Updated version number to match sv-p56.c and added smart prototypes ============================================================================ */ #ifndef SPEECH_VOLTMETER_defined -#define SPEECH_VOLTMETER_defined 220 +#define SPEECH_VOLTMETER_defined 230 /* DEFINITION FOR SMART PROTOTYPES */ #ifndef ARGS @@ -38,12 +38,26 @@ #endif #endif +/* + * Maximum bit depth supported by the speech voltmeter. + * Increase this constant if higher bit depths are needed. + */ +#define SVP56_MAX_NO_BITS 32 + +/* + * Maximum number of thresholds supported by the speech voltmeter. + * Derived from SVP56_MAX_NO_BITS: thres_no = bitno - 1. + */ +#define SVP56_MAX_THRESHOLDS (SVP56_MAX_NO_BITS - 1) + /* State for speech voltmeter function */ typedef struct { float f; /* sampling frequency, in Hz */ - unsigned long a[15]; /* activity count */ - double c[15]; /* threshold level; 15 is the no.of thres. */ - unsigned long hang[15]; /* hangover count */ + unsigned long a[SVP56_MAX_THRESHOLDS]; /* activity count per threshold */ + double c[SVP56_MAX_THRESHOLDS]; /* threshold level */ + unsigned long hang[SVP56_MAX_THRESHOLDS]; /* hangover count */ + int thres_no; /* actual number of thresholds in use (bitno-1) */ + int bitno; /* bit depth of input signal (default 16) */ unsigned long n; /* number of samples read since last reset */ double s; /* sum of all samples since last reset */ double sq; /* squared sum of samples since last reset */ @@ -59,7 +73,7 @@ typedef struct { /* Speech voltmeter prototypes */ double bin_interp ARGS ((double upcount, double lwcount, double upthr, double lwthr, double Margin, double tol)); -void init_speech_voltmeter ARGS ((SVP56_state * state, double sampl_freq)); +void init_speech_voltmeter ARGS ((SVP56_state * state, double sampl_freq, int bitno)); double speech_voltmeter ARGS ((float *buffer, long smpno, SVP56_state * state)); diff --git a/src/sv56/sv56demo.c b/src/sv56/sv56demo.c index 89fb4cc5..a2387698 100644 --- a/src/sv56/sv56demo.c +++ b/src/sv56/sv56demo.c @@ -185,6 +185,7 @@ /* ... Include of utilities ... */ #include "ugst-utl.h" +#include "wav_io.h" /* Local definitions */ #define MIN_LOG_OFFSET 1.0e-20 /* To avoid sigularity with log(0.0) */ @@ -424,7 +425,7 @@ int main (int argc, char *argv[]) { /* Parameters for operation */ double Overflow; /* Max.positive value for AD_resolution bits */ - long N = 256, N1 = 1, N2 = 0, i, l; + long N = 256, N1 = 1, N2 = 0, i, l, k; double NdB = -26; /* dBov */ /* Intermediate storage variables for speech voltmeter */ @@ -432,7 +433,7 @@ int main (int argc, char *argv[]) { /* File-related variables */ char FileIn[MAX_STRLEN], FileOut[MAX_STRLEN]; - FILE *Fi, *Fo; /* input/output file pointers */ + AUDIO_FILE *Fi, *Fo; /* input/output file pointers */ FILE *out = stdout; /* where to print the statistical results */ #ifdef VMS char mrs[15]; @@ -440,13 +441,16 @@ int main (int argc, char *argv[]) { /* Other variables */ char quiet = 0, use_active_level = 1, long_summary = 1; - short buffer[4096]; + short short_buf[4096]; /* for 8/16-bit reads */ + long long_buf[4096]; /* for 24/32-bit reads */ + int write_buf32[4096]; /* for 32-bit writes (4 bytes each) */ float Buf[4096]; long NrSat = 0, start_byte, bitno = 16; + char user_set_bitno = 0, is_float = 0; + int bps, file_bitno; double sf = 16000, factor; double ActiveLeveldB, DesiredSpeechLeveldB; static char funny[5] = { '/', '-', '\\', '|', '-' }; - static unsigned mask[5] = { 0xFFFF, 0xFFFE, 0xFFFB, 0xFFF8, 0xFFF0 }; /* ......... GET PARAMETERS ......... */ @@ -471,8 +475,9 @@ int main (int argc, char *argv[]) { argv += 2; argc -= 2; } else if (strcmp (argv[1], "-bits") == 0) { - /* Change default sampling frequency */ + /* Change default A/D resolution */ bitno = atol (argv[2]); + user_set_bitno = 1; /* Update argc/argv to next valid option/argument */ argv += 2; @@ -557,44 +562,65 @@ int main (int argc, char *argv[]) { FIND_PAR_D (7, "_Sampling Frequency: ................... ", sf, sf); FIND_PAR_L (8, "_A/D resolution: ....................... ", bitno, bitno); + /* Validate bitno */ + if (bitno < 1 || bitno > 32) { + fprintf (stderr, "Error: bitno must be between 1 and 32\n"); + exit (1); + } + +/* + * ......... FILE PREPARATION ......... + */ + + /* Opening input file; auto-detects WAV vs raw */ + if ((Fi = audio_open_read (FileIn, 0, 0, 0)) == NULL) + KILL (FileIn, 2); + + /* If WAV, derive file bit depth and sample rate from header */ + if (audio_is_wav (Fi)) { + file_bitno = Fi->bits_per_sample; + if (audio_get_sample_rate (Fi) > 0) + sf = (double) audio_get_sample_rate (Fi); + if (!user_set_bitno) + bitno = file_bitno; + else if (bitno > file_bitno) { + fprintf (stderr, "Error: requested resolution (%ld bits) exceeds file bit depth (%d)\n", bitno, file_bitno); + exit (1); + } + } else { + file_bitno = 16; /* raw PCM is always 16-bit */ + } + + /* Check if input is IEEE float format */ + is_float = (audio_is_wav (Fi) && Fi->audio_format == 3); + if (is_float && !user_set_bitno) + bitno = 32; + + /* Bytes per sample for the file format */ + bps = file_bitno / 8; /* ......... SOME INITIALIZATIONS ......... */ start_byte = --N1; - start_byte *= N * sizeof (short); + start_byte *= N * bps; /* Check if is to process the whole file */ if (N2 == 0) { - struct stat st; - - /* ... find the input file size ... */ - stat (FileIn, &st); - N2 = ceil ((st.st_size - start_byte) / (double) (N * sizeof (short))); + long data_size = audio_get_data_size (Fi); + N2 = ceil ((data_size - start_byte) / (double) (N * bps)); } - /* Overflow (saturation) point */ - Overflow = pow ((double) 2.0, (double) (bitno - 1)); + /* Overflow (saturation) point: file_overflow for normalization, Overflow for algorithm */ + Overflow = is_float ? 1.0 : pow ((double) 2.0, (double) (file_bitno - 1)); /* reset variables for speech level measurements */ - init_speech_voltmeter (&state, sf); - - -/* - * ......... FILE PREPARATION ......... - */ - - /* Opening input file; abort if there's any problem */ -#ifdef VMS - sprintf (mrs, "mrs=%d", 2 * N); -#endif - if ((Fi = fopen (FileIn, RB)) == NULL) - KILL (FileIn, 2); + init_speech_voltmeter (&state, sf, (int)bitno); /* Creates output file */ - if ((Fo = fopen (FileOut, WB)) == NULL) + if ((Fo = audio_open_write (FileOut, (long) sf, 1, file_bitno)) == NULL) KILL (FileOut, 3); /* Move pointer to 1st block of interest */ - if (fseek (Fi, start_byte, 0) < 0l) + if (audio_seek (Fi, start_byte) < 0) KILL (FileIn, 4); @@ -607,19 +633,27 @@ int main (int argc, char *argv[]) { /* Process selected blocks */ for (i = 0; i < N2; i++) { /* Read samples ... */ - if ((l = fread (buffer, sizeof (short), N, Fi)) > 0) { - /* ... Convert samples to float */ - sh2fl ((long) l, buffer, Buf, bitno, 1); - - /* ... Get the active level */ - ActiveLeveldB = speech_voltmeter (Buf, (long) l, &state); - - /* Print some preliminary information */ - if (!quiet) - printf ("%c\r", funny[i % 5]); + if (is_float) { + if ((l = audio_read (Fi, Buf, N)) <= 0) + KILL (FileIn, 5); + } else if (file_bitno <= 16) { + if ((l = audio_read (Fi, short_buf, N)) <= 0) + KILL (FileIn, 5); + for (k = 0; k < l; k++) + Buf[k] = (float) ((double) short_buf[k] / Overflow); } else { - KILL (FileIn, 5); + if ((l = audio_read (Fi, long_buf, N)) <= 0) + KILL (FileIn, 5); + for (k = 0; k < l; k++) + Buf[k] = (float) ((double) long_buf[k] / Overflow); } + + /* ... Get the active level */ + ActiveLeveldB = speech_voltmeter (Buf, (long) l, &state); + + /* Print some preliminary information */ + if (!quiet) + printf ("%c\r", funny[i % 5]); } /* Beautify screen ... */ @@ -645,26 +679,60 @@ int main (int argc, char *argv[]) { /* EQUALIZATION: hard clipping (with truncation) */ /* Move pointer to 1st desired block */ - if (fseek (Fi, start_byte, 0) < 0l) + if (audio_seek (Fi, start_byte) < 0) KILL (FileIn, 4); /* Get data of interest, equalize and de-normalize */ for (i = 0; i < N2; i++) { - if ((l = fread (buffer, sizeof (short), N, Fi)) > 0) { - /* convert samples to float */ - sh2fl ((long) l, buffer, Buf, bitno, 1); - - /* equalizes vector */ + if (is_float) { + if ((l = audio_read (Fi, Buf, N)) <= 0) + KILL (FileIn, 5); + /* equalizes vector and convert to 32-bit PCM */ + for (k = 0; k < l; k++) { + double val = (double) Buf[k] * factor; + if (val > 1.0 - (1.0 / 2147483648.0)) { val = 1.0 - (1.0 / 2147483648.0); NrSat++; } + else if (val < -1.0) { val = -1.0; NrSat++; } + write_buf32[k] = (int) (val * 2147483648.0); + } + if ((l = audio_write (Fo, write_buf32, l)) < 0) + KILL (FileOut, 6); + } else if (file_bitno <= 16) { + if ((l = audio_read (Fi, short_buf, N)) <= 0) + KILL (FileIn, 5); + /* Use float path for backward-compatible rounding */ + for (k = 0; k < l; k++) + Buf[k] = (float) ((double) short_buf[k] / Overflow); scale (Buf, (long) l, (double) factor); - - /* Convert from float to short with hard clip and truncation */ - NrSat += fl2sh ((long) l, Buf, buffer, (double) 0.0, mask[16 - bitno]); - - /* write equalized, de-normalized and hard-clipped samples to file */ - if ((l = fwrite (buffer, sizeof (short), l, Fo)) < 0) + for (k = 0; k < l; k++) { + double val = (double) Buf[k] * Overflow; + if (val > Overflow - 1.0) { val = Overflow - 1.0; NrSat++; } + else if (val < -Overflow) { val = -Overflow; NrSat++; } + short_buf[k] = (short) val; + } + if ((l = audio_write (Fo, short_buf, l)) < 0) + KILL (FileOut, 6); + } else if (file_bitno == 24) { + if ((l = audio_read (Fi, long_buf, N)) <= 0) + KILL (FileIn, 5); + for (k = 0; k < l; k++) { + double val = (double) long_buf[k] * factor; + if (val > Overflow - 1.0) { val = Overflow - 1.0; NrSat++; } + else if (val < -Overflow) { val = -Overflow; NrSat++; } + long_buf[k] = (long) val; + } + if ((l = audio_write (Fo, long_buf, l)) < 0) KILL (FileOut, 6); } else { - KILL (FileIn, 5); + if ((l = audio_read (Fi, long_buf, N)) <= 0) + KILL (FileIn, 5); + for (k = 0; k < l; k++) { + double val = (double) long_buf[k] * factor; + if (val > Overflow - 1.0) { val = Overflow - 1.0; NrSat++; } + else if (val < -Overflow) { val = -Overflow; NrSat++; } + write_buf32[k] = (int) val; + } + if ((l = audio_write (Fo, write_buf32, l)) < 0) + KILL (FileOut, 6); } } @@ -680,8 +748,8 @@ int main (int argc, char *argv[]) { printf ("---> DONE \n"); /* Close files ... */ - fclose (Fi); - fclose (Fo); + audio_close (Fi); + audio_close (Fo); if (out != stdout) fclose (out); #if !defined(VMS) diff --git a/src/sv56/test_data/actlev_multi.log.ref b/src/sv56/test_data/actlev_multi.log.ref new file mode 100644 index 00000000..050558a9 --- /dev/null +++ b/src/sv56/test_data/actlev_multi.log.ref @@ -0,0 +1,2 @@ +Samples: 105472 Min: -5141930 Max: 8227312 DC: 2326.99 RMSLev[dB]: -25.478 ActLev[dB]: -25.023 %Active: 90.044 RMSPkF[dB]: 25.310 ActPkF[dB]: 24.854 test_data/speech_normal_24bit.wav +Samples: 105472 Min: -659712 Max: 1055232 DC: 298.53 RMSLev[dB]: -91.478 ActLev[dB]: -91.029 %Active: 90.181 RMSPkF[dB]: 25.306 ActPkF[dB]: 24.858 test_data/speech_vquiet_32bit.wav diff --git a/src/sv56/test_data/actlev_normal_24bit.log.ref b/src/sv56/test_data/actlev_normal_24bit.log.ref new file mode 100644 index 00000000..79982f46 --- /dev/null +++ b/src/sv56/test_data/actlev_normal_24bit.log.ref @@ -0,0 +1 @@ +Samples: 105472 Min: -5141930 Max: 8227312 DC: 2326.99 RMSLev[dB]: -25.478 ActLev[dB]: -25.023 %Active: 90.044 RMSPkF[dB]: 25.310 ActPkF[dB]: 24.854 test_data/speech_normal_24bit.wav diff --git a/src/sv56/test_data/actlev_vquiet_32bit.log.ref b/src/sv56/test_data/actlev_vquiet_32bit.log.ref new file mode 100644 index 00000000..9373737a --- /dev/null +++ b/src/sv56/test_data/actlev_vquiet_32bit.log.ref @@ -0,0 +1 @@ +Samples: 105472 Min: -659712 Max: 1055232 DC: 298.53 RMSLev[dB]: -91.478 ActLev[dB]: -91.029 %Active: 90.181 RMSPkF[dB]: 25.306 ActPkF[dB]: 24.858 test_data/speech_vquiet_32bit.wav diff --git a/src/sv56/test_data/actlev_vquiet_32bit_16bit.log.ref b/src/sv56/test_data/actlev_vquiet_32bit_16bit.log.ref new file mode 100644 index 00000000..5b2b193f --- /dev/null +++ b/src/sv56/test_data/actlev_vquiet_32bit_16bit.log.ref @@ -0,0 +1 @@ +Samples: 105472 Min: -659712 Max: 1055232 DC: 298.53 RMSLev[dB]: -91.478 ActLev[dB]: -100.000 %Active: 0.000 RMSPkF[dB]: 25.306 ActPkF[dB]: 33.828 test_data/speech_vquiet_32bit.wav diff --git a/src/sv56/test_data/speech_normal_16bit.wav b/src/sv56/test_data/speech_normal_16bit.wav new file mode 100644 index 00000000..a66436b6 Binary files /dev/null and b/src/sv56/test_data/speech_normal_16bit.wav differ diff --git a/src/sv56/test_data/speech_normal_24bit.log.ref b/src/sv56/test_data/speech_normal_24bit.log.ref new file mode 100644 index 00000000..fea652c8 --- /dev/null +++ b/src/sv56/test_data/speech_normal_24bit.log.ref @@ -0,0 +1,19 @@ + ------------------------------------------------------- + Input file: ................... test_data/speech_normal_24bit.wav, 24 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 412 [] + dBov desired for output: ...... -26.000 [dBov] + Norm factor desired is: ....... 0.894 [times] + Max norm WITHOUT saturation: .. -24.854 [dBov] + ------------------------------------------------------- + DC level: ..................... 2327 [PCM] + Maximum positive value: ....... 8227312 [PCM] + Maximum negative value: ....... -5141930 [PCM] + ------------------------------------------------------- + Long term energy (rms): ....... -25.478 [dBov] + Active speech level: .......... -25.023 [dBov] + RMS peak-factor found: ........ 25.310 [dB] + Active peak factor found: ..... 24.854 [dB] + Activity factor: .............. 90.044 [%] + ------------------------------------------------------- diff --git a/src/sv56/test_data/speech_normal_24bit.ref b/src/sv56/test_data/speech_normal_24bit.ref new file mode 100644 index 00000000..7d91f4a8 Binary files /dev/null and b/src/sv56/test_data/speech_normal_24bit.ref differ diff --git a/src/sv56/test_data/speech_normal_24bit.wav b/src/sv56/test_data/speech_normal_24bit.wav new file mode 100644 index 00000000..cdca0c79 Binary files /dev/null and b/src/sv56/test_data/speech_normal_24bit.wav differ diff --git a/src/sv56/test_data/speech_normal_32bit.log.ref b/src/sv56/test_data/speech_normal_32bit.log.ref new file mode 100644 index 00000000..0b7433fa --- /dev/null +++ b/src/sv56/test_data/speech_normal_32bit.log.ref @@ -0,0 +1,19 @@ + ------------------------------------------------------- + Input file: ................... test_data/speech_normal_32bit.wav, 32 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 412 [] + dBov desired for output: ...... -26.000 [dBov] + Norm factor desired is: ....... 0.894 [times] + Max norm WITHOUT saturation: .. -24.854 [dBov] + ------------------------------------------------------- + DC level: ..................... 595710 [PCM] + Maximum positive value: ....... 2106191744 [PCM] + Maximum negative value: ....... -1316334336 [PCM] + ------------------------------------------------------- + Long term energy (rms): ....... -25.478 [dBov] + Active speech level: .......... -25.023 [dBov] + RMS peak-factor found: ........ 25.310 [dB] + Active peak factor found: ..... 24.854 [dB] + Activity factor: .............. 90.044 [%] + ------------------------------------------------------- diff --git a/src/sv56/test_data/speech_normal_32bit.ref b/src/sv56/test_data/speech_normal_32bit.ref new file mode 100644 index 00000000..49c5ac17 Binary files /dev/null and b/src/sv56/test_data/speech_normal_32bit.ref differ diff --git a/src/sv56/test_data/speech_normal_32bit.wav b/src/sv56/test_data/speech_normal_32bit.wav new file mode 100644 index 00000000..7c203d03 Binary files /dev/null and b/src/sv56/test_data/speech_normal_32bit.wav differ diff --git a/src/sv56/test_data/speech_normal_32bit_float.wav b/src/sv56/test_data/speech_normal_32bit_float.wav new file mode 100644 index 00000000..a517054d Binary files /dev/null and b/src/sv56/test_data/speech_normal_32bit_float.wav differ diff --git a/src/sv56/test_data/speech_quiet_16bit.wav b/src/sv56/test_data/speech_quiet_16bit.wav new file mode 100644 index 00000000..419b344e Binary files /dev/null and b/src/sv56/test_data/speech_quiet_16bit.wav differ diff --git a/src/sv56/test_data/speech_quiet_24bit.log.ref b/src/sv56/test_data/speech_quiet_24bit.log.ref new file mode 100644 index 00000000..76aa133c --- /dev/null +++ b/src/sv56/test_data/speech_quiet_24bit.log.ref @@ -0,0 +1,19 @@ + ------------------------------------------------------- + Input file: ................... test_data/speech_quiet_24bit.wav, 24 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 412 [] + dBov desired for output: ...... -26.000 [dBov] + Norm factor desired is: ....... 44.876 [times] + Max norm WITHOUT saturation: .. -24.871 [dBov] + ------------------------------------------------------- + DC level: ..................... 46 [PCM] + Maximum positive value: ....... 164155 [PCM] + Maximum negative value: ....... -102591 [PCM] + ------------------------------------------------------- + Long term energy (rms): ....... -59.478 [dBov] + Active speech level: .......... -59.040 [dBov] + RMS peak-factor found: ........ 25.310 [dB] + Active peak factor found: ..... 24.871 [dB] + Activity factor: .............. 90.403 [%] + ------------------------------------------------------- diff --git a/src/sv56/test_data/speech_quiet_24bit.ref b/src/sv56/test_data/speech_quiet_24bit.ref new file mode 100644 index 00000000..19e05473 Binary files /dev/null and b/src/sv56/test_data/speech_quiet_24bit.ref differ diff --git a/src/sv56/test_data/speech_quiet_24bit.wav b/src/sv56/test_data/speech_quiet_24bit.wav new file mode 100644 index 00000000..ad1029ad Binary files /dev/null and b/src/sv56/test_data/speech_quiet_24bit.wav differ diff --git a/src/sv56/test_data/speech_quiet_32bit.wav b/src/sv56/test_data/speech_quiet_32bit.wav new file mode 100644 index 00000000..86573363 Binary files /dev/null and b/src/sv56/test_data/speech_quiet_32bit.wav differ diff --git a/src/sv56/test_data/speech_quiet_32bit_float.log.ref b/src/sv56/test_data/speech_quiet_32bit_float.log.ref new file mode 100644 index 00000000..00ee7154 --- /dev/null +++ b/src/sv56/test_data/speech_quiet_32bit_float.log.ref @@ -0,0 +1,19 @@ + ------------------------------------------------------- + Input file: ................... test_data/speech_quiet_32bit_float.wav, 32 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 412 [] + dBov desired for output: ...... -26.000 [dBov] + Norm factor desired is: ....... 44.876 [times] + Max norm WITHOUT saturation: .. -24.872 [dBov] + ------------------------------------------------------- + DC level: ..................... 0 [PCM] + Maximum positive value: ....... 0 [PCM] + Maximum negative value: ....... -0 [PCM] + ------------------------------------------------------- + Long term energy (rms): ....... -59.478 [dBov] + Active speech level: .......... -59.040 [dBov] + RMS peak-factor found: ........ 25.310 [dB] + Active peak factor found: ..... 24.872 [dB] + Activity factor: .............. 90.403 [%] + ------------------------------------------------------- diff --git a/src/sv56/test_data/speech_quiet_32bit_float.ref b/src/sv56/test_data/speech_quiet_32bit_float.ref new file mode 100644 index 00000000..27fcc092 Binary files /dev/null and b/src/sv56/test_data/speech_quiet_32bit_float.ref differ diff --git a/src/sv56/test_data/speech_quiet_32bit_float.wav b/src/sv56/test_data/speech_quiet_32bit_float.wav new file mode 100644 index 00000000..f3aac555 Binary files /dev/null and b/src/sv56/test_data/speech_quiet_32bit_float.wav differ diff --git a/src/sv56/test_data/speech_vquiet_32bit.log.ref b/src/sv56/test_data/speech_vquiet_32bit.log.ref new file mode 100644 index 00000000..e2284e77 --- /dev/null +++ b/src/sv56/test_data/speech_vquiet_32bit.log.ref @@ -0,0 +1,19 @@ + ------------------------------------------------------- + Input file: ................... test_data/speech_vquiet_32bit.wav, 32 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 412 [] + dBov desired for output: ...... -26.000 [dBov] + Norm factor desired is: ....... 1784.261 [times] + Max norm WITHOUT saturation: .. -24.858 [dBov] + ------------------------------------------------------- + DC level: ..................... 299 [PCM] + Maximum positive value: ....... 1055232 [PCM] + Maximum negative value: ....... -659712 [PCM] + ------------------------------------------------------- + Long term energy (rms): ....... -91.478 [dBov] + Active speech level: .......... -91.029 [dBov] + RMS peak-factor found: ........ 25.306 [dB] + Active peak factor found: ..... 24.858 [dB] + Activity factor: .............. 90.181 [%] + ------------------------------------------------------- diff --git a/src/sv56/test_data/speech_vquiet_32bit.ref b/src/sv56/test_data/speech_vquiet_32bit.ref new file mode 100644 index 00000000..757bd5c5 Binary files /dev/null and b/src/sv56/test_data/speech_vquiet_32bit.ref differ diff --git a/src/sv56/test_data/speech_vquiet_32bit.wav b/src/sv56/test_data/speech_vquiet_32bit.wav new file mode 100644 index 00000000..310fe13f Binary files /dev/null and b/src/sv56/test_data/speech_vquiet_32bit.wav differ diff --git a/src/sv56/test_data/speech_vquiet_32bit_16bit.log.ref b/src/sv56/test_data/speech_vquiet_32bit_16bit.log.ref new file mode 100644 index 00000000..1e4e978a --- /dev/null +++ b/src/sv56/test_data/speech_vquiet_32bit_16bit.log.ref @@ -0,0 +1,18 @@ + ------------------------------------------------------- + Input file: ................... test_data/speech_vquiet_32bit.wav, 16 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 412 [] + dBov desired for output: ...... -26.000 [dBov] + Activity factor is ZERO -- the file is silence or idle noise + ------------------------------------------------------- + DC level: ..................... 299 [PCM] + Maximum positive value: ....... 1055232 [PCM] + Maximum negative value: ....... -659712 [PCM] + ------------------------------------------------------- + Noise/silence energy (rms): ... -91.478 [dB] + ------------------------------------------------------- +%SV-W-SAT, the dB level chosen causes SATURATION: old max=1055232; new max=5288688069 +%SV-I-MAXLEVDB, the maximum norm factor to PREVENT clipping is -33.828dB; + ------------------------------------------------------- + Number of clippings: .......... 81 [] diff --git a/src/sv56/test_data/speech_vquiet_32bit_16bit.ref b/src/sv56/test_data/speech_vquiet_32bit_16bit.ref new file mode 100644 index 00000000..19994da3 Binary files /dev/null and b/src/sv56/test_data/speech_vquiet_32bit_16bit.ref differ diff --git a/src/sv56/test_data/voice_12bit.log.ref b/src/sv56/test_data/voice_12bit.log.ref new file mode 100644 index 00000000..74d3e0da --- /dev/null +++ b/src/sv56/test_data/voice_12bit.log.ref @@ -0,0 +1,19 @@ + ------------------------------------------------------- + Input file: ................... test_data/voice.src, 12 bits, fs=16000 Hz + Block Length: ................. 256 [samples] + Starting Block: ............... 1 [] + Number of Blocks: ............. 206 [] + dBov desired for output: ...... -30.000 [dBov] + Norm factor desired is: ....... 0.584 [times] + Max norm WITHOUT saturation: .. -24.408 [dBov] + ------------------------------------------------------- + DC level: ..................... 9 [PCM] + Maximum positive value: ....... 29472 [PCM] + Maximum negative value: ....... -19874 [PCM] + ------------------------------------------------------- + Long term energy (rms): ....... -25.478 [dBov] + Active speech level: .......... -25.329 [dBov] + RMS peak-factor found: ........ 24.557 [dB] + Active peak factor found: ..... 24.408 [dB] + Activity factor: .............. 96.625 [%] + ------------------------------------------------------- diff --git a/src/sv56/test_data/voice_12bit.nrm b/src/sv56/test_data/voice_12bit.nrm new file mode 100644 index 00000000..f4271d78 Binary files /dev/null and b/src/sv56/test_data/voice_12bit.nrm differ diff --git a/src/sv56/test_data/voice_16bit.ref b/src/sv56/test_data/voice_16bit.ref new file mode 100644 index 00000000..d6d8ba84 --- /dev/null +++ b/src/sv56/test_data/voice_16bit.ref @@ -0,0 +1 @@ +ActLev[dB]: -25.329 RMSLev[dB]: -25.478 Activity[%]: 96.625 diff --git a/src/sv56/test_data/voice_24.src b/src/sv56/test_data/voice_24.src new file mode 100644 index 00000000..550cb83d Binary files /dev/null and b/src/sv56/test_data/voice_24.src differ diff --git a/src/sv56/test_data/voice_24bit.ref b/src/sv56/test_data/voice_24bit.ref new file mode 100644 index 00000000..d6d8ba84 --- /dev/null +++ b/src/sv56/test_data/voice_24bit.ref @@ -0,0 +1 @@ +ActLev[dB]: -25.329 RMSLev[dB]: -25.478 Activity[%]: 96.625 diff --git a/src/sv56/test_data/voice_32.src b/src/sv56/test_data/voice_32.src new file mode 100644 index 00000000..b82a9840 Binary files /dev/null and b/src/sv56/test_data/voice_32.src differ diff --git a/src/sv56/test_data/voice_32bit.ref b/src/sv56/test_data/voice_32bit.ref new file mode 100644 index 00000000..d6d8ba84 --- /dev/null +++ b/src/sv56/test_data/voice_32bit.ref @@ -0,0 +1 @@ +ActLev[dB]: -25.329 RMSLev[dB]: -25.478 Activity[%]: 96.625 diff --git a/src/utl/wav_io.c b/src/utl/wav_io.c new file mode 100644 index 00000000..733a123a --- /dev/null +++ b/src/utl/wav_io.c @@ -0,0 +1,447 @@ +/* wav_io.c - WAV file I/O support for STL tools + * + * Canonical WAV (RIFF) reader/writer for 8/16/24/32-bit PCM and 32-bit IEEE float. + * See wav_io.h for API documentation. + */ +#include "wav_io.h" +#include +#include +#include + +/* WAV format constants */ +#define WAV_RIFF_TAG 0x46464952 /* "RIFF" */ +#define WAV_WAVE_TAG 0x45564157 /* "WAVE" */ +#define WAV_FMT_TAG 0x20746D66 /* "fmt " */ +#define WAV_DATA_TAG 0x61746164 /* "data" */ +#define WAV_PCM_FORMAT 1 +#define WAV_FLOAT_FORMAT 3 +#define WAV_HEADER_SIZE 44 + +/* --- Helper: read little-endian integers from file --- */ + +static int read_u16 (FILE * fp, unsigned short *val) { + unsigned char b[2]; + if (fread (b, 1, 2, fp) != 2) + return 0; + *val = (unsigned short) (b[0] | (b[1] << 8)); + return 1; +} + +static int read_u32 (FILE * fp, unsigned long *val) { + unsigned char b[4]; + if (fread (b, 1, 4, fp) != 4) + return 0; + *val = (unsigned long) b[0] | ((unsigned long) b[1] << 8) | ((unsigned long) b[2] << 16) | ((unsigned long) b[3] << 24); + return 1; +} + +/* --- Helper: write little-endian integers to file --- */ + +static void write_u16 (FILE * fp, unsigned short val) { + unsigned char b[2]; + b[0] = (unsigned char) (val & 0xFF); + b[1] = (unsigned char) ((val >> 8) & 0xFF); + fwrite (b, 1, 2, fp); +} + +static void write_u32 (FILE * fp, unsigned long val) { + unsigned char b[4]; + b[0] = (unsigned char) (val & 0xFF); + b[1] = (unsigned char) ((val >> 8) & 0xFF); + b[2] = (unsigned char) ((val >> 16) & 0xFF); + b[3] = (unsigned char) ((val >> 24) & 0xFF); + fwrite (b, 1, 4, fp); +} + +/* --- Helper: case-insensitive extension check --- */ + +static int has_wav_extension (const char *filename) { + const char *dot; + dot = strrchr (filename, '.'); + if (!dot) + return 0; + if ((dot[1] == 'w' || dot[1] == 'W') && (dot[2] == 'a' || dot[2] == 'A') && (dot[3] == 'v' || dot[3] == 'V') && dot[4] == '\0') + return 1; + return 0; +} + +/* --- Public API --- */ + +AUDIO_FILE *audio_open_read (const char *filename, long expected_rate, int expected_channels, int expected_bits) { + AUDIO_FILE *af; + unsigned long riff_tag, file_size, wave_tag; + unsigned long chunk_id, chunk_size; + unsigned short audio_format, num_channels, bits_per_sample; + unsigned long sample_rate; + int fmt_found = 0; + + af = (AUDIO_FILE *) calloc (1, sizeof (AUDIO_FILE)); + if (!af) + return NULL; + + af->fp = fopen (filename, "rb"); + if (!af->fp) { + fprintf (stderr, "ERROR: Cannot open input file '%s'\n", filename); + free (af); + return NULL; + } + + af->write_mode = 0; + + /* Check for RIFF header */ + if (!read_u32 (af->fp, &riff_tag) || riff_tag != WAV_RIFF_TAG) { + /* Not a WAV file — treat as raw PCM */ + fseek (af->fp, 0, SEEK_SET); + af->is_wav = 0; + af->channels = 1; + af->sample_rate = 0; + af->bits_per_sample = 16; + af->data_offset = 0; + return af; + } + + /* Parse RIFF/WAVE header */ + if (!read_u32 (af->fp, &file_size) || !read_u32 (af->fp, &wave_tag) || wave_tag != WAV_WAVE_TAG) { + fprintf (stderr, "ERROR: '%s' has RIFF header but is not a WAVE file\n", filename); + fclose (af->fp); + free (af); + return NULL; + } + + /* Find fmt and data chunks */ + while (read_u32 (af->fp, &chunk_id) && read_u32 (af->fp, &chunk_size)) { + if (chunk_id == WAV_FMT_TAG) { + if (!read_u16 (af->fp, &audio_format) || !read_u16 (af->fp, &num_channels) || !read_u32 (af->fp, &sample_rate)) { + fprintf (stderr, "ERROR: Cannot read fmt chunk in '%s'\n", filename); + fclose (af->fp); + free (af); + return NULL; + } + /* Skip byte rate and block align */ + fseek (af->fp, 4 + 2, SEEK_CUR); + if (!read_u16 (af->fp, &bits_per_sample)) { + fprintf (stderr, "ERROR: Cannot read bits_per_sample in '%s'\n", filename); + fclose (af->fp); + free (af); + return NULL; + } + /* Skip any extra fmt bytes */ + if (chunk_size > 16) + fseek (af->fp, chunk_size - 16, SEEK_CUR); + fmt_found = 1; + } else if (chunk_id == WAV_DATA_TAG) { + if (!fmt_found) { + fprintf (stderr, "ERROR: data chunk before fmt chunk in '%s'\n", filename); + fclose (af->fp); + free (af); + return NULL; + } + af->data_offset = ftell (af->fp); + af->data_size = (long) chunk_size; + break; + } else { + /* Skip unknown chunk */ + fseek (af->fp, chunk_size, SEEK_CUR); + } + } + + if (!fmt_found || af->data_offset == 0) { + fprintf (stderr, "ERROR: Missing fmt or data chunk in '%s'\n", filename); + fclose (af->fp); + free (af); + return NULL; + } + + /* Validate format */ + if (audio_format != WAV_PCM_FORMAT && audio_format != WAV_FLOAT_FORMAT) { + fprintf (stderr, "ERROR: '%s' has unsupported WAV format (format tag=%d). Only PCM and IEEE float are supported.\n", filename, audio_format); + fclose (af->fp); + free (af); + return NULL; + } + + if (audio_format == WAV_FLOAT_FORMAT && bits_per_sample != 32) { + fprintf (stderr, "ERROR: '%s' is float WAV with %d bits (only 32-bit float supported).\n", filename, bits_per_sample); + fclose (af->fp); + free (af); + return NULL; + } + + if (audio_format == WAV_PCM_FORMAT && bits_per_sample != 8 && bits_per_sample != 16 && bits_per_sample != 24 && bits_per_sample != 32) { + fprintf (stderr, "ERROR: '%s' has unsupported bit depth (%d). Supported: 8, 16, 24, 32.\n", filename, bits_per_sample); + fclose (af->fp); + free (af); + return NULL; + } + + /* Check expected parameters */ + if (expected_bits > 0 && (int) bits_per_sample != expected_bits) { + fprintf (stderr, "ERROR: WAV bit depth (%d) does not match expected (%d) in '%s'\n", bits_per_sample, expected_bits, filename); + fclose (af->fp); + free (af); + return NULL; + } + + if (expected_rate > 0 && (long) sample_rate != expected_rate) { + fprintf (stderr, "ERROR: WAV sample rate (%lu Hz) does not match expected rate (%ld Hz) in '%s'\n", sample_rate, expected_rate, filename); + fclose (af->fp); + free (af); + return NULL; + } + + if (expected_channels > 0 && (int) num_channels != expected_channels) { + fprintf (stderr, "ERROR: WAV channel count (%d) does not match expected channels (%d) in '%s'\n", num_channels, expected_channels, filename); + fclose (af->fp); + free (af); + return NULL; + } + + af->is_wav = 1; + af->channels = (int) num_channels; + af->sample_rate = (long) sample_rate; + af->bits_per_sample = (int) bits_per_sample; + af->audio_format = (int) audio_format; + + return af; +} + + +AUDIO_FILE *audio_open_write (const char *filename, long sample_rate, int channels, int bits_per_sample) { + AUDIO_FILE *af; + + af = (AUDIO_FILE *) calloc (1, sizeof (AUDIO_FILE)); + if (!af) + return NULL; + + af->fp = fopen (filename, "wb"); + if (!af->fp) { + fprintf (stderr, "ERROR: Cannot open output file '%s'\n", filename); + free (af); + return NULL; + } + + af->write_mode = 1; + af->sample_rate = sample_rate; + af->channels = channels; + af->bits_per_sample = bits_per_sample > 0 ? bits_per_sample : 16; + + if (has_wav_extension (filename)) { + af->is_wav = 1; + /* Write placeholder WAV header (44 bytes) — updated on close */ + write_u32 (af->fp, WAV_RIFF_TAG); + write_u32 (af->fp, 0); /* file size - 8 (placeholder) */ + write_u32 (af->fp, WAV_WAVE_TAG); + /* fmt chunk */ + write_u32 (af->fp, WAV_FMT_TAG); + write_u32 (af->fp, 16); /* fmt chunk size */ + write_u16 (af->fp, WAV_PCM_FORMAT); + write_u16 (af->fp, (unsigned short) af->channels); + write_u32 (af->fp, (unsigned long) af->sample_rate); + write_u32 (af->fp, (unsigned long) (af->sample_rate * af->channels * af->bits_per_sample / 8)); /* byte rate */ + write_u16 (af->fp, (unsigned short) (af->channels * af->bits_per_sample / 8)); /* block align */ + write_u16 (af->fp, (unsigned short) af->bits_per_sample); + /* data chunk header */ + write_u32 (af->fp, WAV_DATA_TAG); + write_u32 (af->fp, 0); /* data size (placeholder) */ + af->data_offset = ftell (af->fp); + af->data_size = 0; + } else { + af->is_wav = 0; + af->data_size = 0; + } + + return af; +} + + +long audio_read (AUDIO_FILE * af, void *buffer, long nsamples) { + int bps, ch, sample_bytes, frame_bytes; + long frames_read, i; + + if (!af || !af->fp || !buffer || nsamples <= 0) + return 0; + + bps = af->bits_per_sample; + ch = af->is_wav ? af->channels : 1; + sample_bytes = (bps == 24) ? 3 : (bps / 8); + frame_bytes = sample_bytes * ch; + + /* Raw files are native-endian; direct fread is safe for 8/16-bit mono */ + if (!af->is_wav && ch <= 1 && bps <= 2) { + return (long) fread (buffer, (size_t) sample_bytes, (size_t) nsamples, af->fp); + } + + /* WAV (little-endian) and multi-channel: extract with byte-order handling */ + { + unsigned char *raw_buf; + + if (ch > 1 && !af->warned_multichan) { + fprintf (stderr, "WARNING: Multi-channel WAV (%d channels). Extracting channel 1 only.\n", ch); + af->warned_multichan = 1; + } + + raw_buf = (unsigned char *) malloc ((size_t) (nsamples * frame_bytes)); + if (!raw_buf) + return 0; + + frames_read = (long) fread (raw_buf, (size_t) frame_bytes, (size_t) nsamples, af->fp); + + if (bps == 8) { + unsigned char *out = (unsigned char *) buffer; + for (i = 0; i < frames_read; i++) + out[i] = raw_buf[i * frame_bytes]; + } else if (bps == 16) { + short *out = (short *) buffer; + for (i = 0; i < frames_read; i++) { + unsigned char *p = raw_buf + i * frame_bytes; + out[i] = (short) (p[0] | (p[1] << 8)); + } + } else if (bps == 24) { + long *out = (long *) buffer; + for (i = 0; i < frames_read; i++) { + unsigned char *p = raw_buf + i * frame_bytes; + out[i] = (long) p[0] | ((long) p[1] << 8) | ((long) (signed char) p[2] << 16); + } + } else if (bps == 32) { + long *out = (long *) buffer; + if (af->audio_format == WAV_FLOAT_FORMAT) { + float *fout = (float *) buffer; + for (i = 0; i < frames_read; i++) { + unsigned char *p = raw_buf + i * frame_bytes; + union { unsigned char b[4]; float f; } u; + u.b[0] = p[0]; u.b[1] = p[1]; u.b[2] = p[2]; u.b[3] = p[3]; + fout[i] = u.f; + } + } else { + for (i = 0; i < frames_read; i++) { + unsigned char *p = raw_buf + i * frame_bytes; + out[i] = (long) p[0] | ((long) p[1] << 8) | ((long) p[2] << 16) | ((long) (signed char) p[3] << 24); + } + } + } + + free (raw_buf); + return frames_read; + } +} + + +long audio_write (AUDIO_FILE * af, void *buffer, long nsamples) { + int bps, sample_bytes; + long n, i; + + if (!af || !af->fp || !buffer || nsamples <= 0) + return 0; + + bps = af->bits_per_sample; + sample_bytes = (bps == 24) ? 3 : (bps / 8); + + if (bps != 24) { + /* For raw or 8-bit: direct fwrite (native endian or single byte) */ + if (!af->is_wav || bps == 8) { + n = (long) fwrite (buffer, (size_t) sample_bytes, (size_t) nsamples, af->fp); + af->data_size += n * sample_bytes; + return n; + } + /* WAV 16-bit: write little-endian */ + if (bps == 16) { + short *in = (short *) buffer; + for (i = 0; i < nsamples; i++) { + unsigned char b[2]; + b[0] = (unsigned char) (in[i] & 0xFF); + b[1] = (unsigned char) ((in[i] >> 8) & 0xFF); + if (fwrite (b, 1, 2, af->fp) != 2) break; + } + af->data_size += i * 2; + return i; + } + /* WAV 32-bit: write little-endian */ + { + int *in = (int *) buffer; + for (i = 0; i < nsamples; i++) { + unsigned char b[4]; + unsigned int val = (unsigned int) in[i]; + b[0] = (unsigned char) (val & 0xFF); + b[1] = (unsigned char) ((val >> 8) & 0xFF); + b[2] = (unsigned char) ((val >> 16) & 0xFF); + b[3] = (unsigned char) ((val >> 24) & 0xFF); + if (fwrite (b, 1, 4, af->fp) != 4) break; + } + af->data_size += i * 4; + return i; + } + } + + /* 24-bit: pack from long */ + { + long *in = (long *) buffer; + for (i = 0; i < nsamples; i++) { + unsigned char b[3]; + long val = in[i]; + b[0] = (unsigned char) (val & 0xFF); + b[1] = (unsigned char) ((val >> 8) & 0xFF); + b[2] = (unsigned char) ((val >> 16) & 0xFF); + if (fwrite (b, 1, 3, af->fp) != 3) + break; + } + af->data_size += i * 3; + return i; + } +} + + +void audio_close (AUDIO_FILE * af) { + if (!af) + return; + + if (af->fp) { + if (af->write_mode && af->is_wav) { + /* Update WAV header with final sizes */ + unsigned long file_size = (unsigned long) af->data_size + WAV_HEADER_SIZE - 8; + fseek (af->fp, 4, SEEK_SET); + write_u32 (af->fp, file_size); + fseek (af->fp, WAV_HEADER_SIZE - 4, SEEK_SET); + write_u32 (af->fp, (unsigned long) af->data_size); + } + fclose (af->fp); + } + + free (af); +} + + +int audio_is_wav (AUDIO_FILE * af) { + return af ? af->is_wav : 0; +} + + +long audio_get_sample_rate (AUDIO_FILE * af) { + return af ? af->sample_rate : 0; +} + + +int audio_get_channels (AUDIO_FILE * af) { + return af ? af->channels : 0; +} + + +int audio_seek (AUDIO_FILE * af, long offset) { + if (!af || !af->fp) + return -1; + return fseek (af->fp, af->data_offset + offset, SEEK_SET) < 0 ? -1 : 0; +} + + +long audio_get_data_size (AUDIO_FILE * af) { + if (!af || !af->fp) + return 0; + if (af->is_wav) + return af->data_size; + else { + long cur = ftell (af->fp); + fseek (af->fp, 0, SEEK_END); + long size = ftell (af->fp); + fseek (af->fp, cur, SEEK_SET); + return size; + } +} diff --git a/src/utl/wav_io.h b/src/utl/wav_io.h new file mode 100644 index 00000000..63c2f64c --- /dev/null +++ b/src/utl/wav_io.h @@ -0,0 +1,74 @@ +/* wav_io.h - WAV file I/O support for STL tools + * + * Provides transparent reading/writing of WAV and raw PCM files. + * Input format is auto-detected (RIFF header check). + * Output format is determined by filename extension (.wav = WAV, else raw). + * Supports 8/16/24/32-bit PCM and 32-bit IEEE float WAV. + */ +#ifndef WAV_IO_H +#define WAV_IO_H + +#include + +typedef struct { + FILE *fp; + int is_wav; /* 1 if WAV format, 0 if raw PCM */ + int channels; /* Number of channels (from WAV header, or 1 for raw) */ + long sample_rate; /* Sample rate in Hz (from WAV header, or 0 for raw) */ + int bits_per_sample; /* Bits per sample (from WAV header, or 16 for raw) */ + int audio_format; /* WAV format tag: 1=PCM, 3=IEEE float (0 for raw) */ + long data_offset; /* Byte offset to start of PCM data */ + long data_size; /* Bytes of PCM data written (for WAV write) */ + int warned_multichan; /* Flag: already warned about multi-channel extraction */ + int write_mode; /* 1 if opened for writing, 0 for reading */ +} AUDIO_FILE; + +/* Open file for reading. Auto-detects WAV vs raw PCM. + * expected_rate: if > 0, error if WAV sample rate doesn't match + * expected_channels: if > 0, error if WAV channel count doesn't match + * expected_bits: if > 0, error if WAV bits_per_sample doesn't match + * Returns NULL on error (prints message to stderr). */ +AUDIO_FILE *audio_open_read (const char *filename, long expected_rate, int expected_channels, int expected_bits); + +/* Open file for writing. Writes WAV if filename ends in .wav, raw otherwise. + * sample_rate/channels/bits_per_sample used for WAV header. + * For raw output, these are ignored. + * Returns NULL on error. */ +AUDIO_FILE *audio_open_write (const char *filename, long sample_rate, int channels, int bits_per_sample); + +/* Read up to nsamples into buffer, using the appropriate sample size + * based on the file's bits_per_sample (8/16/24/32-bit PCM or 32-bit float). + * Buffer must be large enough for the native sample size. + * For multi-channel WAV, extracts channel 0 (warns once on stderr). + * Returns number of samples actually read. */ +long audio_read (AUDIO_FILE * af, void *buffer, long nsamples); + +/* Write nsamples from buffer, using the appropriate sample size + * based on the file's bits_per_sample. + * Returns number of samples actually written. */ +long audio_write (AUDIO_FILE * af, void *buffer, long nsamples); + +/* Close file. For WAV output, updates header with final data size. */ +void audio_close (AUDIO_FILE * af); + +/* Returns 1 if file was detected/opened as WAV, 0 if raw */ +int audio_is_wav (AUDIO_FILE * af); + +/* Returns sample rate from WAV header (0 if raw) */ +long audio_get_sample_rate (AUDIO_FILE * af); + +/* Returns channel count from WAV header (0 if raw) */ +int audio_get_channels (AUDIO_FILE * af); + +/* Seek to a byte offset relative to the start of PCM data. + * For WAV files, accounts for the header (data_offset). + * For raw files, seeks from byte 0. + * Returns 0 on success, -1 on failure. */ +int audio_seek (AUDIO_FILE * af, long offset); + +/* Returns total size of PCM data in bytes. + * For WAV, returns the data chunk size from the header. + * For raw, returns the file size. */ +long audio_get_data_size (AUDIO_FILE * af); + +#endif /* WAV_IO_H */ diff --git a/src/wmc_tool/CMakeLists.txt b/src/wmc_tool/CMakeLists.txt index 858ee70c..7a82a12b 100644 --- a/src/wmc_tool/CMakeLists.txt +++ b/src/wmc_tool/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1...3.25) project(wmc_tool) include(CTest) @@ -8,7 +8,7 @@ if(WIN32) -D_CRT_SECURE_NO_WARNINGS /MP ) -endif() +endif() add_executable(wmc_tool c_parser.cpp output.cpp text_utils.cpp wmc_tool.cpp) find_package(PythonInterp) @@ -29,6 +29,3 @@ add_test(NAME wmc_tool_test_rom_file2 COMMAND ${PYTHON_EXECUTABLE} ${TEST_WMC_TO add_test(NAME wmc_tool_test_desinstrument_file COMMAND ${PYTHON_EXECUTABLE} ${TEST_WMC_TOOL_SCRIPT} -e $ -v -d di_test_file1.c WORKING_DIRECTORY ${TEST_DIR}) add_test(NAME wmc_tool_test_desinstrument_rom_file COMMAND ${PYTHON_EXECUTABLE} ${TEST_WMC_TOOL_SCRIPT} -e $ -v -d di_test_rom.c WORKING_DIRECTORY ${TEST_DIR}) add_test(NAME wmc_tool_test_wmc_auto_files COMMAND ${PYTHON_EXECUTABLE} ${TEST_WMC_TOOL_SCRIPT} -e $ -c ${TEST_DIR} test_file3.c WORKING_DIRECTORY ${TEST_DIR}) - - -